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]>
364 lines
19 KiB
Verilog
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
|