2026-10-25 12:56:54 +02:00
|
|
|
// Testbench for strix_core: SPI register access, single and pair captures read back across
|
|
|
|
|
// transactions, and the frequency counter. Rings are replaced by the SIM model in ring_osc.v.
|
2026-10-25 12:25:25 +02:00
|
|
|
`timescale 1ns / 1ps
|
|
|
|
|
|
|
|
|
|
module tb_strix_core;
|
|
|
|
|
|
|
|
|
|
localparam CAPTURE_ADDR_WIDTH = 6; // 64-byte buffer keeps the simulation short
|
|
|
|
|
localparam CAPTURE_BYTES = 1 << CAPTURE_ADDR_WIDTH;
|
2026-10-25 12:56:54 +02:00
|
|
|
localparam SCK_HALF = 62.5; // 8 MHz SCK
|
|
|
|
|
|
|
|
|
|
localparam [6:0] REG_ID0 = 7'h00, REG_ID3 = 7'h03, REG_VERSION = 7'h04,
|
|
|
|
|
REG_SOURCE_A = 7'h08, REG_DECIM_LOW = 7'h0A, REG_CONTROL = 7'h0B,
|
|
|
|
|
REG_STATUS = 7'h0C, REG_SOURCE_B = 7'h0D, REG_MODE = 7'h0E,
|
|
|
|
|
REG_RING_COUNT = 7'h0F, REG_DATA = 7'h10, REG_FREQ_SELECT = 7'h11,
|
|
|
|
|
REG_FREQ_COUNT0 = 7'h15;
|
2026-10-25 12:25:25 +02:00
|
|
|
|
|
|
|
|
reg clk = 1'b0;
|
2026-10-25 12:56:54 +02:00
|
|
|
always #4.1667 clk = ~clk; // 120 MHz
|
2026-10-25 12:25:25 +02:00
|
|
|
|
|
|
|
|
reg reset = 1'b1;
|
|
|
|
|
reg sck = 1'b0, mosi = 1'b0, ss_n = 1'b1;
|
|
|
|
|
wire miso, done;
|
|
|
|
|
|
2026-10-25 12:56:54 +02:00
|
|
|
strix_core #(.CAPTURE_ADDR_WIDTH(CAPTURE_ADDR_WIDTH), .FREQ_GATE_BITS(14)) dut (
|
2026-10-25 12:25:25 +02:00
|
|
|
.clk(clk), .reset(reset),
|
|
|
|
|
.spi_sck(sck), .spi_mosi(mosi), .spi_ss_n(ss_n), .spi_miso(miso),
|
|
|
|
|
.capture_done(done)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
integer errors = 0;
|
|
|
|
|
|
|
|
|
|
task spi_byte(input [7:0] out, output [7:0] in);
|
|
|
|
|
integer i;
|
|
|
|
|
begin
|
|
|
|
|
for (i = 7; i >= 0; i = i - 1) begin
|
|
|
|
|
mosi = out[i];
|
|
|
|
|
#SCK_HALF sck = 1'b1;
|
|
|
|
|
in[i] = miso;
|
|
|
|
|
#SCK_HALF sck = 1'b0;
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
endtask
|
|
|
|
|
|
|
|
|
|
task write_reg(input [6:0] addr, input [7:0] value);
|
|
|
|
|
reg [7:0] dummy;
|
|
|
|
|
begin
|
|
|
|
|
ss_n = 1'b0; #SCK_HALF;
|
|
|
|
|
spi_byte({1'b0, addr}, dummy);
|
|
|
|
|
spi_byte(value, dummy);
|
|
|
|
|
#SCK_HALF ss_n = 1'b1; #(4 * SCK_HALF);
|
|
|
|
|
end
|
|
|
|
|
endtask
|
|
|
|
|
|
|
|
|
|
task read_reg(input [6:0] addr, output [7:0] value);
|
|
|
|
|
reg [7:0] dummy;
|
|
|
|
|
begin
|
|
|
|
|
ss_n = 1'b0; #SCK_HALF;
|
|
|
|
|
spi_byte({1'b1, addr}, dummy);
|
|
|
|
|
spi_byte(8'h00, value);
|
|
|
|
|
#SCK_HALF ss_n = 1'b1; #(4 * SCK_HALF);
|
|
|
|
|
end
|
|
|
|
|
endtask
|
|
|
|
|
|
2026-10-25 12:56:54 +02:00
|
|
|
task expect(input [7:0] got, input [7:0] want, input [8*16-1:0] what);
|
|
|
|
|
if (got !== want) begin
|
|
|
|
|
$display("FAIL %0s: got %02x, expected %02x", what, got, want);
|
|
|
|
|
errors = errors + 1;
|
|
|
|
|
end
|
|
|
|
|
endtask
|
|
|
|
|
|
|
|
|
|
task run_capture;
|
|
|
|
|
reg [7:0] status;
|
|
|
|
|
integer polls;
|
|
|
|
|
begin
|
|
|
|
|
write_reg(REG_CONTROL, 8'h01);
|
|
|
|
|
polls = 0;
|
|
|
|
|
read_reg(REG_STATUS, status);
|
|
|
|
|
while (status[1] !== 1'b1 && polls < 1000) begin
|
|
|
|
|
read_reg(REG_STATUS, status);
|
|
|
|
|
polls = polls + 1;
|
|
|
|
|
end
|
|
|
|
|
if (status[1] !== 1'b1 || done !== 1'b1) begin
|
|
|
|
|
$display("FAIL capture did not finish");
|
|
|
|
|
errors = errors + 1;
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
endtask
|
|
|
|
|
|
|
|
|
|
// Captured bytes must be defined and vary, otherwise the read-back checks prove nothing.
|
|
|
|
|
task check_buffer_contents;
|
|
|
|
|
integer i, changes;
|
|
|
|
|
begin
|
|
|
|
|
changes = 0;
|
|
|
|
|
for (i = 0; i < CAPTURE_BYTES; i = i + 1) begin
|
|
|
|
|
if (^dut.capture.buffer[i] === 1'bx) begin
|
|
|
|
|
$display("FAIL buffer[%0d] undefined", i);
|
|
|
|
|
errors = errors + 1;
|
|
|
|
|
end
|
|
|
|
|
if (i > 0 && dut.capture.buffer[i] !== dut.capture.buffer[i - 1]) changes = changes + 1;
|
|
|
|
|
end
|
|
|
|
|
if (changes == 0) begin
|
|
|
|
|
$display("FAIL capture buffer is constant");
|
|
|
|
|
errors = errors + 1;
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
endtask
|
|
|
|
|
|
2026-10-25 12:25:25 +02:00
|
|
|
task read_data(input integer first, input integer count);
|
|
|
|
|
reg [7:0] dummy, value;
|
|
|
|
|
integer i;
|
|
|
|
|
begin
|
|
|
|
|
ss_n = 1'b0; #SCK_HALF;
|
2026-10-25 12:56:54 +02:00
|
|
|
spi_byte({1'b1, REG_DATA}, dummy);
|
2026-10-25 12:25:25 +02:00
|
|
|
for (i = 0; i < count; i = i + 1) begin
|
|
|
|
|
spi_byte(8'h00, value);
|
|
|
|
|
if (value !== dut.capture.buffer[first + i]) begin
|
|
|
|
|
$display("FAIL data[%0d]: got %02x, expected %02x", first + i, value, dut.capture.buffer[first + i]);
|
|
|
|
|
errors = errors + 1;
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
#SCK_HALF ss_n = 1'b1; #(4 * SCK_HALF);
|
|
|
|
|
end
|
|
|
|
|
endtask
|
|
|
|
|
|
|
|
|
|
reg [7:0] v;
|
2026-10-25 12:56:54 +02:00
|
|
|
integer i, polls;
|
2026-10-25 12:25:25 +02:00
|
|
|
|
|
|
|
|
initial begin
|
|
|
|
|
#100 reset = 1'b0;
|
|
|
|
|
#200;
|
|
|
|
|
|
2026-10-25 12:56:54 +02:00
|
|
|
// Identification
|
|
|
|
|
read_reg(REG_ID0, v); expect(v, "S", "ID0");
|
|
|
|
|
read_reg(REG_ID3, v); expect(v, "X", "ID3");
|
|
|
|
|
read_reg(REG_VERSION, v); expect(v, 8'h02, "VERSION");
|
|
|
|
|
read_reg(REG_RING_COUNT, v); expect(v, 8'd16, "RING_COUNT");
|
|
|
|
|
|
|
|
|
|
// Single capture of the XOR of rings 0-1, every 3rd sample, read in two transactions
|
|
|
|
|
write_reg(REG_SOURCE_A, 8'h21);
|
|
|
|
|
write_reg(REG_DECIM_LOW, 8'h03);
|
|
|
|
|
read_reg(REG_SOURCE_A, v); expect(v, 8'h21, "SOURCE_A");
|
|
|
|
|
run_capture;
|
|
|
|
|
check_buffer_contents;
|
|
|
|
|
read_data(0, CAPTURE_BYTES / 2);
|
|
|
|
|
read_data(CAPTURE_BYTES / 2, CAPTURE_BYTES / 2);
|
|
|
|
|
write_reg(REG_CONTROL, 8'h02); // rewind
|
|
|
|
|
read_data(0, 4);
|
2026-10-25 12:25:25 +02:00
|
|
|
|
2026-10-25 12:56:54 +02:00
|
|
|
// Pair capture with A = B: every bit pair must be 00 or 11
|
|
|
|
|
write_reg(REG_SOURCE_A, 8'h10);
|
|
|
|
|
write_reg(REG_SOURCE_B, 8'h10);
|
|
|
|
|
write_reg(REG_MODE, 8'h01);
|
|
|
|
|
write_reg(REG_DECIM_LOW, 8'h01);
|
|
|
|
|
run_capture;
|
|
|
|
|
check_buffer_contents;
|
|
|
|
|
for (i = 0; i < 4 * CAPTURE_BYTES; i = i + 1) begin
|
|
|
|
|
v = dut.capture.buffer[i / 4] >> (2 * (i % 4));
|
|
|
|
|
if (v[1] !== v[0]) begin
|
|
|
|
|
$display("FAIL pair %0d differs", i);
|
2026-10-25 12:25:25 +02:00
|
|
|
errors = errors + 1;
|
|
|
|
|
end
|
|
|
|
|
end
|
2026-10-25 12:56:54 +02:00
|
|
|
read_data(0, CAPTURE_BYTES);
|
|
|
|
|
|
|
|
|
|
// Frequency counter on ring 3
|
|
|
|
|
write_reg(REG_FREQ_SELECT, 8'h03);
|
|
|
|
|
write_reg(REG_CONTROL, 8'h04);
|
|
|
|
|
polls = 0;
|
|
|
|
|
read_reg(REG_STATUS, v);
|
|
|
|
|
while (v[3] !== 1'b1 && polls < 1000) begin
|
|
|
|
|
read_reg(REG_STATUS, v);
|
|
|
|
|
polls = polls + 1;
|
|
|
|
|
end
|
|
|
|
|
if (v[3] !== 1'b1) begin
|
|
|
|
|
$display("FAIL frequency measurement did not finish");
|
|
|
|
|
errors = errors + 1;
|
|
|
|
|
end
|
|
|
|
|
read_reg(REG_FREQ_COUNT0, v);
|
|
|
|
|
if (v == 8'd0 || dut.counter.count === 32'bx) begin
|
|
|
|
|
$display("FAIL frequency count is %0d", dut.counter.count);
|
2026-10-25 12:25:25 +02:00
|
|
|
errors = errors + 1;
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if (errors == 0) $display("PASS");
|
|
|
|
|
else $display("%0d error(s)", errors);
|
|
|
|
|
$finish;
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
endmodule
|