
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リポジトリ