// 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