209 lines
6.7 KiB
Verilog
209 lines
6.7 KiB
Verilog
`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
|