deepseekを使いつつ,人が介在することで,GAOで実機波形を取得しながら開発を進めた.当初,SDカードの初期化処理について,カード側から一切応答がなく,繰り返しGAOで波形を取得し,ロジックを修正するものの改善が見られなかった. 

 この状況では,deepseekはカードまたは,ハードウェアの問題だと繰り返し指摘していた.

 まったく改善しないので,Sipeedのサンプル回路の中にある,SDカードが使われているデザインのリポジトリを通知し,その初期化ロジックを使うように指示した.

 これでSDカードから応答が返るようになり,以後はGAO波形を手動でdeepseekに渡しながら,ロジックの修正を進めていった.

 AIエージェントを使った開発では,ロジアナなどの値をフィードバックすることでエージェントループを組む.しかし,デバイスの初期化を完了出来ず,デバイス側の応答がない状態では,一向にデバッグが進まない.AIはその状況を,ハードウェアの不具合と判断してしまうので,人が介入してデバイスが応答するところまで導く必要がある.

 SDカードの初期化ロジックまでは動くが,なかなか画像データを読み出せなかった.deep seek v4.1 flash ではうまくいかないので,deep seek v4 pro を使ったところ,画像がDVI出力された.

 色調(R,G,Bの不整合)と左右反転の修正を指示して完成した.

●生成されたRTLコード

sd_reader.sv

