diff --git a/src/frontend/inst64/idma_inst64_events.sv b/src/frontend/inst64/idma_inst64_events.sv index 59d0a5ea7..65e1736f0 100644 --- a/src/frontend/inst64/idma_inst64_events.sv +++ b/src/frontend/inst64/idma_inst64_events.sv @@ -72,12 +72,14 @@ module idma_inst64_events #( events_o.r_done = axi_req_i.r_ready && axi_rsp_i.r_valid; events_o.r_bw = axi_req_i.r_ready && axi_rsp_i.r_valid; events_o.r_stall = axi_req_i.r_ready && !axi_rsp_i.r_valid; + events_o.buf_r_stall = !axi_req_i.r_ready && axi_rsp_i.r_valid; // w events_o.w_valid = axi_req_i.w_valid; events_o.w_ready = axi_rsp_i.w_ready; events_o.w_done = axi_rsp_i.w_ready && axi_req_i.w_valid; events_o.w_stall = !axi_rsp_i.w_ready && axi_req_i.w_valid; + events_o.buf_w_stall = axi_rsp_i.w_ready && !axi_req_i.w_valid; if (axi_rsp_i.w_ready && axi_req_i.w_valid) begin events_o.num_bytes_written = num_bytes_written; end diff --git a/test/frontend/idma_inst64_base.sv b/test/frontend/idma_inst64_base.sv index e1607874d..de0cf9ede 100644 --- a/test/frontend/idma_inst64_base.sv +++ b/test/frontend/idma_inst64_base.sv @@ -9,6 +9,8 @@ /// one AXI plus one OBI simulation memory per channel. module idma_inst64_base #( parameter int unsigned DMATracing = idma_inst64_tb_pkg::DMATracing, + /// Stall the AXI R source and W sink in antiphase, see `gen_stall` below + parameter bit StallPattern = 1'b0, /// TCDM (OBI) window; every address outside it decodes to ToSoC, i.e. AXI parameter logic [63:0] TcdmStart = 64'h0000_0000_1000_0000, parameter logic [63:0] TcdmEnd = 64'h0000_0000_1001_0000 @@ -92,6 +94,40 @@ module idma_inst64_base #( //-------------------------------------- // Memory subsystem //-------------------------------------- + axi_req_t [NumChannels-1:0] mem_req; + axi_resp_t [NumChannels-1:0] mem_res; + + // Antiphase R/W stall: the DMA buffer alternately backs up and runs dry. + // A go bit only falls after its channel handshake, so no valid is ever withdrawn. + if (StallPattern) begin : gen_stall + logic [4:0] phase; + logic w_go, r_go; + + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + phase <= '0; + w_go <= 1'b1; + r_go <= 1'b1; + end else begin + phase <= phase + 5'd1; + w_go <= !phase[4] || (w_go && axi_req[0].w_valid && !mem_res[0].w_ready); + r_go <= phase[4] || (r_go && mem_res[0].r_valid && !axi_req[0].r_ready); + end + end + + always_comb begin + mem_req = axi_req; + axi_res = mem_res; + mem_req[0].w_valid = axi_req[0].w_valid && w_go; + mem_req[0].r_ready = axi_req[0].r_ready && r_go; + axi_res[0].w_ready = mem_res[0].w_ready && w_go; + axi_res[0].r_valid = mem_res[0].r_valid && r_go; + end + end else begin : gen_no_stall + assign mem_req = axi_req; + assign axi_res = mem_res; + end + for (genvar c = 0; c < NumChannels; c++) begin : gen_mem_ch axi_sim_mem #( .AddrWidth ( AxiAddrWidth ), @@ -107,8 +143,8 @@ module idma_inst64_base #( ) i_axi_sim_mem ( .clk_i ( clk ), .rst_ni ( rst_n ), - .axi_req_i ( axi_req[c] ), - .axi_rsp_o ( axi_res[c] ), + .axi_req_i ( mem_req[c] ), + .axi_rsp_o ( mem_res[c] ), .mon_w_valid_o ( /* NC */ ), .mon_w_addr_o ( /* NC */ ), .mon_w_data_o ( /* NC */ ), diff --git a/test/frontend/idma_inst64_tb_pkg.sv b/test/frontend/idma_inst64_tb_pkg.sv index 64cc16aa7..a4790c58a 100644 --- a/test/frontend/idma_inst64_tb_pkg.sv +++ b/test/frontend/idma_inst64_tb_pkg.sv @@ -103,8 +103,8 @@ package idma_inst64_tb_pkg; logic ar_valid, ar_ready, ar_done, ar_stall; axi_pkg::len_t ar_len; axi_pkg::size_t ar_size; - logic r_valid, r_ready, r_done, r_bw, r_stall; - logic w_valid, w_ready, w_done, w_stall; + logic r_valid, r_ready, r_done, r_bw, r_stall, buf_r_stall; + logic w_valid, w_ready, w_done, w_stall, buf_w_stall; logic [31:0] num_bytes_written; logic b_valid, b_ready, b_done; logic obi_wr_req, obi_rd_req; diff --git a/test/frontend/tb_idma_inst64_axi_copy.sv b/test/frontend/tb_idma_inst64_axi_copy.sv index a41eecb8d..2911a6181 100644 --- a/test/frontend/tb_idma_inst64_axi_copy.sv +++ b/test/frontend/tb_idma_inst64_axi_copy.sv @@ -12,7 +12,8 @@ module tb_idma_inst64_axi_copy; import idma_inst64_tb_pkg::*; - idma_inst64_base harness (); + // The antiphase R/W stall is what makes the two buffer-stall events assert at all + idma_inst64_base #(.StallPattern(1'b1)) harness (); localparam int unsigned TimeoutCycles = 32'd200000; localparam int unsigned CopySize = 32'd4096; @@ -44,8 +45,8 @@ module tb_idma_inst64_axi_copy; typedef enum int unsigned { EvAwValid, EvAwReady, EvAwDone, EvAwStall, EvAwLen, EvAwSize, EvArValid, EvArReady, EvArDone, EvArStall, EvArLen, EvArSize, - EvRValid, EvRReady, EvRDone, EvRBw, EvRStall, - EvWValid, EvWReady, EvWDone, EvWStall, EvBytes, + EvRValid, EvRReady, EvRDone, EvRBw, EvRStall, EvRBufStall, + EvWValid, EvWReady, EvWDone, EvWStall, EvWBufStall, EvBytes, EvBValid, EvBReady, EvBDone, EvBusy, EvNumFields } ev_field_e; @@ -88,11 +89,15 @@ module tb_idma_inst64_axi_copy; // r_bw duplicates r_done in the RTL; compare it to the pins, never to ev.r_done. ev_field_ok[EvRBw ] = ev.r_bw === (bus_req.r_ready && bus_res.r_valid); ev_field_ok[EvRStall ] = ev.r_stall === (bus_req.r_ready && !bus_res.r_valid); + // Read buffer full: the DUT refuses data the memory is offering + ev_field_ok[EvRBufStall] = ev.buf_r_stall === (!bus_req.r_ready && bus_res.r_valid); ev_field_ok[EvWValid ] = ev.w_valid === bus_req.w_valid; ev_field_ok[EvWReady ] = ev.w_ready === bus_res.w_ready; ev_field_ok[EvWDone ] = ev.w_done === w_hs; ev_field_ok[EvWStall ] = ev.w_stall === (bus_req.w_valid && !bus_res.w_ready); + // Write buffer empty: the memory is ready and the DUT has no beat to give + ev_field_ok[EvWBufStall] = ev.buf_w_stall === (bus_res.w_ready && !bus_req.w_valid); ev_field_ok[EvBytes ] = ev.num_bytes_written === (w_hs ? 32'($countones(bus_req.w.strb)) : 32'd0); @@ -127,6 +132,8 @@ module tb_idma_inst64_axi_copy; int unsigned ev_b_beats = 0; int unsigned ev_busy_cycles = 0; int unsigned ev_bytes_written = 0; + int unsigned ev_buf_r_stalls = 0; + int unsigned ev_buf_w_stalls = 0; axi_pkg::len_t ev_ar_len_seen, ev_aw_len_seen; axi_pkg::size_t ev_ar_size_seen, ev_aw_size_seen; @@ -150,6 +157,8 @@ module tb_idma_inst64_axi_copy; if (ev.w_done) ev_w_beats++; if (ev.b_done) ev_b_beats++; if (ev.dma_busy) ev_busy_cycles++; + if (ev.buf_r_stall) ev_buf_r_stalls++; + if (ev.buf_w_stall) ev_buf_w_stalls++; ev_bytes_written += ev.num_bytes_written; end end @@ -283,16 +292,19 @@ module tb_idma_inst64_axi_copy; ExpAxLen, ExpAxSize); end if (ev_busy_cycles == 0) $fatal(1, "events never reported the DMA busy"); + // The stall pattern forces both, so a zero here means the event is not driven + if (ev_buf_r_stalls == 0) $fatal(1, "events never reported a read buffer stall"); + if (ev_buf_w_stalls == 0) $fatal(1, "events never reported a write buffer stall"); if (errors != 0) $fatal(1, "TEST FAILED: %0d errors", errors); $display("[TB] TEST PASSED: %0d B copied, ar=%0d aw=%0d beats", bytes_checked, axi_ar_beats, axi_aw_beats); $display({"[TB] events cross-checked vs bus pins: %0d fields every cycle; totals ", "ar=%0d aw=%0d b=%0d r=%0d r_bw=%0d w=%0d beats, len=%0d size=%0d, ", - "%0d B written, busy %0d cycles"}, + "%0d B written, busy %0d cycles, buf_r_stall %0d buf_w_stall %0d cycles"}, NumEvFields, ev_ar_beats, ev_aw_beats, ev_b_beats, ev_r_beats, ev_r_bw_beats, ev_w_beats, ev_aw_len_seen, ev_aw_size_seen, - ev_bytes_written, ev_busy_cycles); + ev_bytes_written, ev_busy_cycles, ev_buf_r_stalls, ev_buf_w_stalls); $finish; end