言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Spartan-3A で Hello World!

P1040741

LED ばかりでは味気無いので、液晶画面を使って Hello World! を出してみた。

ユーザーガイドを読む

ユーザーガイドの「キャラクLCD」の所に液晶の使い方が書いてあるのでじっくり読む。難しい。。。PicoBlaze って何だ???知らない単語は置いといて、出来そうな事だけやろう。おお、何とカタカナも出るようだ!ワクワク。

説明を読むと、結構時間をきっちり測って信号を出さないといけないようだ。スターターキットのクロックが 50MHz なので、一クロックあたり 1000000us / 50000000Hz = 0.02us か!これは数えないといけないのかな。

数を数える。

数を数える事すらやってないので、まずただクロックに合わせて数を数えるだけのプログラムを書いてみる。0.02us を 2 ** 24 倍すると 335,544.32 us になるので、32 ビット目から 25 ビット目を LED に表示すると 0.3 秒ずつ動くところが見えるのではないか。

// counter.v
`timescale 1ns / 1ps
module counter(input CLK, output [7:0] LED);

    reg [31:0] counter = 0; // 32 ビットレジスタを作ってゼロにする。
    assign LED[7:0] = counter[31:24]; // 25 ビット目から 32 ビット目を LED に接続   
    always @(posedge CLK) counter = counter + 1; // CLK が 1 の時カウンタ増分

endmodule
# counter.ucf
NET "CLK" LOC = "E12"| IOSTANDARD = LVCMOS33 ;

NET "LED<7>" LOC = "W21" | IOSTANDARD = LVTTL | SLEW = QUIETIO | DRIVE = 4 ;
NET "LED<6>" LOC = "Y22" | IOSTANDARD = LVTTL | SLEW = QUIETIO | DRIVE = 4 ;
NET "LED<5>" LOC = "V20" | IOSTANDARD = LVTTL | SLEW = QUIETIO | DRIVE = 4 ;
NET "LED<4>" LOC = "V19" | IOSTANDARD = LVTTL | SLEW = QUIETIO | DRIVE = 4 ;
NET "LED<3>" LOC = "U19" | IOSTANDARD = LVTTL | SLEW = QUIETIO | DRIVE = 4 ;
NET "LED<2>" LOC = "U20" | IOSTANDARD = LVTTL | SLEW = QUIETIO | DRIVE = 4 ;
NET "LED<1>" LOC = "T19" | IOSTANDARD = LVTTL | SLEW = QUIETIO | DRIVE = 4 ;
NET "LED<0>" LOC = "R20" | IOSTANDARD = LVTTL | SLEW = QUIETIO | DRIVE = 4 ;

おお、なんとか上手く行ってるみたい。assign の辺りが関数言語っぽくて好きだなあ。

時間待ち

次に説明書を読むと、どうやら何秒待って何かするという事を沢山やらないといけないみたい。そんなのどうやってやるんだ!という事で液晶はまだ諦めて、ある時間が過ぎたら何かするという部品を作ってみた。

// counter2.v (ucf ファイルは counter.v と同じ)
`timescale 1ns / 1ps
`define SECOND 50000000
module counter2(
    input wire CLK,
   output reg [7:0] LED
    );

    reg [7:0] state = 0; // 状態(一クロックだけ変わって後は 0)
    reg [7:0] jump = 1; // 次の状態
    reg [31:0] counter = 0; // 待ち時間

    always @(posedge CLK) begin
        state   <= counter == 0 ? jump : 0;
        counter <= counter == 0 ? 0    : counter - 1;
        case(state)
            1: begin LED <= 1; counter <= 1 * `SECOND; jump <= 2; end
            2: begin LED <= 2; counter <= 2 * `SECOND; jump <= 3; end
            3: begin LED <= 4; counter <= 3 * `SECOND; jump <= 1; end
        endcase
    end
endmodule

一秒間 LED0 が点灯、二秒間 LED1 が点灯、三秒間 LED2 が点灯というのを繰り返す。state は状態を保持する変数で、普段はゼロですがカウンタが完了して次の状態に移行する際一瞬だけ次の状態を指す。その時に LED と新しいカウンタを設定する。もっとスマートなやり方があったら教えてください!

また、なんとなく = の代わりに <= を使ってみた。ノンブロッキング代入と言って、例えば case 中の state は直前に代入した state では無く、一回前の state を参照するらしい。