// ======================================================
//  sd_reader.sv
//  SDカード SPIモード ブロックリーダ
//  Sipeed TangMega-138KPro-example (audio_music) の sd_init / sd_read の
//  ロジックを忠実に移植した版。
//
//  【クロック】 Sipeed版と同様、2相クロック方式
//    clk_ref       = clk (50MHz)          : MOSI を posedge で出力
//    clk_ref_180deg= !clk                  : sd_clk に接続
//    MISO は clk_ref_180deg の posedge (= sd_clk の立ち上がり) でラッチ
//
//  ※ SPIモード0 : sd_clk 立ち下がり(=clk_ref posedge)で MOSI 確定,
//                  sd_clk 立ち上がりで MISO サンプル
// ======================================================
module sd_reader (
    input  logic        clk,          // 50MHz
    input  logic        rst_n,

    output logic        sd_clk,
    output logic        sd_cmd,       // MOSI
    input  logic        sd_dat,       // MISO
    output logic        sd_cs_n,

    input  logic        read_req,
    input  logic [31:0] block_addr,

    output logic [7:0]  data,
    output logic        data_valid,
    output logic        busy,
    output logic        init_done,
    output logic [2:0]  debug_stage
);

    // ---- 2相クロック ----
    wire clk_ref       = clk;
    wire clk_ref_180   = ~clk;
    assign sd_clk      = clk_ref_180;

    // ========================================================================
    // コマンド定義 (48bit)
    // ========================================================================
    localparam logic [47:0] CMD0   = {8'h40, 8'h00, 8'h00, 8'h00, 8'h00, 8'h95};
    localparam logic [47:0] CMD8   = {8'h48, 8'h00, 8'h00, 8'h01, 8'hAA, 8'h87};
    localparam logic [47:0] CMD55  = {8'h77, 8'h00, 8'h00, 8'h00, 8'h00, 8'hFF};
    localparam logic [47:0] ACMD41 = {8'h69, 8'h40, 8'h00, 8'h00, 8'h00, 8'hFF};

    // ========================================================================
    // 状態定義 (初期化 + 読み出しを統合)
    // ========================================================================
    typedef enum logic [3:0] {
        ST_IDLE,           // 上電安定待ち
        ST_SEND_CMD0,      // CMD0 送信
        ST_WAIT_CMD0,      // CMD0 応答待ち
        ST_SEND_CMD8,      // CMD8 送信
        ST_WAIT_CMD8,      // CMD8 応答待ち
        ST_SEND_CMD55,     // CMD55 送信
        ST_WAIT_CMD55,     // CMD55 応答待ち
        ST_SEND_ACMD41,    // ACMD41 送信
        ST_WAIT_ACMD41,    // ACMD41 応答待ち
        ST_INIT_DONE,      // 初期化完了
        ST_RD_IDLE,        // 読み出し待機
        ST_RD_SEND,        // CMD17 送信
        ST_RD_WAIT_R1,     // CMD17 R1 応答待ち
        ST_RD_WAIT_TOKEN,  // データトークン待ち
        ST_RD_DATA,        // データ読み出し
        ST_RD_CRC          // CRC 読捨て
    } state_t;

    state_t state, next_state;

    // ========================================================================
    // 上電安定カウンタ
    // ========================================================================
    localparam POWER_ON_NUM = 74;
    logic [7:0] poweron_cnt;

    always_ff @(posedge clk_ref or negedge rst_n) begin
        if (!rst_n)
            poweron_cnt <= 8'd0;
        else if (poweron_cnt < POWER_ON_NUM + 3'd6)
            poweron_cnt <= poweron_cnt + 1'b1;
    end

    // ========================================================================
    // コマンド送信 (clk_ref の posedge = sd_clk の negedge で出力)
    // ========================================================================
    logic [5:0]  cmd_bit_cnt;
    logic        send_active;
    logic        cmd_sent;      // 48bit 送信完了パルス
    logic [47:0] cur_cmd;
    logic        sd_mosi;

    assign sd_cmd = sd_mosi;

    always_comb begin
        send_active = (state == ST_SEND_CMD0)  || (state == ST_SEND_CMD8) ||
                      (state == ST_SEND_CMD55) || (state == ST_SEND_ACMD41) ||
                      (state == ST_RD_SEND);
    end

    always_comb begin
        case (state)
            ST_SEND_CMD0:   cur_cmd = CMD0;
            ST_SEND_CMD8:   cur_cmd = CMD8;
            ST_SEND_CMD55:  cur_cmd = CMD55;
            ST_SEND_ACMD41: cur_cmd = ACMD41;
            ST_RD_SEND:     cur_cmd = {8'h51, block_addr, 8'hFF};
            default:        cur_cmd = 48'hFFFFFFFFFFFF;
        endcase
    end

    always_ff @(posedge clk_ref or negedge rst_n) begin
        if (!rst_n) begin
            cmd_bit_cnt <= 6'd0;
            sd_mosi     <= 1'b1;
            cmd_sent    <= 1'b0;
        end else begin
            cmd_sent <= 1'b0;
            if (send_active) begin
                sd_mosi <= cur_cmd[47 - cmd_bit_cnt];
                if (cmd_bit_cnt == 6'd47) begin
                    cmd_bit_cnt <= 6'd0;
                    cmd_sent    <= 1'b1;
                end else
                    cmd_bit_cnt <= cmd_bit_cnt + 1'b1;
            end else begin
                sd_mosi     <= 1'b1;
                cmd_bit_cnt <= 6'd0;
            end
        end
    end

    // ========================================================================
    // 応答受信 (clk_ref_180deg の posedge = sd_clk の立ち上がりでラッチ)
    //   初期化コマンド: R1 = 8bit。スタートビット(0)検出後、8bit 受信。
    // ========================================================================
    logic        rx_flag;
    logic [2:0]  rx_bit_cnt;
    logic [7:0]  rx_sr;
    logic [7:0]  rx_byte;
    logic        rx_byte_ready;
    logic        r1_mode;      // R1 受信 (スタートビット検出)
    logic        data_mode;    // データ連続受信

    always_comb begin
        r1_mode = (state == ST_WAIT_CMD0)  || (state == ST_WAIT_CMD8) ||
                  (state == ST_WAIT_CMD55) || (state == ST_WAIT_ACMD41) ||
                  (state == ST_RD_WAIT_R1);
        data_mode = (state == ST_RD_WAIT_TOKEN) || (state == ST_RD_DATA) ||
                    (state == ST_RD_CRC);
    end

    always_ff @(posedge clk_ref_180 or negedge rst_n) begin
        if (!rst_n) begin
            rx_flag       <= 1'b0;
            rx_bit_cnt    <= 3'd0;
            rx_sr         <= 8'd0;
            rx_byte       <= 8'd0;
            rx_byte_ready <= 1'b0;
        end else begin
            rx_byte_ready <= 1'b0;

            if (r1_mode || data_mode) begin
                if (!rx_flag) begin
                    if (r1_mode) begin
                        // スタートビット (MISO=0) は R1 応答の bit7 (MSB) そのもの。
                        // これを破棄せず rx_sr[0] に取り込み、残り 7bit をシフトして
                        // 8bit の R1 を正しく組み立てる (破棄すると R1 が 1bit 左シフトし、
                        // 例えば CMD0 の応答 0x01 が 0x03 として受信されて初期化が完了しない)。
                        if (sd_dat == 1'b0) begin
                            rx_flag    <= 1'b1;
                            rx_sr      <= {7'b0, sd_dat};
                            rx_bit_cnt <= 3'd1;
                        end
                    end else begin
                        // データモード: 連続 8bit
                        rx_flag    <= 1'b1;
                        rx_sr      <= {7'b0, sd_dat};
                        rx_bit_cnt <= 3'd1;
                    end
                end else begin
                    rx_sr <= {rx_sr[6:0], sd_dat};
                    if (rx_bit_cnt == 3'd7) begin
                        rx_byte       <= {rx_sr[6:0], sd_dat};
                        rx_byte_ready <= 1'b1;
                        rx_flag       <= 1'b0;
                        rx_bit_cnt    <= 3'd0;
                    end else
                        rx_bit_cnt <= rx_bit_cnt + 1'b1;
                end
            end else begin
                rx_flag    <= 1'b0;
                rx_bit_cnt <= 3'd0;
            end
        end
    end

    // ========================================================================
    // メイン状態機械
    // ========================================================================
    logic [24:0] over_time_cnt;
    logic [7:0]  acmd41_tries;
    logic [9:0]  data_cnt;
    logic [9:0]  crc_cnt;

    always_ff @(posedge clk_ref or negedge rst_n) begin
        if (!rst_n)
            state <= ST_IDLE;
        else
            state <= next_state;
    end

    always_comb begin
        next_state = state;
        case (state)
            ST_IDLE: begin
                if (poweron_cnt == POWER_ON_NUM + 3'd6)
                    next_state = ST_SEND_CMD0;
            end

            ST_SEND_CMD0: begin
                if (cmd_sent)
                    next_state = ST_WAIT_CMD0;
            end

            ST_WAIT_CMD0: begin
                if (rx_byte_ready) begin
                    if (rx_byte == 8'h01)
                        next_state = ST_SEND_CMD8;
                    else
                        next_state = ST_SEND_CMD0;
                end else if (over_time_cnt == 25'd25_000_000)
                    next_state = ST_SEND_CMD0;
            end

            ST_SEND_CMD8: begin
                if (cmd_sent)
                    next_state = ST_WAIT_CMD8;
            end

            ST_WAIT_CMD8: begin
                if (rx_byte_ready)
                    next_state = ST_SEND_CMD55;
                else if (over_time_cnt == 25'd25_000_000)
                    next_state = ST_SEND_CMD8;
            end

            ST_SEND_CMD55: begin
                if (cmd_sent)
                    next_state = ST_WAIT_CMD55;
            end

            ST_WAIT_CMD55: begin
                if (rx_byte_ready)
                    next_state = ST_SEND_ACMD41;
                else if (over_time_cnt == 25'd25_000_000)
                    next_state = ST_SEND_CMD55;
            end

            ST_SEND_ACMD41: begin
                if (cmd_sent)
                    next_state = ST_WAIT_ACMD41;
            end

            ST_WAIT_ACMD41: begin
                if (rx_byte_ready) begin
                    if (rx_byte == 8'h00)
                        next_state = ST_INIT_DONE;
                    else
                        next_state = ST_SEND_CMD55;
                end else if (over_time_cnt == 25'd25_000_000)
                    next_state = ST_SEND_CMD55;
            end

            ST_INIT_DONE: next_state = ST_RD_IDLE;
            ST_RD_IDLE: begin
                if (read_req)
                    next_state = ST_RD_SEND;
            end
            ST_RD_SEND: begin
                if (cmd_sent)
                    next_state = ST_RD_WAIT_R1;
            end
            ST_RD_WAIT_R1: begin
                if (rx_byte_ready) begin
                    if (rx_byte == 8'h00)
                        next_state = ST_RD_WAIT_TOKEN;
                    else
                        next_state = ST_RD_IDLE;
                end
            end
            ST_RD_WAIT_TOKEN: begin
                if (rx_byte_ready && rx_byte == 8'hFE)
                    next_state = ST_RD_DATA;
            end
            ST_RD_DATA: begin
                if (rx_byte_ready && data_cnt == 10'd511)
                    next_state = ST_RD_CRC;
            end
            ST_RD_CRC: begin
                if (rx_byte_ready && crc_cnt == 10'd1)
                    next_state = ST_RD_IDLE;
            end
            default: next_state = ST_IDLE;
        endcase
    end

    // ---- 制御出力 (state に同期して更新) ----
    always_ff @(posedge clk_ref or negedge rst_n) begin
        if (!rst_n) begin
            over_time_cnt <= 25'd0;
            acmd41_tries  <= 8'd0;
            data_cnt      <= 10'd0;
            crc_cnt       <= 10'd0;
            sd_cs_n       <= 1'b1;
            data          <= 8'h00;
            data_valid    <= 1'b0;
            busy          <= 1'b1;
            init_done     <= 1'b0;
        end else begin
            data_valid <= 1'b0;

            // 送信中 / 応答待ちで CS を制御
            case (state)
                ST_SEND_CMD0, ST_SEND_CMD8, ST_SEND_CMD55, ST_SEND_ACMD41,
                ST_RD_SEND:
                    sd_cs_n <= 1'b0;

                ST_WAIT_CMD0, ST_WAIT_CMD8, ST_WAIT_CMD55, ST_WAIT_ACMD41,
                ST_RD_WAIT_R1, ST_RD_WAIT_TOKEN, ST_RD_DATA, ST_RD_CRC:
                    sd_cs_n <= 1'b0;

                default:
                    sd_cs_n <= 1'b1;
            endcase

            // タイムアウトカウンタ
            if (state == ST_WAIT_CMD0 || state == ST_WAIT_CMD8 ||
                state == ST_WAIT_CMD55 || state == ST_WAIT_ACMD41)
                over_time_cnt <= over_time_cnt + 1'b1;
            else
                over_time_cnt <= 25'd0;

            // 初期化完了
            if (state == ST_INIT_DONE) begin
                init_done <= 1'b1;
                busy      <= 1'b0;
            end

            // データ受信
            if (state == ST_RD_DATA && rx_byte_ready) begin
                data       <= rx_byte;
                data_valid <= 1'b1;
                if (data_cnt == 10'd511)
                    data_cnt <= 10'd0;
                else
                    data_cnt <= data_cnt + 1'b1;
            end

            if (state == ST_RD_CRC && rx_byte_ready) begin
                if (crc_cnt == 10'd1)
                    crc_cnt <= 10'd0;
                else
                    crc_cnt <= crc_cnt + 1'b1;
            end

            // busy: ブロック読出し中は High、待機(ST_RD_IDLE)では Low。
            // 従来は read_req で High にするだけで読出し完了時に Low に戻さない
            // ため、先頭ブロック(ヘッダ)しか読めず画像データが入らなかった。
            if (state == ST_RD_SEND || state == ST_RD_WAIT_R1 ||
                state == ST_RD_WAIT_TOKEN || state == ST_RD_DATA ||
                state == ST_RD_CRC)
                busy <= 1'b1;
            else if (state == ST_RD_IDLE)
                busy <= 1'b0;
        end
    end

    // ========================================================================
    // デバッグフェーズ出力
    // ========================================================================
    always_comb begin
        case (state)
            ST_IDLE:                              debug_stage = 3'd0;
            ST_SEND_CMD0, ST_WAIT_CMD0:           debug_stage = 3'd1;
            ST_SEND_CMD8, ST_WAIT_CMD8:           debug_stage = 3'd2;
            ST_SEND_CMD55, ST_WAIT_CMD55,
            ST_SEND_ACMD41, ST_WAIT_ACMD41:       debug_stage = 3'd3;
            default:                              debug_stage = 3'd4;
        endcase
    end

endmodule
広げる

bmp_parser.sv

// ============================================================================
//  bmp_parser.sv
//  SDカードから読み出したバイトストリームから BMP (無圧縮 24bit) を解釈する
//
//  【機能】
//   ・BITMAPFILEHEADER (14byte) と BITMAPINFOHEADER (40byte) をパース
//   ・画像の幅・高さ・ビット深度・データオフセットを抽出
//   ・画像データ部を 24bit (R,G,B) ピクセルとして出力する
//
//  【入出力】
//   入力  in_data      : SDリーダからのバイト (data)
//   入力  in_valid     : SDリーダからの有効信号 (data_valid)
//   出力  px_x         : 出力ピクセルの X座標 (0..WIDTH-1)
//   出力  px_y         : 出力ピクセルの Y座標 (0..HEIGHT-1)
//   出力  px_r/g/b     : ピクセル色 (各8bit)
//   出力  px_valid     : ピクセル有効 (1クロックパルス)
//   出力  img_ready    : ヘッダパース完了 (幅/高さ確定)
//   出力  width        : 画像の幅
//   出力  height       : 画像の高さ
//
//  ※ BMP はボトムアップ (下の行から格納) なので、Y座標は反転して出力する。
//  ※ 各行は4バイト境界にパディングされる (row_padding)。
// ============================================================================
module bmp_parser (
    input  logic        clk,
    input  logic        rst_n,

    // 入力バイトストリーム
    input  logic [7:0]  in_data,
    input  logic        in_valid,

    // 出力ピクセル
    output logic [15:0] px_x,
    output logic [15:0] px_y,
    output logic [7:0]  px_r,
    output logic [7:0]  px_g,
    output logic [7:0]  px_b,
    output logic        px_valid,

    // ヘッダ情報
    output logic        img_ready,
    output logic [15:0] width,
    output logic [15:0] height
);

    // ========================================================================
    // バイトカウンタ (ストリーム先頭からの位置)
    // ========================================================================
    logic [31:0] byte_pos;   // 0-based

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            byte_pos <= 32'd0;
        end else if (in_valid) begin
            byte_pos <= byte_pos + 32'd1;
        end
    end

    // ========================================================================
    // ヘッダ情報レジスタ
    // ========================================================================
    // BITMAPFILEHEADER (14byte)
    //   0..1   : 'BM' (0x42 0x4D)
    //   2..5   : ファイルサイズ
    //   6..9   : 予約
    //   10..13 : データオフセット (LE)
    // BITMAPINFOHEADER (40byte, オフセット14から)
    //   14..17 : ヘッダサイズ (40)
    //   18..21 : 幅 (LE)
    //   22..25 : 高さ (LE)
    //   26..27 : プレーン数 (1)
    //   28..29 : ビット深度 (24)
    //   30..33 : 圧縮 (0=無圧縮)
    //   34..37 : 画像データサイズ
    //   38..53 : 解像度・パレットなど
    logic [31:0] data_offset;   // 画像データ先頭オフセット
    logic [31:0] img_width;     // 幅
    logic [31:0] img_height;    // 高さ
    logic [15:0] bit_depth;     // ビット深度

    // リトルエンディアンのバイトを順次受けて32bitに組み立てる
    logic [31:0] offset_sh;
    logic [31:0] width_sh;
    logic [31:0] height_sh;
    logic [15:0] depth_sh;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            offset_sh <= 32'd0;
            width_sh  <= 32'd0;
            height_sh <= 32'd0;
            depth_sh  <= 16'd0;
        end else if (in_valid) begin
            case (byte_pos)
                32'd10: offset_sh <= {in_data, 24'd0};
                32'd11: offset_sh <= {in_data, offset_sh[31:8]};
                32'd12: offset_sh <= {in_data, offset_sh[31:8]};
                32'd13: begin
                    offset_sh <= {in_data, offset_sh[31:8]};
                    data_offset <= {in_data, offset_sh[31:8]};
                end

                32'd18: width_sh <= {in_data, 24'd0};
                32'd19: width_sh <= {in_data, width_sh[31:8]};
                32'd20: width_sh <= {in_data, width_sh[31:8]};
                32'd21: begin
                    width_sh <= {in_data, width_sh[31:8]};
                    img_width <= {in_data, width_sh[31:8]};
                end

                32'd22: height_sh <= {in_data, 24'd0};
                32'd23: height_sh <= {in_data, height_sh[31:8]};
                32'd24: height_sh <= {in_data, height_sh[31:8]};
                32'd25: begin
                    height_sh <= {in_data, height_sh[31:8]};
                    img_height <= {in_data, height_sh[31:8]};
                end

                32'd28: depth_sh <= {in_data, 8'd0};
                32'd29: begin
                    depth_sh <= {in_data, depth_sh[15:8]};
                    bit_depth <= {in_data, depth_sh[15:8]};
                end

                default: ; // 何もしない
            endcase
        end
    end

    // ヘッダパース完了 (データオフセットに到達)
    assign img_ready = (byte_pos == data_offset) && (data_offset != 32'd0);
    assign width     = img_width[15:0];
    assign height    = img_height[15:0];

    // ========================================================================
    // ピクセルデータ抽出
    // ========================================================================
    // 行のバイト数 = 幅 * 3 (24bit) → 4バイト境界に丸める
    logic [31:0] row_bytes_raw;
    logic [31:0] row_bytes;     // パディング込みの行バイト数
    logic [15:0] row_pad;

    assign row_bytes_raw = img_width * 3;
    assign row_bytes     = (row_bytes_raw + 3) & 32'hFFFFFFFC;
    assign row_pad       = row_bytes[15:0] - row_bytes_raw[15:0];

    // 画像データ内での位置 (データオフセットからの相対バイト)
    logic [31:0] rel_pos;
    logic [31:0] rel_pos_next;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            rel_pos <= 32'd0;
        else if (in_valid && byte_pos >= data_offset)
            rel_pos <= rel_pos + 32'd1;
        else if (byte_pos < data_offset)
            rel_pos <= 32'd0;
    end

    // 現在のピクセル内バイト位相 (0,1,2 = B,G,R)
    logic [1:0] px_phase;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            px_phase <= 2'd0;
        else if (in_valid && byte_pos >= data_offset)
            px_phase <= (px_phase == 2'd2) ? 2'd0 : px_phase + 2'd1;
    end

    // 行内 X と 行番号 Y
    logic [15:0] x_cnt;
    logic [15:0] y_cnt;     // ボトムアップ (下から)

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            x_cnt <= 16'd0;
            y_cnt <= 16'd0;
        end else if (in_valid && byte_pos >= data_offset) begin
            if (px_phase == 2'd2) begin
                // 1ピクセル完了
                if (x_cnt == img_width[15:0] - 1) begin
                    x_cnt <= 16'd0;
                    y_cnt <= y_cnt + 16'd1;
                end else begin
                    x_cnt <= x_cnt + 16'd1;
                end
            end
        end
    end

    // ピクセル色の組み立て (B → G → R の順)
    logic [7:0] px_blue, px_green, px_red;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            px_blue  <= 8'd0;
            px_green <= 8'd0;
            px_red   <= 8'd0;
        end else if (in_valid && byte_pos >= data_offset) begin
            case (px_phase)
                2'd0: px_blue  <= in_data;
                2'd1: px_green <= in_data;
                2'd2: px_red   <= in_data;
            endcase
        end
    end

    // ピクセル出力 (Y座標をトップアップに反転)
    assign px_x = x_cnt;
    assign px_y = img_height[15:0] - 1 - y_cnt;
    assign px_r = px_red;
    assign px_g = px_green;
    assign px_b = px_blue;

    // px_valid: ピクセルの3バイト目 (R) が揃った時
    logic px_3rd_byte;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            px_3rd_byte <= 1'b0;
        else
            px_3rd_byte <= in_valid && (byte_pos >= data_offset) && (px_phase == 2'd2);
    end
    assign px_valid = px_3rd_byte;

endmodule
広げる

参考文献

(1)GitHubリポジトリ

コメントを残す