STEP20 work toward the V2 hardware release gate. Adds real new RTL
implementing the three pieces the previous freeze (V2.0.0) explicitly
left open, plus real, disclosed verification findings. Does NOT
declare hardware release complete -- see below.
New RTL:
- spi_host_bridge.v: real SPI slave protocol engine (WRITE_JOB/
WRITE_MEM/READ_MEM/STATUS/RESET opcodes), replacing the 110-pin
reg_* testbench bus as the intended physical host interface.
Isolated regression 18/18 PASS (tb_spi_host_bridge.v); two real
MISO-timing bugs found and fixed during its own development (see
the module's header for the root-cause writeup).
- ecp5_pll_sys_clk.v: real, tool-generated (Project Trellis ecppll)
EHXPLLL wrapper, 16MHz oscillator -> 64MHz system clock, with a
declared (not fabricated) simulation-only PLL bypass.
- reset_sync.v: standard async-assert/sync-deassert reset bridge
gating on external POR and PLL lock.
- fpga_neural_v2_top.v: board-level top wiring the above around the
STEP19 compute+memory design's own already-frozen submodules
(zero modification to neural_processor.v, dependency_manager.v,
sdram_unified_backend.v, or any other previously-frozen file).
Real findings from this step's own re-verification (both logged in
full in hardware/v2/logs/errors.log):
- ERR-0024: the current Icarus Verilog v13.0 install (updated since
the last freeze) gives WRONG bit-exact results for the
already-committed STEP19 regression. Cross-checked against
Verilator per this project's own standing protocol (DEC-0004) --
the STEP19 baseline (single SDRAM, N=2/N=4, raw reg_* interface) IS
bit-exact correct, reconfirmed today, matching the historical cycle
counts exactly. Two provably-zero-behavior-change declaration-order
fixes were required just to get the current toolchain to elaborate
the already-shipped STEP19 files at all.
- ERR-0025: a real SPI-bridge protocol race (fixed) plus a SEPARATE,
real, UNRESOLVED defect -- two jobs dispatched through the real SPI
path with realistic pacing produce wrong compute results, even
though job registration itself is confirmed correct at the
handshake. Root cause not yet isolated. Committed as a known-failing
regression (tb_fpga_neural_v2_top_smoke.v) documenting the gap
honestly rather than hiding it.
Given ERR-0025 Part B is real and unresolved, synthesis/P&R of the new
board-level top was deliberately not attempted this round, and V2
hardware release is NOT declared complete. See decisions.log DEC-0036
and hardware/v2/docs/{CHIP_READINESS,OPEN_ITEMS}.md for the full,
itemized status.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
225 lines
9.3 KiB
Verilog
225 lines
9.3 KiB
Verilog
`timescale 1ns/1ps
|
|
|
|
// ================================================================
|
|
// Isolated unit regression for spi_host_bridge.v (STEP20).
|
|
//
|
|
// Emulates: (1) dependency_manager.v's reg_ready contract (a level,
|
|
// asserted only when the target node is free -- here deliberately
|
|
// delayed for a few cycles on the first job to prove reg_valid is
|
|
// HELD, not pulsed blind); (2) the host-arb slot_mem_arbiter's
|
|
// mem_ready contract (one clean req/ready handshake, SDRAM-like fixed
|
|
// latency, backed by a simple associative model array standing in for
|
|
// real SDRAM content).
|
|
//
|
|
// Per spi_host_bridge.v's own documented protocol: CS must stay
|
|
// asserted (low) for the WHOLE WRITE_MEM/READ_MEM transaction,
|
|
// including the internal wait for mem_ready -- SCLK may be idled
|
|
// (held low, no toggling) during that wait without losing state. This
|
|
// testbench's SPI master BFM does exactly that.
|
|
// ================================================================
|
|
|
|
module tb_spi_host_bridge;
|
|
|
|
localparam ADDR_WIDTH = 23;
|
|
localparam N_NODES = 16;
|
|
localparam MAX_DEPS = 4;
|
|
localparam NODEW = $clog2(N_NODES);
|
|
localparam REQW = $clog2(MAX_DEPS+1);
|
|
|
|
reg clk = 0, rst = 1;
|
|
always #5 clk = ~clk; // 100MHz sim clock (arbitrary, faster than SPI)
|
|
|
|
reg sclk = 0, mosi = 0, cs_n = 1;
|
|
wire miso;
|
|
|
|
reg reg_ready_model = 0;
|
|
wire reg_valid;
|
|
wire [NODEW-1:0] reg_node_id;
|
|
wire [REQW-1:0] reg_required;
|
|
wire [MAX_DEPS*NODEW-1:0] reg_producer_ids;
|
|
wire [ADDR_WIDTH-1:0] reg_x_base, reg_w_base, reg_result_addr;
|
|
wire [15:0] reg_n_tiles;
|
|
|
|
wire mem_req, mem_wr, mem_lb_n, mem_ub_n;
|
|
wire [ADDR_WIDTH-1:0] mem_addr;
|
|
wire [15:0] mem_wdata;
|
|
reg [15:0] mem_rdata_model;
|
|
reg mem_ready_model = 0;
|
|
|
|
wire soft_rst_pulse;
|
|
|
|
spi_host_bridge #(
|
|
.ADDR_WIDTH(ADDR_WIDTH), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS)
|
|
) dut (
|
|
.clk(clk), .rst(rst),
|
|
.sclk(sclk), .mosi(mosi), .miso(miso), .cs_n(cs_n),
|
|
.reg_valid(reg_valid), .reg_ready(reg_ready_model),
|
|
.reg_node_id(reg_node_id), .reg_required(reg_required),
|
|
.reg_producer_ids(reg_producer_ids),
|
|
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base),
|
|
.reg_n_tiles(reg_n_tiles), .reg_result_addr(reg_result_addr),
|
|
.mem_req(mem_req), .mem_wr(mem_wr), .mem_addr(mem_addr),
|
|
.mem_wdata(mem_wdata), .mem_lb_n(mem_lb_n), .mem_ub_n(mem_ub_n),
|
|
.mem_rdata(mem_rdata_model), .mem_ready(mem_ready_model),
|
|
.soft_rst_pulse(soft_rst_pulse)
|
|
);
|
|
|
|
// ---- simple backing memory model: fixed 6-cycle mem_ready latency ----
|
|
reg [15:0] mem_model [0:1023];
|
|
integer mem_latency_cnt;
|
|
reg mem_pending;
|
|
always @(posedge clk) begin
|
|
if (rst) begin
|
|
mem_ready_model <= 1'b0; mem_pending <= 1'b0; mem_latency_cnt <= 0;
|
|
end else begin
|
|
mem_ready_model <= 1'b0;
|
|
if (mem_req && !mem_pending) begin
|
|
mem_pending <= 1'b1;
|
|
mem_latency_cnt <= 6;
|
|
end else if (mem_pending) begin
|
|
if (mem_latency_cnt == 0) begin
|
|
mem_pending <= 1'b0;
|
|
mem_ready_model <= 1'b1;
|
|
if (mem_wr) mem_model[mem_addr[9:0]] <= mem_wdata;
|
|
else mem_rdata_model <= mem_model[mem_addr[9:0]];
|
|
end else begin
|
|
mem_latency_cnt <= mem_latency_cnt - 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
// ---- SPI master BFM: mode 0, MSB-first ----
|
|
// Bit period = 500ns (2MHz SPI clock) against a 100MHz sim `clk`:
|
|
// a 50x margin over the ~4-clk-cycle CDC synchronizer latency,
|
|
// representative of a REAL deployment (system clock 64-80MHz vs a
|
|
// practical SPI clock in the low single-digit MHz -- see this
|
|
// module's own header for the documented minimum ratio). A torture
|
|
// rate close to the CDC latency (as an earlier draft of this
|
|
// testbench used) is not a realistic operating point and is not
|
|
// what this module is specified against.
|
|
task spi_byte(input [7:0] tx, output [7:0] rx);
|
|
integer i;
|
|
begin
|
|
rx = 8'h00;
|
|
for (i = 7; i >= 0; i = i - 1) begin
|
|
mosi = tx[i];
|
|
#200; sclk = 1; #50; rx = {rx[6:0], miso}; #50; sclk = 0; #200;
|
|
end
|
|
end
|
|
endtask
|
|
|
|
integer errors = 0, tests = 0;
|
|
task check(input cond, input [255:0] name);
|
|
begin
|
|
tests = tests + 1;
|
|
if (!cond) begin errors = errors + 1; $display("FAIL: %0s", name); end
|
|
else $display("PASS: %0s", name);
|
|
end
|
|
endtask
|
|
|
|
reg [7:0] rxb;
|
|
reg [ADDR_WIDTH-1:0] exp_addr;
|
|
|
|
initial begin
|
|
rst = 1; cs_n = 1; sclk = 0; mosi = 0;
|
|
repeat (10) @(posedge clk);
|
|
rst = 0;
|
|
repeat (5) @(posedge clk);
|
|
|
|
// ================= Test A: WRITE_JOB, delayed reg_ready =====
|
|
reg_ready_model = 0;
|
|
cs_n = 0; #20;
|
|
spi_byte(8'h10, rxb); // opcode WRITE_JOB
|
|
spi_byte(8'h05, rxb); // node_id=5
|
|
spi_byte(8'h02, rxb); // required=2
|
|
spi_byte(8'hAB, rxb); // producer_ids[15:8]
|
|
spi_byte(8'hCD, rxb); // producer_ids[7:0]
|
|
spi_byte(8'h00, rxb); // x_base[22:16]
|
|
spi_byte(8'h10, rxb); // x_base[15:8]
|
|
spi_byte(8'h00, rxb); // x_base[7:0] -> x_base=0x001000
|
|
spi_byte(8'h00, rxb); // w_base[22:16]
|
|
spi_byte(8'h20, rxb); // w_base[15:8]
|
|
spi_byte(8'h00, rxb); // w_base[7:0] -> w_base=0x002000
|
|
spi_byte(8'h00, rxb); // n_tiles[15:8]
|
|
spi_byte(8'h04, rxb); // n_tiles[7:0] -> n_tiles=4
|
|
spi_byte(8'h00, rxb); // result_addr[22:16]
|
|
spi_byte(8'h30, rxb); // result_addr[15:8]
|
|
spi_byte(8'h00, rxb); // result_addr[7:0] -> result_addr=0x003000
|
|
|
|
// reg_valid must now be held (reg_ready still 0). Allow for the
|
|
// CDC synchronizer latency on the LAST bit before sampling.
|
|
repeat (8) @(posedge clk);
|
|
check(reg_valid == 1'b1, "A: reg_valid asserted after 15th payload byte");
|
|
check(reg_node_id == 5, "A: reg_node_id");
|
|
check(reg_required == 2, "A: reg_required");
|
|
check(reg_producer_ids == 16'hABCD, "A: reg_producer_ids");
|
|
check(reg_x_base == 23'h001000, "A: reg_x_base");
|
|
check(reg_w_base == 23'h002000, "A: reg_w_base");
|
|
check(reg_n_tiles == 16'h0004, "A: reg_n_tiles");
|
|
check(reg_result_addr == 23'h003000, "A: reg_result_addr");
|
|
|
|
repeat (3) begin
|
|
@(posedge clk);
|
|
check(reg_valid == 1'b1, "A: reg_valid still held while reg_ready=0");
|
|
end
|
|
reg_ready_model = 1;
|
|
@(posedge clk);
|
|
#1;
|
|
check(reg_valid == 1'b0, "A: reg_valid drops the cycle after reg_ready seen");
|
|
reg_ready_model = 0;
|
|
cs_n = 1; #40;
|
|
|
|
// ================= Test B: STATUS after accepted job ========
|
|
cs_n = 0; #20;
|
|
spi_byte(8'h20, rxb); // opcode STATUS
|
|
spi_byte(8'h00, rxb); // clocks out status byte
|
|
$monitoroff;
|
|
check(rxb[2] == 1'b1, "B: STATUS last_job_accepted=1");
|
|
check(rxb[0] == 1'b0, "B: STATUS job_busy=0 (already accepted)");
|
|
cs_n = 1; #40;
|
|
|
|
// ================= Test C: WRITE_MEM, single word ===========
|
|
cs_n = 0; #20;
|
|
spi_byte(8'h01, rxb); // opcode WRITE_MEM
|
|
spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h55, rxb); // addr=0x000055
|
|
spi_byte(8'h00, rxb); spi_byte(8'h01, rxb); // len_words=1
|
|
spi_byte(8'h12, rxb); spi_byte(8'h34, rxb); // data=0x1234
|
|
// hold CS low, idle SCLK, while the memory model latency elapses
|
|
#200;
|
|
cs_n = 1; #40;
|
|
check(mem_model[16'h0055] == 16'h1234, "C: WRITE_MEM wrote 0x1234 @ 0x000055");
|
|
|
|
// ================= Test D: READ_MEM, single word =============
|
|
cs_n = 0; #20;
|
|
spi_byte(8'h02, rxb); // opcode READ_MEM
|
|
spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h55, rxb); // addr=0x000055
|
|
spi_byte(8'h00, rxb); spi_byte(8'h01, rxb); // len_words=1
|
|
#200; // idle SCLK while the read latency elapses
|
|
spi_byte(8'h00, rxb); exp_addr = rxb; // MSB
|
|
check(rxb == 8'h12, "D: READ_MEM MSB byte == 0x12");
|
|
spi_byte(8'h00, rxb);
|
|
check(rxb == 8'h34, "D: READ_MEM LSB byte == 0x34");
|
|
cs_n = 1; #40;
|
|
|
|
// ================= Test E: RESET opcode ======================
|
|
cs_n = 0; #20;
|
|
spi_byte(8'h0F, rxb); // opcode RESET
|
|
cs_n = 1;
|
|
begin : wait_soft_rst
|
|
integer wi; reg seen;
|
|
seen = 1'b0;
|
|
for (wi = 0; wi < 10; wi = wi + 1) begin
|
|
@(posedge clk);
|
|
if (soft_rst_pulse) seen = 1'b1;
|
|
end
|
|
check(seen, "E: soft_rst_pulse asserted after CS rises (within CDC latency)");
|
|
end
|
|
|
|
$display("=== tb_spi_host_bridge: %0d/%0d PASS ===", tests-errors, tests);
|
|
if (errors != 0) $display("*** %0d FAILURES ***", errors);
|
|
$finish;
|
|
end
|
|
|
|
endmodule
|