●使うもの
▲マトリクスLED
秋月電子で販売されていたモジュール.“ドット抜け有りマトリクスLED”としてジャンク販売されていたものを入手.
▲FPGAボード
Gowin FPGAを搭載したTang Primer 25K(Sipeed).こちらも秋月電子などで購入できる.
▲ベースボード
FPGAとマトリクスLEDモジュールとは,Pmod互換の2×6ピンインターフェースで接続する.そのために専用の基板を作った.電源回路などもこの基板に搭載している.
●codexに渡したプロンプト
Verilog
FPGAのデザイン作ってください.
# マトリクスLEDに文字をスクロール表示
FPGAボードにPmodで接続されたマトリクスLEDをダイナミックドライブする.
スクロール表示する文字は,「Write once, run anywhere」
# ハードウェア
## FPGAボード
Tang Primer 25K(Sipeed)
## LEDマトリクス
16行×32桁
## システムクロック
50MHz
## インターフェース
Pmodポート1
### ピン配置
SIN1, シリアルデータ1(行データ選択,正論理)
SIN2,シリアルデータ2(桁データ,31~16,正論理)
SIN3,シリアルデータ3(桁データ,15~0,正論理)
LATCH,データラッチ(正論理,1でデータラッチ)
LED_CLK,データ通信用クロック
STROBE_, 負論理
### 物理制約ファイル
物理制約ファイルの内容は次の通り
IO_LOC "SIN2" A10;
IO_PORT "SIN2" PULL_MODE=UP IO_TYPE=LVCMOS33 DRIVE=8;
IO_LOC "SIN3" E10;
IO_PORT "SIN3" PULL_MODE=NONE IO_TYPE=LVCMOS33 DRIVE=8;
//IO_LOC "" L11;
//IO_PORT "" PULL_MODE=NONE IO_TYPE=LVCMOS33 DRIVE=8;
//IO_LOC "" K5;
//IO_PORT "" PULL_MODE=NONE IO_TYPE=LVCMOS33 DRIVE=8;
IO_LOC "SIN1" A11;
IO_PORT "SIN1" PULL_MODE=NONE IO_TYPE=LVCMOS33 DRIVE=8;
IO_LOC "LATCH" E11;
IO_PORT "LATCH" PULL_MODE=NONE IO_TYPE=LVCMOS33 DRIVE=8;
IO_LOC "LED_CLK" K11;
IO_PORT "LED_CLK" PULL_MODE=NONE IO_TYPE=LVCMOS33 DRIVE=8;
IO_LOC "STROBE_" L5;
IO_PORT "STROBE_" PULL_MODE=NONE IO_TYPE=LVCMOS33 DRIVE=8;
IO_LOC "USER_KEY" H11;
IO_PORT "USER_KEY" PULL_MODE=NONE DRIVE=OFF BANK_VCCIO=3.3;
IO_LOC "clk" E2;
IO_PORT "clk" PULL_MODE=NONE DRIVE=OFF BANK_VCCIO=3.3;
# 仕様
各ドットの明るさは4諧調で制御できるように実装する
100msごとに1ドットぶん左スクロールするExpand
●codex 回答
Verilog
rtl/matrix_led_top.v (line 1) と constraints/tang_primer_25k_pmod1_led_matrix.cst (line 1) を追加しました。Write once, run anywhere を 100msごとに1dot 左スクロールし、16x32 行列をダイナミックドライブするトップRTLです。4諧調は 2bit の BAM で実装してあり、表示データ生成は rtl/matrix_led_top.v (line 271)、走査とシリアル送出の状態機械は rtl/matrix_led_top.v (line 316) にあります。
実機依存になりやすい行/列の向きとシフト順は rtl/matrix_led_top.v (line 32) の ROW0_IS_TOP、COL0_IS_LEFT、SHIFT_MSB_FIRST で切り替えられるようにしてあります。使い方は README.md (line 1) にまとめました。
この環境には Verilog のコンパイルツールが無かったので、ローカルでの構文コンパイルまでは未実施です。次は Gowin で一度焼いて、文字の左右反転や行順が違えば上の3パラメータを詰めるのが最短です。●codexに生成させたRTLコード -matrix_led_top.v-
Verilog
module matrix_led_top (
input wire clk,
input wire USER_KEY,
output reg SIN1,
output reg SIN2,
output reg SIN3,
output reg LATCH,
output reg LED_CLK,
output reg STROBE_
);
localparam integer CLK_HZ = 50_000_000;
localparam integer DISPLAY_ROWS = 16;
localparam integer DISPLAY_COLS = 32;
localparam integer FONT_W = 8;
localparam integer FONT_H = 8;
localparam integer CHAR_STRIDE = 9;
localparam integer MSG_LEN = 24;
localparam integer TEXT_COLUMNS = MSG_LEN * CHAR_STRIDE;
localparam integer SCROLL_SPAN = TEXT_COLUMNS + DISPLAY_COLS;
localparam integer SCROLL_TICK_CYC = CLK_HZ / 10;
localparam integer BASE_ON_TICKS = 5_000;
localparam integer STATE_LOAD = 0;
localparam integer STATE_SHIFT_SETUP = 1;
localparam integer STATE_SHIFT_HIGH = 2;
localparam integer STATE_SHIFT_LOW = 3;
localparam integer STATE_LATCH_ON = 4;
localparam integer STATE_LATCH_OFF = 5;
localparam integer STATE_HOLD = 6;
localparam integer ROW0_IS_TOP = 1;
localparam integer COL0_IS_LEFT = 0;
localparam integer SHIFT_MSB_FIRST = 0;
reg [2:0] state;
reg plane_sel;
reg [3:0] scan_row;
reg [4:0] bit_index;
reg [15:0] row_shift_word;
reg [31:0] col_shift_word;
reg [13:0] hold_count;
reg [22:0] scroll_tick_count;
reg [7:0] scroll_offset;
function [15:0] row_word_for;
input [3:0] row_idx;
integer panel_row;
begin
row_word_for = 16'd0;
panel_row = ROW0_IS_TOP ? row_idx : (DISPLAY_ROWS - 1 - row_idx);
row_word_for[panel_row] = 1'b1;
end
endfunction
function serial_pick16;
input [15:0] word;
input [4:0] idx;
begin
if (SHIFT_MSB_FIRST != 0) begin
serial_pick16 = word[idx];
end else begin
serial_pick16 = word[15 - idx];
end
end
endfunction
function [7:0] message_char;
input integer index;
begin
case (index)
0: message_char = "W";
1: message_char = "r";
2: message_char = "i";
3: message_char = "t";
4: message_char = "e";
5: message_char = " ";
6: message_char = "o";
7: message_char = "n";
8: message_char = "c";
9: message_char = "e";
10: message_char = ",";
11: message_char = " ";
12: message_char = "r";
13: message_char = "u";
14: message_char = "n";
15: message_char = " ";
16: message_char = "a";
17: message_char = "n";
18: message_char = "y";
19: message_char = "w";
20: message_char = "h";
21: message_char = "e";
22: message_char = "r";
23: message_char = "e";
default: message_char = " ";
endcase
end
endfunction
function [7:0] font_row_bits;
input [7:0] ch;
input [2:0] row;
begin
font_row_bits = 8'b00000000;
case (ch)
" ": begin
font_row_bits = 8'b00000000;
end
",": begin
case (row)
3'd5: font_row_bits = 8'b00011000;
3'd6: font_row_bits = 8'b00011000;
3'd7: font_row_bits = 8'b00110000;
default: font_row_bits = 8'b00000000;
endcase
end
"W": begin
case (row)
3'd0: font_row_bits = 8'b10000001;
3'd1: font_row_bits = 8'b10000001;
3'd2: font_row_bits = 8'b10000001;
3'd3: font_row_bits = 8'b10011001;
3'd4: font_row_bits = 8'b10011001;
3'd5: font_row_bits = 8'b10100101;
3'd6: font_row_bits = 8'b11000011;
default: font_row_bits = 8'b00000000;
endcase
end
"a": begin
case (row)
3'd1: font_row_bits = 8'b00111100;
3'd2: font_row_bits = 8'b00000100;
3'd3: font_row_bits = 8'b00111100;
3'd4: font_row_bits = 8'b01000100;
3'd5: font_row_bits = 8'b01001100;
3'd6: font_row_bits = 8'b00110100;
default: font_row_bits = 8'b00000000;
endcase
end
"c": begin
case (row)
3'd1: font_row_bits = 8'b00111000;
3'd2: font_row_bits = 8'b01000100;
3'd3: font_row_bits = 8'b01000000;
3'd4: font_row_bits = 8'b01000000;
3'd5: font_row_bits = 8'b01000100;
3'd6: font_row_bits = 8'b00111000;
default: font_row_bits = 8'b00000000;
endcase
end
"e": begin
case (row)
3'd1: font_row_bits = 8'b00111000;
3'd2: font_row_bits = 8'b01000100;
3'd3: font_row_bits = 8'b01111100;
3'd4: font_row_bits = 8'b01000000;
3'd5: font_row_bits = 8'b01000100;
3'd6: font_row_bits = 8'b00111000;
default: font_row_bits = 8'b00000000;
endcase
end
"h": begin
case (row)
3'd0: font_row_bits = 8'b01000000;
3'd1: font_row_bits = 8'b01000000;
3'd2: font_row_bits = 8'b01011000;
3'd3: font_row_bits = 8'b01100100;
3'd4: font_row_bits = 8'b01000100;
3'd5: font_row_bits = 8'b01000100;
3'd6: font_row_bits = 8'b01000100;
default: font_row_bits = 8'b00000000;
endcase
end
"i": begin
case (row)
3'd0: font_row_bits = 8'b00010000;
3'd2: font_row_bits = 8'b00110000;
3'd3: font_row_bits = 8'b00010000;
3'd4: font_row_bits = 8'b00010000;
3'd5: font_row_bits = 8'b00010000;
3'd6: font_row_bits = 8'b00111000;
default: font_row_bits = 8'b00000000;
endcase
end
"n": begin
case (row)
3'd1: font_row_bits = 8'b01111000;
3'd2: font_row_bits = 8'b01000100;
3'd3: font_row_bits = 8'b01000100;
3'd4: font_row_bits = 8'b01000100;
3'd5: font_row_bits = 8'b01000100;
3'd6: font_row_bits = 8'b01000100;
default: font_row_bits = 8'b00000000;
endcase
end
"o": begin
case (row)
3'd1: font_row_bits = 8'b00111000;
3'd2: font_row_bits = 8'b01000100;
3'd3: font_row_bits = 8'b01000100;
3'd4: font_row_bits = 8'b01000100;
3'd5: font_row_bits = 8'b01000100;
3'd6: font_row_bits = 8'b00111000;
default: font_row_bits = 8'b00000000;
endcase
end
"r": begin
case (row)
3'd1: font_row_bits = 8'b01011000;
3'd2: font_row_bits = 8'b01100100;
3'd3: font_row_bits = 8'b01000000;
3'd4: font_row_bits = 8'b01000000;
3'd5: font_row_bits = 8'b01000000;
3'd6: font_row_bits = 8'b01000000;
default: font_row_bits = 8'b00000000;
endcase
end
"t": begin
case (row)
3'd0: font_row_bits = 8'b00010000;
3'd1: font_row_bits = 8'b00010000;
3'd2: font_row_bits = 8'b00111100;
3'd3: font_row_bits = 8'b00010000;
3'd4: font_row_bits = 8'b00010000;
3'd5: font_row_bits = 8'b00010100;
3'd6: font_row_bits = 8'b00001000;
default: font_row_bits = 8'b00000000;
endcase
end
"u": begin
case (row)
3'd1: font_row_bits = 8'b01000100;
3'd2: font_row_bits = 8'b01000100;
3'd3: font_row_bits = 8'b01000100;
3'd4: font_row_bits = 8'b01000100;
3'd5: font_row_bits = 8'b01001100;
3'd6: font_row_bits = 8'b00110100;
default: font_row_bits = 8'b00000000;
endcase
end
"w": begin
case (row)
3'd1: font_row_bits = 8'b01000100;
3'd2: font_row_bits = 8'b01000100;
3'd3: font_row_bits = 8'b01010100;
3'd4: font_row_bits = 8'b01010100;
3'd5: font_row_bits = 8'b01010100;
3'd6: font_row_bits = 8'b00101000;
default: font_row_bits = 8'b00000000;
endcase
end
"y": begin
case (row)
3'd1: font_row_bits = 8'b01000100;
3'd2: font_row_bits = 8'b01000100;
3'd3: font_row_bits = 8'b01000100;
3'd4: font_row_bits = 8'b00111100;
3'd5: font_row_bits = 8'b00000100;
3'd6: font_row_bits = 8'b00111000;
default: font_row_bits = 8'b00000000;
endcase
end
default: begin
font_row_bits = 8'b00000000;
end
endcase
end
endfunction
function [1:0] pixel_gray;
input integer screen_col;
input [3:0] row_idx;
input [7:0] scroll_pos;
integer text_x;
integer cell_index;
integer x_in_cell;
reg [7:0] ch;
reg [7:0] glyph_row;
begin
pixel_gray = 2'd0;
text_x = screen_col + scroll_pos - DISPLAY_COLS;
if ((text_x >= 0) && (text_x < TEXT_COLUMNS) && (row_idx < DISPLAY_ROWS)) begin
cell_index = text_x / CHAR_STRIDE;
x_in_cell = text_x % CHAR_STRIDE;
if ((cell_index < MSG_LEN) && (x_in_cell < FONT_W)) begin
ch = message_char(cell_index);
glyph_row = font_row_bits(ch, row_idx[3:1]);
if (glyph_row[7 - x_in_cell]) begin
pixel_gray = 2'd3;
end
end
end
end
endfunction
function [31:0] column_word_for;
input [3:0] row_idx;
input plane;
input [7:0] scroll_pos;
integer col;
integer panel_col;
reg [1:0] gray;
begin
column_word_for = 32'd0;
for (col = 0; col < DISPLAY_COLS; col = col + 1) begin
panel_col = COL0_IS_LEFT ? col : (DISPLAY_COLS - 1 - col);
gray = pixel_gray(col, row_idx, scroll_pos);
if (gray[plane]) begin
column_word_for[panel_col] = 1'b1;
end
end
end
endfunction
always @(posedge clk or negedge USER_KEY) begin
if (!USER_KEY) begin
state <= STATE_LOAD;
plane_sel <= 1'b0;
scan_row <= 4'd0;
bit_index <= 5'd15;
row_shift_word <= 16'd0;
col_shift_word <= 32'd0;
hold_count <= 14'd0;
scroll_tick_count <= 23'd0;
scroll_offset <= 8'd0;
SIN1 <= 1'b0;
SIN2 <= 1'b0;
SIN3 <= 1'b0;
LATCH <= 1'b0;
LED_CLK <= 1'b0;
STROBE_ <= 1'b1;
end else begin
if (scroll_tick_count == SCROLL_TICK_CYC - 1) begin
scroll_tick_count <= 23'd0;
if (scroll_offset == SCROLL_SPAN - 1) begin
scroll_offset <= 8'd0;
end else begin
scroll_offset <= scroll_offset + 8'd1;
end
end else begin
scroll_tick_count <= scroll_tick_count + 23'd1;
end
case (state)
STATE_LOAD: begin
STROBE_ <= 1'b1;
LATCH <= 1'b0;
LED_CLK <= 1'b0;
SIN1 <= 1'b0;
SIN2 <= 1'b0;
SIN3 <= 1'b0;
row_shift_word <= row_word_for(scan_row);
col_shift_word <= column_word_for(scan_row, plane_sel, scroll_offset);
bit_index <= 5'd15;
state <= STATE_SHIFT_SETUP;
end
STATE_SHIFT_SETUP: begin
LED_CLK <= 1'b0;
SIN1 <= serial_pick16(row_shift_word, bit_index);
SIN2 <= serial_pick16(col_shift_word[31:16], bit_index);
SIN3 <= serial_pick16(col_shift_word[15:0], bit_index);
state <= STATE_SHIFT_HIGH;
end
STATE_SHIFT_HIGH: begin
LED_CLK <= 1'b1;
state <= STATE_SHIFT_LOW;
end
STATE_SHIFT_LOW: begin
LED_CLK <= 1'b0;
if (bit_index == 0) begin
state <= STATE_LATCH_ON;
end else begin
bit_index <= bit_index - 5'd1;
state <= STATE_SHIFT_SETUP;
end
end
STATE_LATCH_ON: begin
LATCH <= 1'b1;
state <= STATE_LATCH_OFF;
end
STATE_LATCH_OFF: begin
LATCH <= 1'b0;
STROBE_ <= 1'b0;
hold_count <= plane_sel ? ((BASE_ON_TICKS << 1) - 1) : (BASE_ON_TICKS - 1);
state <= STATE_HOLD;
end
STATE_HOLD: begin
if (hold_count == 0) begin
STROBE_ <= 1'b1;
if (scan_row == DISPLAY_ROWS - 1) begin
scan_row <= 4'd0;
plane_sel <= ~plane_sel;
end else begin
scan_row <= scan_row + 4'd1;
end
state <= STATE_LOAD;
end else begin
hold_count <= hold_count - 14'd1;
end
end
default: begin
state <= STATE_LOAD;
end
endcase
end
end
endmodule
Expand
