open-standards/wai/wai-quantum-landscape-fpga/tb/tb_landscape_axi.v
Transaction Science c2db9d17d1 wai-quantum-landscape-fpga: the link meets the board as it boots, and results go back by DMA
The processor's master and high-performance ports boot at 128 bits and loading a bitstream does not
change them, so the AXI build meets them with width converters and writes no processor register;
ps-ports reads them, and the host touches no AXI address if they differ. The registers move to
M_AXI_HPM0_FPD at 0xA000_0000, a port designs on this board have already used. A write master sends
a batch's results to DDR, a page at a time, and the testbench checks every word it writes. Writes are
now timed to their response. The class campaign's logs are added.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:30:14 -05:00

286 lines
13 KiB
Verilog

// SPDX-License-Identifier: Apache-2.0
// Replays a session recorded from the host's software model in its AXI form (`gen-tb --axi`)
// against the AXI link (rtl/landscape_axi.v) and the engine behind it: register accesses over
// AXI-Lite, write lists through the DMA from a memory model that answers with irregular delays and
// several bursts outstanding, and batches of chains through the sequencer, whose results are read
// from the result RAM and written back to the memory model. Every read and every word written back
// must be what the model returned. A mismatch names the line.
`timescale 1ns/1ps
module tb;
reg clk = 0;
always #5 clk = ~clk; // pl_clk0, 100 MHz
reg rstn = 0;
// AXI-Lite master
reg [31:0] awaddr = 0, wdata = 0, araddr = 0;
reg awvalid = 0, wvalid = 0, bready = 0, arvalid = 0, rready = 0;
wire awready, wready, bvalid, arready, rvalid;
wire [1:0] bresp, rresp;
wire [31:0] rdata;
// DMA read port
wire [48:0] m_araddr;
wire [7:0] m_arlen;
wire [2:0] m_arsize, m_arprot;
wire [1:0] m_arburst;
wire [3:0] m_arcache;
wire m_arvalid, m_rready;
reg m_arready = 0, m_rvalid = 0, m_rlast = 0;
reg [63:0] m_rdata = 0;
reg [1:0] m_rresp = 0;
// DMA write port
wire [48:0] m_awaddr;
wire [7:0] m_awlen, m_wstrb;
wire [2:0] m_awsize, m_awprot;
wire [1:0] m_awburst;
wire [3:0] m_awcache;
wire [63:0] m_wdata;
wire m_awvalid, m_wlast, m_wvalid, m_bready;
reg m_awready = 0, m_wready = 0, m_bvalid = 0;
reg [1:0] m_bresp = 0;
wire [94:0] emio_i;
landscape_axi dut (
.clk(clk), .rstn(rstn),
.s_awaddr(awaddr), .s_awvalid(awvalid), .s_awready(awready), .s_wdata(wdata), .s_wvalid(wvalid),
.s_wready(wready), .s_bresp(bresp), .s_bvalid(bvalid), .s_bready(bready),
.s_araddr(araddr), .s_arvalid(arvalid), .s_arready(arready), .s_rdata(rdata), .s_rresp(rresp),
.s_rvalid(rvalid), .s_rready(rready),
.m_araddr(m_araddr), .m_arlen(m_arlen), .m_arsize(m_arsize), .m_arburst(m_arburst), .m_arcache(m_arcache),
.m_arprot(m_arprot), .m_arvalid(m_arvalid), .m_arready(m_arready), .m_rdata(m_rdata), .m_rresp(m_rresp),
.m_rlast(m_rlast), .m_rvalid(m_rvalid), .m_rready(m_rready),
.m_awaddr(m_awaddr), .m_awlen(m_awlen), .m_awsize(m_awsize), .m_awburst(m_awburst), .m_awcache(m_awcache),
.m_awprot(m_awprot), .m_awvalid(m_awvalid), .m_awready(m_awready), .m_wdata(m_wdata), .m_wstrb(m_wstrb),
.m_wlast(m_wlast), .m_wvalid(m_wvalid), .m_wready(m_wready), .m_bresp(m_bresp), .m_bvalid(m_bvalid), .m_bready(m_bready),
.emio_i(emio_i)
);
// ---- DDR: a memory model behind the high-performance port --------------------------------------
localparam [48:0] BASE = 49'h0_1000_0000;
reg [63:0] mem [0:65535];
reg [48:0] q_addr [0:15];
reg [7:0] q_len [0:15];
reg [4:0] q_head = 0, q_tail = 0;
reg [8:0] beat = 0;
integer seed = 7;
wire [4:0] q_used = q_tail - q_head;
always @(posedge clk) begin
// address channel: accept when there is room, sometimes late
m_arready <= 1'b0;
if (m_arvalid && !m_arready && q_used < 5'd8 && ($random(seed) & 3) != 0) begin
m_arready <= 1'b1;
q_addr[q_tail[3:0]] <= m_araddr;
q_len[q_tail[3:0]] <= m_arlen;
q_tail <= q_tail + 5'd1;
if (m_arsize != 3'd3 || m_arburst != 2'b01) begin $display("DMA: bad size or burst"); $finish; end
if ((m_araddr[11:0] + ({4'd0, m_arlen} + 12'd1) * 12'd8) > 13'h1000 && m_araddr[11:0] != 12'd0) begin
$display("DMA: a burst crosses a 4 KiB page at %h", m_araddr); $finish;
end
end
// data channel: one beat at a time, with gaps
if (m_rvalid && m_rready) begin
m_rvalid <= 1'b0;
if (m_rlast) begin q_head <= q_head + 5'd1; beat <= 0; end
else beat <= beat + 9'd1;
end else if (!m_rvalid && q_used != 5'd0 && ($random(seed) % 5) != 0) begin
m_rvalid <= 1'b1;
m_rdata <= mem[(q_addr[q_head[3:0]] - BASE) / 8 + beat];
m_rlast <= (beat == {1'b0, q_len[q_head[3:0]]});
m_rresp <= 2'b00;
end
end
// ---- the write side of the memory model: one burst at a time, with gaps ------------------------
localparam [48:0] WB_BASE = 49'h0_2000_0000;
reg [63:0] wmem [0:4095];
reg [48:0] w_addr = 0;
reg w_open = 0;
always @(posedge clk) begin
m_awready <= 1'b0;
m_wready <= 1'b0;
if (m_bvalid && m_bready) m_bvalid <= 1'b0;
if (m_awvalid && !m_awready && !w_open && ($random(seed) & 1) != 0) begin
m_awready <= 1'b1;
w_addr <= m_awaddr;
w_open <= 1'b1;
if (m_awsize != 3'd3 || m_awburst != 2'b01) begin $display("write-back: bad size or burst"); $finish; end
if ({1'b0, m_awaddr[11:0]} + ({5'd0, m_awlen} + 13'd1) * 13'd8 > 13'h1000) begin
$display("write-back: a burst crosses a 4 KiB page at %h", m_awaddr); $finish;
end
end
if (m_wvalid && !m_wready && w_open && ($random(seed) & 3) != 0) m_wready <= 1'b1;
if (m_wvalid && m_wready) begin
if (m_wstrb != 8'hFF) begin $display("write-back: bad strobes"); $finish; end
wmem[(w_addr - WB_BASE) / 8] <= m_wdata;
w_addr <= w_addr + 49'd8;
if (m_wlast) begin w_open <= 1'b0; m_bvalid <= 1'b1; m_bresp <= 2'b00; end
end
end
// ---- AXI-Lite master tasks --------------------------------------------------------------------
// The master drives just after a clock edge and samples the slave's outputs at the edge, as
// they were before it: a handshake completes on the edge where valid and ready were both high.
integer line, t;
reg a_ok, w_ok, done_hs;
task axi_write(input [15:0] word, input [31:0] d);
begin
@(posedge clk); #1;
awaddr = 32'hA000_0000 | ({16'd0, word} << 2); wdata = d; awvalid = 1; wvalid = 1;
t = 0;
while (awvalid || wvalid) begin
@(posedge clk);
a_ok = awready; w_ok = wready;
#1;
if (a_ok) awvalid = 0;
if (w_ok) wvalid = 0;
t = t + 1;
if (t > 1000) begin $display("TIMEOUT write %h at line %0d", word, line); $finish; end
end
bready = 1;
done_hs = 0; t = 0;
while (!done_hs) begin
@(posedge clk);
done_hs = bvalid;
#1;
t = t + 1;
if (t > 1000) begin $display("TIMEOUT b at line %0d", line); $finish; end
end
bready = 0;
end
endtask
task axi_read(input [15:0] word, output [31:0] d);
begin
@(posedge clk); #1;
araddr = 32'hA000_0000 | ({16'd0, word} << 2); arvalid = 1;
t = 0;
while (arvalid) begin
@(posedge clk);
a_ok = arready;
#1;
if (a_ok) arvalid = 0;
t = t + 1;
if (t > 1000) begin $display("TIMEOUT ar %h at line %0d", word, line); $finish; end
end
rready = 1;
done_hs = 0; t = 0;
while (!done_hs) begin
@(posedge clk);
done_hs = rvalid;
d = rdata;
#1;
t = t + 1;
if (t > 1000) begin $display("TIMEOUT r at line %0d", line); $finish; end
end
rready = 0;
end
endtask
integer fd, r, errors, reads, k, n, i, polls, longest;
reg [31:0] op, a, d, m, got, word;
reg [63:0] ent_a, ent_d;
initial begin
errors = 0; reads = 0; line = 0; longest = 0;
repeat (20) @(posedge clk);
rstn = 1;
repeat (5) @(posedge clk);
if (emio_i[7:0] !== 8'hA5) begin $display("no signature"); $finish; end
axi_read(16'h8000, got);
if (got !== 32'h4C415849) begin $display("wrapper ID %h", got); $finish; end
fd = $fopen("landscape_axi_tb.script", "r");
if (fd == 0) begin $display("no script"); $finish; end
r = $fscanf(fd, "%s", op);
while (r == 1) begin
line = line + 1;
if (op == "W") begin
r = $fscanf(fd, " %h %h", a, d);
axi_write(a[15:0], d);
end else if (op == "R") begin
r = $fscanf(fd, " %h %h %h", a, d, m);
axi_read(a[15:0], got);
reads = reads + 1;
if ((got & m) !== (d & m)) begin
errors = errors + 1;
if (errors <= 25) $display("MISMATCH line %0d: R %h got %h want %h", line, a[15:0], got, d);
end
end else if (op == "I") begin
got = 1; polls = 0;
while (got[0]) begin
axi_read(16'h0002, got);
polls = polls + 1;
if (polls > 200000) begin $display("TIMEOUT busy at line %0d", line); $finish; end
end
end else if (op == "D") begin
r = $fscanf(fd, " %d", n);
for (k = 0; k < n; k = k + 1) begin
r = $fscanf(fd, " %h %h", ent_a, ent_d);
mem[k] = {16'd0, ent_a[15:0], ent_d[31:0]};
line = line + 1;
end
axi_write(16'h8002, BASE[31:0]);
axi_write(16'h8003, {15'd0, BASE[48:32]});
axi_write(16'h8004, n);
axi_write(16'h8005, 1);
got = 2; polls = 0;
while (got[1]) begin
axi_read(16'h8001, got);
polls = polls + 1;
if (polls > 200000) begin $display("TIMEOUT DMA at line %0d", line); $finish; end
end
if (got[2]) begin $display("DMA error at line %0d", line); errors = errors + 1; end
end else if (op == "Q") begin
r = $fscanf(fd, " %d", n);
axi_write(16'h8010, n);
axi_write(16'h8011, 1);
// an engine access while the sequencer holds the engine: answered at once, dropped
axi_read(16'h0000, got);
if (got !== 32'hDEADBEEF) begin $display("an engine read during a batch returned %h at line %0d", got, line); errors = errors + 1; end
axi_read(16'h8008, got);
if (got !== 32'd1) begin $display("DROPS %0d after one dropped read, line %0d", got, line); errors = errors + 1; end
axi_write(16'h8008, 0);
got = 1; polls = 0;
while (got[0]) begin
axi_read(16'h8001, got);
polls = polls + 1;
if (polls > 2000000) begin $display("TIMEOUT sequencer at line %0d", line); $finish; end
end
if (polls > longest) longest = polls;
end else if (op == "K") begin
// the results of the last batch, written back: n chains, six words each, to check
r = $fscanf(fd, " %d", n);
axi_read(16'h8025, d);
axi_write(16'h8020, WB_BASE[31:0]);
axi_write(16'h8021, {15'd0, WB_BASE[48:32]});
axi_write(16'h8022, 0);
axi_write(16'h8023, n);
axi_write(16'h8024, 1);
got = 8; polls = 0;
while (got[3]) begin
axi_read(16'h8001, got);
polls = polls + 1;
if (polls > 200000) begin $display("TIMEOUT write-back at line %0d", line); $finish; end
end
if (got[4]) begin $display("write-back error at line %0d", line); errors = errors + 1; end
axi_read(16'h8025, got);
if (got !== d + 1) begin $display("write-back did not run at line %0d", line); errors = errors + 1; end
for (k = 0; k < n; k = k + 1) begin
for (i = 0; i < 6; i = i + 1) begin
r = $fscanf(fd, " %h", d);
line = line + 1;
word = (i % 2) ? wmem[k * 4 + i / 2][63:32] : wmem[k * 4 + i / 2][31:0];
reads = reads + 1;
if (word !== d) begin
errors = errors + 1;
if (errors <= 25) $display("MISMATCH line %0d: written back chain %0d word %0d got %h want %h", line, k, i, word, d);
end
end
end
end else begin
$display("bad op at line %0d", line);
$finish;
end
r = $fscanf(fd, "%s", op);
end
$display("TB DONE: %0d lines, %0d checked reads, %0d mismatches, sim time %0t", line, reads, errors, $time);
$finish;
end
endmodule