open-standards/wai/wai-quantum-landscape-fpga/rtl/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

364 lines
19 KiB
Verilog

// SPDX-License-Identifier: Apache-2.0
// The engine's link to the processor: an AXI-Lite register port, a write-list DMA from DDR, and a
// chain sequencer that runs a batch of chains in fabric.
//
// AXI-Lite (from the processor's full-power-domain master, through a width and a protocol
// converter): word address = byte address >> 2. Words 0x0000-0x7FFF are the engine's own map (src/regs.rs);
// 0x8000 and up are this wrapper's (src/axi.rs). Every access is answered within a few clocks,
// whatever the engine, the DMA or the sequencer is doing: an access that never completes would
// hold the processor's bus, and on a shared, remote board nobody is there to power-cycle it. So
// an engine-map access while the DMA or the sequencer holds the engine's bus is not queued: a
// write is dropped, a read returns 0xDEADBEEF, and either counts in DROPS, which the host checks.
//
// Write-list DMA (an AXI4 read master, through a width converter, into the processor's
// high-performance port): the host puts
// a list of 64-bit entries in DDR, {16'b0, addr[15:0], data[31:0]}, and the wrapper applies each
// as a register write, in order, one every two clocks (the engine needs a clock between a
// coupling-memory clear and the next write). An entry for the chain RAM (0x9000-0x97FF) is
// written there. The list must start on a 128-byte boundary and not cross a 4 KiB page: bursts
// are 16 beats of 8 bytes. A read error stops the applying (DMA_ERR) but the list is still
// drained, so DMA_BUSY always falls.
//
// Chain sequencer: for k in 0..SEQ_COUNT (at most 256), it writes chain k's six words from the
// chain RAM (BOUNDARY 0-3, X0_LO, X0_HI) into the engine, starts the job, waits for it, and copies
// STATE 0-3, STEPS and STATUS into the result RAM (0xA000 + 8k). The job's mode, bound, table and
// workload are what the host set before. The anneal loop runs in fabric; the processor only fills
// the chain RAM and collects the results.
//
// Result write-back (an AXI4 write master on the same port): WB_CHAINS chains of the result RAM from
// WB_FIRST, 32 bytes a chain (its eight words), go to DDR at WB_LO/HI in bursts of 16 beats, one
// burst outstanding. The address must be on a 128-byte boundary and the chains must not cross a 4 KiB
// page, 128 chains a page. A write error is held in WB_ERR; every burst is still sent.
//
// EMIO: eight input lines carry a constant signature (0xA5) that the host reads through the
// processor's GPIO controller before it touches an AXI address: a GPIO read cannot hang the bus,
// so a design replaced by another session's is caught before an AXI access could go unanswered.
`timescale 1ns/1ps
`default_nettype none
module landscape_axi (
input wire clk,
input wire rstn,
// AXI4-Lite slave
input wire [31:0] s_awaddr,
input wire s_awvalid,
output reg s_awready = 1'b0,
input wire [31:0] s_wdata,
input wire s_wvalid,
output reg s_wready = 1'b0,
output wire [1:0] s_bresp,
output reg s_bvalid = 1'b0,
input wire s_bready,
input wire [31:0] s_araddr,
input wire s_arvalid,
output reg s_arready = 1'b0,
output reg [31:0] s_rdata = 32'd0,
output wire [1:0] s_rresp,
output reg s_rvalid = 1'b0,
input wire s_rready,
// AXI4 read master (64-bit data)
output reg [48:0] m_araddr = 49'd0,
output wire [7:0] m_arlen,
output wire [2:0] m_arsize,
output wire [1:0] m_arburst,
output wire [3:0] m_arcache,
output wire [2:0] m_arprot,
output reg m_arvalid = 1'b0,
input wire m_arready,
input wire [63:0] m_rdata,
input wire [1:0] m_rresp,
input wire m_rlast,
input wire m_rvalid,
output wire m_rready,
// AXI4 write master (64-bit data)
output reg [48:0] m_awaddr = 49'd0,
output reg [7:0] m_awlen = 8'd0,
output wire [2:0] m_awsize,
output wire [1:0] m_awburst,
output wire [3:0] m_awcache,
output wire [2:0] m_awprot,
output reg m_awvalid = 1'b0,
input wire m_awready,
output reg [63:0] m_wdata = 64'd0,
output wire [7:0] m_wstrb,
output reg m_wlast = 1'b0,
output reg m_wvalid = 1'b0,
input wire m_wready,
input wire [1:0] m_bresp,
input wire m_bvalid,
output wire m_bready,
// EMIO signature
output wire [94:0] emio_i
);
localparam [31:0] LAXI = 32'h4C41_5849; // "LAXI"
localparam [31:0] WRAP_VERSION = 32'h0000_0002;
assign s_bresp = 2'b00;
assign s_rresp = 2'b00;
assign emio_i = {87'd0, 8'hA5};
// ---- the engine's register bus ------------------------------------------------------------
reg stb = 1'b0, we = 1'b0;
reg [15:0] addr = 16'd0;
reg [31:0] wdata = 32'd0;
wire [31:0] rdata;
wire busy, done;
landscape_core core (.clk(clk), .reg_stb(stb), .reg_we(we), .reg_addr(addr), .reg_wdata(wdata),
.reg_rdata(rdata), .busy_o(busy), .done_o(done));
// ---- wrapper state ------------------------------------------------------------------------
reg [31:0] dma_lo = 32'd0, dma_hi = 32'd0, dma_count = 32'd0;
reg [31:0] dma_left = 32'd0, dma_reqs = 32'd0; // entries still to receive, to request
reg dma_busy = 1'b0, dma_err = 1'b0;
reg [31:0] dma_done_count = 32'd0, drops = 32'd0;
reg [8:0] seq_count = 9'd0;
reg [15:0] seq_k = 16'd0, seq_done_count = 16'd0;
reg seq_busy = 1'b0;
reg [3:0] seq_ph = 4'd0;
reg [2:0] seq_i = 3'd0;
reg [3:0] seq_wait = 4'd0;
wire bus_free = !dma_busy && !seq_busy;
reg [31:0] wb_lo = 32'd0, wb_hi = 32'd0, wb_done_count = 32'd0;
reg [7:0] wb_first = 8'd0;
reg [8:0] wb_chains = 9'd0;
reg wb_busy = 1'b0, wb_err = 1'b0;
reg [2:0] wb_ph = 3'd0; // 0 address, 1-2 the two words of a beat, 3 data, 4 response
reg [10:0] wb_ra = 11'd0; // the result RAM word being fetched
reg [10:0] wb_beats = 11'd0; // beats still to send
reg [4:0] wb_left = 5'd0; // beats left in this burst
reg [31:0] wb_word = 32'd0;
// ---- the chain RAM and the result RAM: 256 chains x 8 words each, one write port each -------
reg [31:0] chain_ram [0:2047];
reg [31:0] res_ram [0:2047];
reg cr_we = 1'b0, rr_we = 1'b0;
reg [10:0] cr_wa = 11'd0, rr_wa = 11'd0;
reg [31:0] cr_wd = 32'd0, rr_wd = 32'd0;
reg [31:0] chain_q = 32'd0, res_q = 32'd0;
reg [15:0] ar_word = 16'd0;
always @(posedge clk) begin
if (cr_we) chain_ram[cr_wa] <= cr_wd;
chain_q <= chain_ram[{seq_k[7:0], seq_i}];
end
always @(posedge clk) begin
if (rr_we) res_ram[rr_wa] <= rr_wd;
res_q <= res_ram[wb_busy ? wb_ra : ar_word[10:0]];
end
// ---- AXI-Lite capture ---------------------------------------------------------------------
reg aw_have = 1'b0, w_have = 1'b0, ar_have = 1'b0;
reg [15:0] aw_word = 16'd0;
reg [31:0] w_val = 32'd0;
reg [2:0] host_ph = 3'd0; // 0 idle, 2 engine read issued, 3 engine data next, 4 wrapper data, 5 result RAM data
reg [31:0] wrap_rdata = 32'd0;
// ---- the DMA's read master ----------------------------------------------------------------
assign m_arsize = 3'd3; // 8 bytes a beat
assign m_arburst = 2'b01; // INCR
assign m_arcache = 4'b0011; // normal, bufferable, not coherent: the host cleans the list to DDR
assign m_arprot = 3'b000;
reg [7:0] burst_len = 8'd0;
assign m_arlen = burst_len;
reg ent_have = 1'b0; // an entry waiting to be applied, on the next clock
reg [63:0] ent = 64'd0;
assign m_rready = !ent_have;
// ---- the write-back's write master ---------------------------------------------------------
assign m_awsize = 3'd3;
assign m_awburst = 2'b01;
assign m_awcache = 4'b0011;
assign m_awprot = 3'b000;
assign m_wstrb = 8'hFF;
assign m_bready = 1'b1;
always @(posedge clk) begin
stb <= 1'b0;
we <= 1'b0;
cr_we <= 1'b0;
rr_we <= 1'b0;
s_awready <= 1'b0; s_wready <= 1'b0; s_arready <= 1'b0;
if (!rstn) begin
dma_busy <= 1'b0; seq_busy <= 1'b0; m_arvalid <= 1'b0; ent_have <= 1'b0;
wb_busy <= 1'b0; m_awvalid <= 1'b0; m_wvalid <= 1'b0;
s_bvalid <= 1'b0; s_rvalid <= 1'b0; aw_have <= 1'b0; w_have <= 1'b0; ar_have <= 1'b0; host_ph <= 3'd0;
end else begin
// capture the host's access: an address and its data, or a read address
if (s_awvalid && !aw_have && !s_awready) begin s_awready <= 1'b1; aw_have <= 1'b1; aw_word <= s_awaddr[17:2]; end
if (s_wvalid && !w_have && !s_wready) begin s_wready <= 1'b1; w_have <= 1'b1; w_val <= s_wdata; end
if (s_arvalid && !ar_have && !s_arready && host_ph == 3'd0) begin s_arready <= 1'b1; ar_have <= 1'b1; ar_word <= s_araddr[17:2]; end
if (s_bvalid && s_bready) s_bvalid <= 1'b0;
if (s_rvalid && s_rready) s_rvalid <= 1'b0;
// ---- the DMA: requests, beats, entries ----
if (dma_busy && !m_arvalid && dma_reqs != 32'd0) begin
burst_len <= (dma_reqs > 32'd16) ? 8'd15 : dma_reqs[7:0] - 8'd1;
m_arvalid <= 1'b1;
end else if (m_arvalid && m_arready) begin
m_arvalid <= 1'b0;
m_araddr <= m_araddr + ({41'd0, burst_len} + 49'd1) * 49'd8;
dma_reqs <= dma_reqs - ({24'd0, burst_len} + 32'd1);
end
if (m_rvalid && m_rready) begin
dma_left <= dma_left - 32'd1;
if (m_rresp != 2'b00) dma_err <= 1'b1;
else if (!dma_err) begin ent <= m_rdata; ent_have <= 1'b1; end
end
if (dma_busy && dma_left == 32'd0 && dma_reqs == 32'd0 && !m_arvalid && !ent_have) begin
dma_busy <= 1'b0;
dma_done_count <= dma_done_count + 32'd1;
end
if (ent_have) begin
ent_have <= 1'b0;
if (ent[47:32] < 16'h8000) begin
stb <= 1'b1; we <= 1'b1; addr <= ent[47:32]; wdata <= ent[31:0];
end else if (ent[47:32] >= 16'h9000 && ent[47:32] < 16'h9800) begin
cr_we <= 1'b1; cr_wa <= ent[42:32]; cr_wd <= ent[31:0];
end
end else if (seq_busy) begin
// per chain: six writes, a start, a wait, six reads into the result RAM
case (seq_ph)
4'd0: seq_ph <= 4'd1; // chain_q follows {seq_k, seq_i}
4'd1: begin
stb <= 1'b1; we <= 1'b1; wdata <= chain_q;
addr <= (seq_i < 3'd4) ? (16'h0100 + {13'd0, seq_i}) : (16'h0010 + {13'd0, seq_i - 3'd4});
if (seq_i == 3'd5) begin seq_i <= 3'd0; seq_ph <= 4'd2; end
else begin seq_i <= seq_i + 3'd1; seq_ph <= 4'd0; end
end
4'd2: begin stb <= 1'b1; we <= 1'b1; addr <= 16'h0003; wdata <= 32'd1; seq_wait <= 4'd3; seq_ph <= 4'd3; end
4'd3: begin
if (seq_wait != 4'd0) seq_wait <= seq_wait - 4'd1;
else if (!busy) begin seq_i <= 3'd0; seq_ph <= 4'd4; end
end
4'd4: begin
stb <= 1'b1; we <= 1'b0;
addr <= (seq_i < 3'd4) ? (16'h0110 + {13'd0, seq_i}) : ((seq_i == 3'd4) ? 16'h0028 : 16'h0002);
seq_ph <= 4'd5;
end
4'd5: seq_ph <= 4'd6; // the engine registers its read data on the clock after stb
4'd6: begin
rr_we <= 1'b1; rr_wa <= {seq_k[7:0], seq_i}; rr_wd <= rdata;
if (seq_i == 3'd5) begin
seq_i <= 3'd0;
seq_done_count <= seq_done_count + 16'd1;
if (seq_k + 16'd1 == {7'd0, seq_count}) seq_busy <= 1'b0;
seq_k <= seq_k + 16'd1;
seq_ph <= 4'd0;
end else begin seq_i <= seq_i + 3'd1; seq_ph <= 4'd4; end
end
default: seq_ph <= 4'd0;
endcase
end
// ---- the write-back: per burst an address, then per beat two words, then the response ----
if (wb_busy) begin
case (wb_ph)
3'd0: begin
if (!m_awvalid) begin
m_awvalid <= 1'b1;
m_awlen <= (wb_beats > 11'd16) ? 8'd15 : wb_beats[7:0] - 8'd1;
wb_left <= (wb_beats > 11'd16) ? 5'd16 : wb_beats[4:0];
end else if (m_awready) begin
m_awvalid <= 1'b0;
m_awaddr <= m_awaddr + ({41'd0, m_awlen} + 49'd1) * 49'd8;
wb_ph <= 3'd1;
end
end
3'd1: begin wb_ra <= wb_ra + 11'd1; wb_ph <= 3'd2; end // res_q follows wb_ra
3'd2: begin wb_word <= res_q; wb_ph <= 3'd5; end
3'd5: begin // the high word's turn
m_wdata <= {res_q, wb_word};
m_wvalid <= 1'b1;
m_wlast <= (wb_left == 5'd1);
wb_ra <= wb_ra + 11'd1;
wb_ph <= 3'd3;
end
3'd3: if (m_wready) begin
m_wvalid <= 1'b0;
wb_left <= wb_left - 5'd1;
wb_beats <= wb_beats - 11'd1;
wb_ph <= (wb_left == 5'd1) ? 3'd4 : 3'd1;
end
3'd4: if (m_bvalid) begin
if (m_bresp != 2'b00) wb_err <= 1'b1;
if (wb_beats == 11'd0) begin
wb_busy <= 1'b0;
wb_done_count <= wb_done_count + 32'd1;
end
wb_ph <= 3'd0;
end
default: wb_ph <= 3'd0;
endcase
end
// ---- the host ----
if (aw_have && w_have && host_ph == 3'd0 && !s_bvalid && !ent_have) begin
if (aw_word < 16'h8000) begin
if (bus_free) begin stb <= 1'b1; we <= 1'b1; addr <= aw_word; wdata <= w_val; end
else drops <= drops + 32'd1;
end else begin
case (aw_word)
16'h8002: dma_lo <= w_val;
16'h8003: dma_hi <= w_val;
16'h8004: dma_count <= w_val;
16'h8005: if (bus_free && dma_count != 32'd0) begin
dma_busy <= 1'b1; dma_err <= 1'b0; dma_left <= dma_count; dma_reqs <= dma_count;
m_araddr <= {dma_hi[16:0], dma_lo};
end
16'h8008: drops <= 32'd0;
16'h8010: seq_count <= (w_val > 32'd256) ? 9'd256 : w_val[8:0];
16'h8011: if (bus_free && !wb_busy && seq_count != 9'd0) begin
seq_busy <= 1'b1; seq_k <= 16'd0; seq_ph <= 4'd0; seq_i <= 3'd0;
end
16'h8020: wb_lo <= w_val;
16'h8021: wb_hi <= w_val;
16'h8022: wb_first <= w_val[7:0];
16'h8023: wb_chains <= (w_val > 32'd256) ? 9'd256 : w_val[8:0];
16'h8024: if (bus_free && !wb_busy && wb_chains != 9'd0) begin
wb_busy <= 1'b1; wb_err <= 1'b0; wb_ph <= 3'd0;
wb_ra <= {wb_first, 3'd0};
wb_beats <= {wb_chains, 2'b00};
m_awaddr <= {wb_hi[16:0], wb_lo};
end
16'h8013: if (seq_busy) begin
// abandon the batch and stop the engine's job
seq_busy <= 1'b0;
stb <= 1'b1; we <= 1'b1; addr <= 16'h0003; wdata <= 32'd4;
end
default: if (aw_word >= 16'h9000 && aw_word < 16'h9800) begin
cr_we <= 1'b1; cr_wa <= aw_word[10:0]; cr_wd <= w_val;
end
endcase
end
aw_have <= 1'b0; w_have <= 1'b0; s_bvalid <= 1'b1;
end else if (ar_have && host_ph == 3'd0 && !s_rvalid) begin
if (ar_word < 16'h8000) begin
if (bus_free) begin stb <= 1'b1; we <= 1'b0; addr <= ar_word; host_ph <= 3'd2; end
else begin drops <= drops + 32'd1; wrap_rdata <= 32'hDEAD_BEEF; host_ph <= 3'd4; end
end else begin
case (ar_word)
16'h8000: wrap_rdata <= LAXI;
16'h8001: wrap_rdata <= {27'd0, wb_err, wb_busy, dma_err, dma_busy, seq_busy};
16'h8002: wrap_rdata <= dma_lo;
16'h8003: wrap_rdata <= dma_hi;
16'h8004: wrap_rdata <= dma_count;
16'h8006: wrap_rdata <= dma_done_count;
16'h8007: wrap_rdata <= dma_left;
16'h8008: wrap_rdata <= drops;
16'h8009: wrap_rdata <= WRAP_VERSION;
16'h8010: wrap_rdata <= {23'd0, seq_count};
16'h8012: wrap_rdata <= {16'd0, seq_done_count};
16'h8025: wrap_rdata <= wb_done_count;
default: wrap_rdata <= 32'hDEAD_BEEF;
endcase
host_ph <= (ar_word >= 16'hA000 && ar_word < 16'hA800) ? 3'd5 : 3'd4;
end
end
if (host_ph == 3'd2) host_ph <= 3'd3;
else if (host_ph == 3'd3) begin s_rdata <= rdata; s_rvalid <= 1'b1; ar_have <= 1'b0; host_ph <= 3'd0; end
else if (host_ph == 3'd4) begin s_rdata <= wrap_rdata; s_rvalid <= 1'b1; ar_have <= 1'b0; host_ph <= 3'd0; end
else if (host_ph == 3'd5) begin s_rdata <= res_q; s_rvalid <= 1'b1; ar_have <= 1'b0; host_ph <= 3'd0; end
end
end
endmodule
`default_nettype wire