`timescale 1 ns / 1 ps //------------------------------------------------------------------------------ // csi040_ctrl 的自检查 testbench。 // // 这个 testbench 驱动 BRAM 风格控制口,监视 DUT 产生的 SPI 总线, // 并检查每个期望的 32 bit 单字节 SPI 帧: // [opcode][addr high][addr low][data] // // ASIC SO 只做了最小模型,用于 direct read 测试: // 在读帧的 payload byte 阶段返回 read_response。 //------------------------------------------------------------------------------ module csi040_ctrl_tb; localparam CLK_PERIOD = 10; // DUT 寄存器总线和 ASIC 侧引脚。 reg clk; reg rst_n; reg [12:0] bram_addr; reg [31:0] bram_din; wire [31:0] bram_dout; reg bram_en; reg [3:0] bram_we; wire busy; wire spi_done; wire error_busy; wire asic_rst_n; wire asic_trig; wire asic_csn; wire asic_sck; wire asic_si; reg asic_so; // SPI monitor 状态。CSN 为低时,shift_in 捕获 SI bit。 integer bit_cnt; integer frame_bits; reg [31:0] shift_in; reg [31:0] frame_in; reg [7:0] read_response; // 使用较小分频和常见 0x02/0x03 opcode,让仿真更短, // 波形里的 SPI 帧也更好读。 csi040_ctrl# ( .SPI_CLK_DIV ( 2 ), .SPI_OPCODE_WRITE ( 8'h02 ), .SPI_OPCODE_READ ( 8'h03 ) ) dut ( .clk ( clk ), .rst_n ( rst_n ), .bram_addr ( bram_addr ), .bram_clk ( clk ), .bram_din ( bram_din ), .bram_dout ( bram_dout ), .bram_en ( bram_en ), .bram_rst ( 1'b0 ), .bram_we ( bram_we ), .busy ( busy ), .spi_done ( spi_done ), .error_busy ( error_busy ), .asic_rst_n ( asic_rst_n ), .asic_trig ( asic_trig ), .asic_csn ( asic_csn ), .asic_sck ( asic_sck ), .asic_si ( asic_si ), .asic_so ( asic_so ) ); // 100 MHz 时钟。 initial begin clk = 1'b0; forever #(CLK_PERIOD/2) clk = ~clk; end // 主定向测试序列。每个 write_bram 要么启动命令,要么更新 shadow 寄存器; // 每个 check_next_frame 等待 CSN 拉高,并比较 monitor 捕获到的 SPI bit。 initial begin rst_n = 1'b0; bram_addr = 0; bram_din = 0; bram_en = 1'b0; bram_we = 4'h0; asic_so = 1'b0; read_response = 8'h28; #(CLK_PERIOD*8); rst_n = 1'b1; // CONTROL 写:ASIC register 0x0002 <= 0x10。 write_bram(13'h0020, 32'h00000010); check_next_frame(32'h02000210); // CH_ENABLE 写会把 16 bit 拆到 ASIC register 0x0007/0x0008。 write_bram(13'h0024, 32'h0000A55A); check_next_frame(32'h0200075A); check_next_frame(32'h020008A5); // 通道参数写。ch_index=3,CH_CUR_A offset=1, // ASIC 地址 = 0x0010 + 3*0x10 + 1 = 0x0041。 write_bram(13'h0040, 32'h00000003); write_bram(13'h0048, 32'h0000007F); check_next_frame(32'h0200417F); // DCS 解锁序列:向 key register 0x0009 连续写 3 个魔术字节。 write_bram(13'h002C, 32'h00000001); check_next_frame(32'h02000955); check_next_frame(32'h020009B3); check_next_frame(32'h0200090A); // Direct 方式从 Part ID register 0x011D 读 1 字节。 // 下面的 ASIC 模型返回 0x28,随后通过 BRAM 读 SPI_RDATA 检查。 write_bram(13'h0008, 32'h0000011D); write_bram(13'h0010, 32'h00000100); check_next_frame(32'h03011D00); read_bram(13'h0014); if (bram_dout[7:0] !== 8'h28) begin $display("ERROR read data expected 28 got %h", bram_dout[7:0]); $stop; end #(CLK_PERIOD*20); $finish; end // CSN 拉低时,开始捕获一帧新的 SPI。 always @(negedge asic_csn) begin bit_cnt <= 0; shift_in <= 0; end // CSN 拉高时,锁存刚捕获完的一帧。 always @(posedge asic_csn) begin frame_in <= shift_in; frame_bits <= bit_cnt; end // SPI monitor:CSN 有效期间,在 SCK 上升沿采样 MOSI/SI。 always @(posedge asic_sck) begin if (!asic_csn) begin shift_in <= {shift_in[30:0], asic_si}; bit_cnt <= bit_cnt + 1; end end // 最小 ASIC 读模型。对当前 32 bit 测试帧来说,bit 24..31 是返回数据字节。 // 在下降沿驱动 SO,保证 DUT 在之后的上升沿能采到。 always @(negedge asic_sck) begin if (!asic_csn && (bit_cnt >= 24) && (bit_cnt < 32)) asic_so <= read_response[31-bit_cnt]; else asic_so <= 1'b0; end // 整字 BRAM 写 helper。先等待 DUT 空闲,避免定向测试误触发 busy-error。 task write_bram; input [12:0] addr; input [31:0] data; begin wait(busy == 1'b0); @(negedge clk); bram_addr = addr; bram_din = data; bram_en = 1'b1; bram_we = 4'hF; @(posedge clk); @(negedge clk); bram_en = 1'b0; bram_we = 4'h0; end endtask // BRAM 读 helper。bram_en 为高且 bram_we 为 0 的时钟沿更新 bram_dout。 task read_bram; input [12:0] addr; begin @(negedge clk); bram_addr = addr; bram_din = 0; bram_en = 1'b1; bram_we = 4'h0; @(posedge clk); @(negedge clk); bram_en = 1'b0; end endtask // 等待下一帧 SPI 完成,并和期望的 32 bit 单字节帧比较。 task check_next_frame; input [31:0] expected; begin @(posedge asic_csn); #(CLK_PERIOD/2); if (frame_bits != 32) begin $display("ERROR frame expected 32 bits got %0d bits", frame_bits); $stop; end if (frame_in !== expected) begin $display("ERROR frame expected %h got %h", expected, frame_in); $stop; end end endtask endmodule