●使うもの

▲マトリクス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(桁データ,3116,正論理)
SIN3,シリアルデータ3(桁データ,150,正論理)
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

コメントを残す