液晶を起動する。

いよいよ説明書どおりじっくり設定。こんな風になった。

// hello.v
`timescale 1ns / 1ps
module hello(
   input wire CLK,
   output reg LCD_E,
   output reg LCD_RS,
   output wire LCD_RW,
   output reg [7:0] LCD_DB
   );

   reg [7:0] state = 0; // 状態(一クロックだけ変わって後は 0)
   reg [7:0] jump = 1; // 次の状態
   reg [31:0] counter = 0; // 待ち時間
   reg [4:0] pulseCounter = 0; // LCD_E 用カウンタ(0 以外で LCD_E = 1)
   reg [31:0] fourBitCounter = 0;
   reg [3:0] fourBitRest; // 4ビット残りデータ
   reg fourBitRestIsData; // 4ビット残りデータなら1 コマンドなら0
   
   assign LCD_RW = 0; // 書き込み専用

   always @(posedge CLK) begin
      LCD_DB[3:0] <= 4'b1111; // 4 ビットモード全て Hi に
      state   <= counter == 0 ? jump : 0;
      counter <= counter == 0 ? 0    : counter - 1;
      
      pulseCounter <= pulseCounter == 0 ? 0 : pulseCounter - 1;
      LCD_E <= pulseCounter == 0 ? 0 : 1;
      
      fourBitProcess;
      
      case(state)
         // 1. ディスプレイは通常、FPGA がコンフィギュレーションを完了すると使用可能ですが、
         // ここでは 15ms 以上待機しワす。 15ms は、50MHz での 750,000 クロック サイクルと同等です。      
         1: begin
            counter <= 750000; jump <= 2;
         end
         
         // 2. LCD_DB<7:4> = 0x3 を書き込み、
         // パルス LCD_E を 12 クロック サイクルの間 High に保持します。
         // 3. 4.1ms 以上待機します。これは、50MHz. での 205,000 クロック サイクルと同等です。
         2: begin
            sendData(0, 3);
            counter <= 205000; jump <= 4;
         end
         
         // 4. LCD_DB<7:4> = 0x3 を書き込み、
         // パルス LCD_E を 12 クロック サイクルの間 High に保持します。
         // 5. 100μs 以上待機します。これは、50MHz での 5,000 クロック サイクルニ同等です。
         4: begin
            sendData(0, 3);
            counter <= 5000; jump <= 6;
         end

         // 6. LCD_DB<7:4> = 0x3 を書き込み、
         // パルス LCD_E を 12 クロック サイクルの間 High に保持します。         
         // 7. 40μs 以上待機します。これは、50MHz での 2,000 クロック サイクルと同等です。
         6: begin
            sendData(0, 3);
            counter <= 2000; jump <= 8;
         end
         
         // 8. LCD_DB<7:4> = 0x2 を書き込み、
         // パルス LCD_E を 12 クロック サイクルの間 High に保持しワす。
         // 9. 40μs 以上待機します。これは、50MHz での 2,000 クロック サイクルと同等です。
         8: begin
            sendData(0, 2);
            counter <= 2000; jump <= 9;
         end
         
         // 1. Function Set コマンド 0x28 を発行し、
         // ディスプレイが Spartan-3A/3AN スタータ キット ボード上で動作するよう設定します。
         9: sendFourBit(0, 8'h28, 2000, 10);
        
         // 2. Entry Mode Set コマンド 0x06 を発行し
         // アドレス ポインタが自動的に増分するよう設定します。
         10: sendFourBit(0, 8'h06, 2000, 11);

         // 3. Display On/Off コマンド 0x0C を発行し、
         // ディスプレイをオンにして、カーソルと点滅をディスエーブルにします。
         11: // sendFourBit(0, 8'h0C, 2000, 12);
             sendFourBit(0, 8'h0F, 2000, 12); // こうするとカーソルが点滅する。
         
         // 4. 最後に、Clear Display コマンドを発行します。 
         // コマンド発行後、少なくとも 1.64ms (82,000 クロック サイクル) 間待機します。
         // 実行時間 : 1.64ms
         12: sendFourBit(0, 8'h01, 82000, 13);
        
         // データを書き込む前に、Set DD RAM Address コマンドを発行し、
         // DD RAM の 7 ビットの初期アドレスを指定します。 DD RAM のロケーションについては、図 5-3 を参照してくセさい。
         13: sendFourBit(0, 8'h80, 2000, 14);
         
         // データの書き込みにヘ、Write Data to CG RAM or DD RAM コマンドを使用します。 
         // 8 ビットのデータ値は、図 5-4 に示す CG ROM または CG RAM へのルックアップ アドレスを表しています。
         14: sendFourBit(1, "H", 2000, 15);
         15: sendFourBit(1, "e", 2000, 16);
         16: sendFourBit(1, "l", 2000, 17);
         17: sendFourBit(1, "l", 2000, 18);
         18: sendFourBit(1, "o", 2000, 19);
         19: sendFourBit(1, " ", 2000, 20);
         20: sendFourBit(1, "W", 2000, 21);
         21: sendFourBit(1, "o", 2000, 22);
         22: sendFourBit(1, "r", 2000, 23);
         23: sendFourBit(1, "l", 2000, 24);
         24: sendFourBit(1, "d", 2000, 25);
         25: sendFourBit(1, "!", 2000, 26);

      endcase
   end

   // 8ビットの信号を4ビットずつ送ります。
   task sendFourBit;
      input isData; // データなら 1
      input [7:0] data; // 送信データ
      input [31:0] nextCounter; // 待ち時間
      input [7:0]  nextJump; // 次の状態

      begin
         sendData(isData, data[7:4]);
         fourBitRestIsData <= isData;
          fourBitRest <= data[3:0];
         fourBitCounter <= 30;
         counter <= nextCounter;
         jump <= nextJump;
      end
   endtask

   // 時間が来たら残りの下位4ビットを送ります。
   task fourBitProcess;
      begin
         fourBitCounter <= fourBitCounter == 0 ? 0 : fourBitCounter - 1;         
         if (fourBitCounter == 15)
            sendData(fourBitRestIsData, fourBitRest);
      end
   endtask

   // データを送って LCD_E パルスをセットします。
   task sendData;
      input isData;
      input [3:0] data;

      begin
         LCD_RS <= isData;
         LCD_DB[7:4] <= data;
         pulseCounter <= 12;
      end
   endtask
 
endmodule
# hello.ucf
NET "CLK" LOC = "E12"| IOSTANDARD = LVCMOS33 ;

NET "LCD_E" LOC = "AB4" | IOSTANDARD = LVCMOS33 | DRIVE = 4 | SLEW = QUIETIO ;
NET "LCD_RS" LOC = "Y14" | IOSTANDARD = LVCMOS33 | DRIVE = 4 | SLEW = QUIETIO ;
NET "LCD_RW" LOC = "W13" | IOSTANDARD = LVCMOS33 | DRIVE = 4 | SLEW = QUIETIO ;
NET "LCD_DB<7>" LOC = "Y15" | IOSTANDARD = LVCMOS33 | DRIVE = 4 | SLEW = QUIETIO ;
NET "LCD_DB<6>" LOC = "AB16" | IOSTANDARD = LVCMOS33 | DRIVE = 4 | SLEW = QUIETIO ;
NET "LCD_DB<5>" LOC = "Y16" | IOSTANDARD = LVCMOS33 | DRIVE = 4 | SLEW = QUIETIO ;
NET "LCD_DB<4>" LOC = "AA12" | IOSTANDARD = LVCMOS33 | DRIVE = 4 | SLEW = QUIETIO ;
NET "LCD_DB<3>" LOC = "AB12" | IOSTANDARD = LVCMOS33 | DRIVE = 4 | SLEW = QUIETIO ;
NET "LCD_DB<2>" LOC = "AB17" | IOSTANDARD = LVCMOS33 | DRIVE = 4 | SLEW = QUIETIO ;
NET "LCD_DB<1>" LOC = "AB18" | IOSTANDARD = LVCMOS33 | DRIVE = 4 | SLEW = QUIETIO ;
NET "LCD_DB<0>" LOC = "Y13" | IOSTANDARD = LVCMOS33 | DRIVE = 4 | SLEW = QUIETIO ;

Hello World にこんなに苦労したのは初めてだ。疲れた。一昔の BASIC のような汚いプログラムになってしまったが、ちょっと休んでからどうやったら綺麗になるか考えたいと思う。屈辱的なほど大変だったがとりあえず出来てよかった。

資料