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 のような汚いプログラムになってしまったが、ちょっと休んでからどうやったら綺麗になるか考えたいと思う。屈辱的なほど大変だったがとりあえず出来てよかった。