Cyclone V GT FPGA Development KitでHello, world!

Cyclone V GT FPGA Development Kitにはキャラクタ液晶(LCD)が付いているので何かと便利に使えそうだ.

さぁ適当なIPをインポートしてサクッとHello, world!と思ったら,そのような便利なIPは(少なくとも無償では)無いらしい.えーI2C通信自分で書くの・・・.今までマイコン等でI2Cの何かを使うときはライブラリの上から適当に使ってきたので,信号の細かな動きは全く知らない.仕方ないので調べながら書いた.この界隈そんなに人少ないのか・・・.

資料

公式のユーザーガイドとリファレンスマニュアル.

https://www.intel.co.jp/content/dam/altera-www/global/ja_JP/pdfs/literature/ug/ug_cvgt_fpga_dev_kit.pdf

https://www.intel.co.jp/content/dam/altera-www/global/ja_JP/pdfs/literature/manual/rm_cvgt_fpga_dev_board.pdf

LCDのデータシート

http://www.newhavendisplay.com/specs/NHD-0216K3Z-NSW-BBW-V3.pdf

環境

プロジェクトファイル一式:https://github.com/SenriYoshikawa/CycloneVGTFPGADevelopmentKit/tree/master/LCD

仕様

  • PB0を押すとリセット
  • PB1を押すとLCDをクリア
  • PB2を押すとLCDに「Hello, world!」を表示

実装

コードすべての説明を書くとかなりの量になりそうなので,特に詰まったところだけメモがてら書き残す.

inoutピン

SCLとSDAは場合により入力にも出力にもなり得る.そんなものどうやってVerilogで書くのだと思い調べたところ,inout宣言した入出力ポート(wire)に対して,条件によりregか'z'(ハイインピーダンス)をassignすればよいらしい.

こんな感じ.

assign i2c_sda   = sda_valid ? sda_reg : 1'bz;

自分(FPGA)はマスターなので,出力したいときはsda_validをHighにしてsda_regに書き込み,スレーブの応答を見たいときはsda_validをLowにしてi2c_sdaを読めば良い.

SCLのためのクロック生成

I2Cの詳細については丁寧に解説しているサイトがたくさんあるのでそちらに譲る.FPGAで実装しようとしてまず困ったのは,I2Cのクロック信号(SCL)がせいぜい数百kHzまでしか対応しないらしく,PLLではそこまで遅いクロックを作ることができないことだ.

仕方がないので以下のような何とも言えないモジュールを書いた.50MHzクロックでカウンタを動かし,50KHzのクロックを生成している.一応動いているので良しとしているが,より良い方法ご存じの方教えてください・・・.

ちなみに,なぜリセットのエッヂ検出をしているかというと,リセット中にカウントが止まるとクロックも止まり,50Kで同期しているレジスタをリセットできないからだ.ここも少し嵌った.

module Clk50K(
    input clk_50M,
    input rstn,
    output reg clk_50K
);

reg [1:0] rstn_buf;
reg [9:0] count_50K;

// rstn_buf
always @(posedge clk_50M) begin
    rstn_buf <= {rstn_buf[0], rstn};
end

// count_50K
always @(posedge clk_50M) begin
    if(rstn_buf == 2'b10) begin
        count_50K <= 0;
    end else if(count_50K >= 499) begin
        count_50K <= 0;
    end else begin
        count_50K <= count_50K + 10'd1;
    end
end

// clk_50K
always @(posedge clk_50M) begin
    if(rstn_buf == 2'b10) begin
        clk_50K <= 1'b0;
    end else if(count_50K >= 499) begin
        clk_50K <= ~clk_50K;
    end else begin
        clk_50K <= clk_50K;
    end
end

endmodule

SCLとSDAの同期

SCLはSDAに対するクロックで,通常のデータ転送においてはSCLがLowの間にSDAを書き換える同期回路でよく見る動作なのだが,スタートシーケンスではSCLがHighの間にSDAをLowに,ストップシーケンスではSCLがHighの間にSDAをHighにしなければならない.

この動作を実現するために,2ビットのひたすらインクリメントするレジスタを設け,この上位ビットをSCLに繋いだ.

reg[1:0]scl_reg scl
2'b01 0
2'b10 1
2'b11 1
2'b00 0

これによりSCLがHighとLowのそれぞれで2クロックあるので,SCLがHighの間にSDAを操作したければ,scl_regが2'b10の時に代入すればよい.通常のデータ代入はscl_regが2'b00か2'b01の時に行えばよい. しかし後で見たら自分でも首を傾げそうな実装だ.みんなどうしてるんだろう.

その他

あとは初めに思ったほどややこしいことはなく,愚直に書いたら動いた.

ピン配置

LCDのピンがどこにつながっているかはリファレンスマニュアルのP2-25に書いてある.

LEDはデバッグ用に3つ繋いでいるがなくても動く.

f:id:otto5:20200317172933p:plain

書き込み・動作確認

例によってデバイスチェーンに気を付けて書き込んで動作確認.

f:id:otto5:20200317161225j:plain

動いた.うれしい.