SNSへはこちら

Tang Nanoでお手軽FPGA入門(6) - LCDを頑張った

前回の記事で、「LCD に円を表示したかったけれど、リソース不足で無理だった」と言うことを述べた気がします。

流石にこれでは悔しすぎるので、根性を見せて頑張ったよという結果を記事にします。需要あるかわからないけどね。

何がダメだったのか

円という図形を配置するには当然、2乗する回路(入力/出力ともに 16bit 長)が2つ必要になるのですが(半径部分は定数ゆえロジック回路自体は不要)、どうやらこの2乗する回路が LUT などなどのリソースをめちゃ食うらしくて、以下のように書くとどうやらこれが2つ回路上に確保されるらしいです。

    assign LCD_R = ((x - 300) ** 2 + (y - 240) ** 2 <= 100 ** 2) ? 5'b11111 : 5'b00000;

まあそりゃそうですよね。各項で並列計算になりますよそりゃ。

一方、諦めて2乗の項を1つだけにしてみたところ、ちゃんと配置配線が通りました。つまり、2乗回路を1つだけ使うようにすれば、円の描画は実現できるということなのです。
(実際は円をバイナリで読ませてその通りにドットを打てばいいのですが、如何せんやり方がわからないもので...)

実装の概要

んで、大いに苦労しました。どこに苦労したかと言うと...

  • always 文の中身をちゃんと書かないと、xxx(レジスタ変数名) does not match a standard flip-flop.と怒られる
    • すべてを ifelse 文の中に収めるように書けばいいと思う(多分)
  • Verilog が初心者
    • reg は必ずしもレジスタを表すものではないらしい
    • regalways 文で値を代入する変数、wireassign 文で値を代入する変数
    • マジで減算が鬼門。未だに挙動がわからない(調べればいい話ですが...)
  • そもそもどうやって2乗回路を1つだけでやりくりしようか
    • 時分割しか方法はないわけだが...

という感じです。では以下に簡単ですが、実装の図解を載せます。

作ったモジュール:twoInputSquare

取り敢えず2乗する回路を1つにまとめることが必要になるので、まとめてラップしてしまったモジュールを作ることにしました。回路としてはこんな構想です。

仕様はこちら。

  • 2入力1出力の回路。すべて16bit
  • sel によって2乗回路への入力を切り替える
    • これによって時分割の実装を可能にする
    • これをインスタンス化するモジュール内でクロックごとに切り替えるようにすれば時分割が完全に実現可能
  • 計算結果は非同期で出力され、それを wire として出力する

という至極単純なものです。ちなみに何故非同期回路かと言うと、単純にクロック等によるイベントの記述が面倒くさかったということに尽きます。あと、周波数的に多分非同期でも問題なく結果が出るだろうと思ったからです。根拠はありませんが。

ではモジュールを記述しましょう。ポイントは、assign 文等の記述で、1文中に2つの ** 2 を書くと、どうやら並列計算になってしまうということです。なので、それを回避するために自前のモジュールを作成する必要があります。と言っても単純です。以下のように。

module square(input wire[15:0] inp, output wire[15:0] outp);
    assign outp = inp ** 2;
endmodule

単純でしょう?これの入り口をセレクタで選べばいいのですから、この下に以下を書けば良いわけですね。

module twoInputSquare(
    input wire[15:0] inp1,
    input wire[15:0] inp2,
    input wire sel,
    output wire[15:0] outp
);

    wire[15:0] inp_for_sq;
    assign inp_for_sq = (~sel) ? inp1 : inp2;
    square sq(inp_for_sq, outp);
endmodule

これで上の回路図のような要件は満たしています(PC 上でシミュレーション済み)。

自作モジュールを組み込む

LCD 用のサンプルに含まれる VGAMod.v を改造してこれを組み込むようにすれば良いのですね。
どこをどう改造したかいちいち言えないので、取り敢えず gist に投げておきます。ご査収ください。

ちなみに各種代入で気をつけないと、忌まわしき xxx(レジスタ変数名) does not match a standard flip-flop. が出るわけです。
具体的には、各種代入を全てを網羅した if 文の中でやるといいです。例えば、以下のように書くとエラーが出るかもしれません。

always @(posedge clk) begin
  sel <= ~sel;
  if( !nRST ) begin
    hoge;
    hage;
  end
end

分かりますか?if 文の外側に sel の代入が来ていますよね。このくらいの単純な回路記述だとエラーにはならないのですが、外部モジュールに接続されていたり、様々なイベント要因があったりするとエラーが出やすくなります。ひょっとしたら「そこラッチで来てるよ」なんて言われちゃうかも。
そこで、この sel の代入を移動して、以下のようにすればいいというわけです。

always @(posedge clk) begin
  if( !nRST ) begin
    hoge;
    hage;
  end
  else begin
    sel <= ~sel;
  end
end

さて、後は上のコードを書き込めば我が国日本の日の丸が完成します。あと何度もいいますが、Verilog の負の数は大嫌い
ところで日本の良い所ってなんでしょう。四季がある?思いやりのある国民性?食べ物が美味しい?いやいや、国旗が簡単に書けるということですよ。背景は白で RGB フル点灯で行けますし、中央の丸は方程式で簡単に記述でき、色が赤なので R だけを点灯させれば良いのですね。良い国だ(冗談ですよ)。

映りはこんな感じです。良かったなあ。

ということで、LCD で円が表示できたけど、リソース不足でこのチップ辛いよ!という内容でした。