// retroDE_ps2 — tb_iop_dmac_via_map // // IOP-DMAC register lifecycle + data-path integration. Proves: // - IOP can program DMAC ch9 through the real 0x1F80_152x window. // - DMAC fetches real source words from IOP RAM via the map's new // dma_rd_* master port — MADR is a live pointer, stepping by 4. // - Transfer emits DMA_START, per-beat DMA_BEAT with correct // src_addr + remaining-count, and DMA_DONE on the final beat. // - Endpoint ready/valid backpressure stalls mid-stream without any // false completion: busy stays high, no DMA_DONE fires, no beat // events advance while ep_ready is low. // // Chain: // TB (preloads payload into IOP RAM via map CPU write) // TB (programs ch9 via map at 0x1F80_152x) // │ // ▼ // iop_memory_map_stub ← iop_dmac_reg_stub (ch9, reads via dma_rd_*) // │ // ▼ // iop_ram_stub // // TB consumes the DMAC endpoint with a manually-driven ep_ready; the // egress bridge is deliberately not wired here — that's the full-chain // TB's job (tb_sif_ee_landing_via_dmac). Keeping this TB minimal keeps // the lifecycle/stall coverage independent of the landing path. // // Scenarios: // 1. Architectural programming. IOP writes MADR + BCR via map; DMAC // emits EV_DMA_CFG; busy stays low; CHCR[0] stays 0. // 2. Normal run: ep_ready = 1, IOP writes CHCR start (bit 0). DMAC // emits START, then emits one BEAT per accepted word with correct // src_addr, finishes with DONE. CHCR[0] clears. // 3. Stall: start a second transfer with ep_ready = 0. Confirm busy // high, no new DONE, beat count unchanged across a wait window. // 4. Recovery: ep_ready back to 1. Transfer completes. // 5. Readback: MADR/BCR/CHCR read through the map. // // Key trace-visible assertions: // - Map emits events with region=IOP_DMAC (4) on ch9 register // accesses, region=IOP_RAM (2) on DMAC source fetches (master_id=4). // - DMAC trace has the CFG/START/BEAT/DONE sequence with BEAT count // equal to BCR, and src_addr stepping by 4 per beat. `timescale 1ns/1ps module tb_iop_dmac_via_map; localparam int RAM_BYTES = 4 * 1024; localparam int RAM_ADDR_W = $clog2(RAM_BYTES); localparam logic [31:0] DMAC_CH9_BASE = 32'h1F80_1520; localparam logic [31:0] DMAC_MADR_ADDR = DMAC_CH9_BASE | 32'h00; localparam logic [31:0] DMAC_BCR_ADDR = DMAC_CH9_BASE | 32'h04; localparam logic [31:0] DMAC_CHCR_ADDR = DMAC_CH9_BASE | 32'h08; // Source address in IOP RAM for the payload — bit-aligned to a // deterministic offset away from 0 so the TB can tell the right // bytes moved. localparam logic [31:0] SRC_BASE = 32'h0000_0200; localparam int BEATS = 4; // small BCR for TB speed logic clk; logic rst_n; initial clk = 1'b0; always #5 clk = ~clk; // ------------------------------------------------------------------ // IOP map // ------------------------------------------------------------------ logic iop_rd_en; logic [31:0] iop_rd_addr; logic [31:0] iop_rd_data; logic iop_rd_valid; logic iop_wr_en; logic [31:0] iop_wr_addr; logic [31:0] iop_wr_data; logic [3:0] iop_wr_be; logic ram_rd_en; logic [20:0] ram_rd_addr; logic [31:0] ram_rd_data; logic ram_rd_valid; logic ram_wr_en; logic [20:0] ram_wr_addr; logic [31:0] ram_wr_data; logic [3:0] ram_wr_be; logic [7:0] ram_master_id; logic map_dmac_rd_en; logic [3:0] map_dmac_rd_addr; logic [31:0] map_dmac_rd_data; logic map_dmac_rd_valid; logic map_dmac_wr_en; logic [3:0] map_dmac_wr_addr; logic [31:0] map_dmac_wr_data; // DMAC as read master through the map logic dma_rd_en; logic [31:0] dma_rd_addr; logic [7:0] dma_master_id; logic [31:0] dma_rd_data; logic dma_rd_valid; logic map_ev_valid; trace_pkg::subsys_e map_ev_subsys; trace_pkg::event_e map_ev_event; logic [63:0] map_ev_arg0, map_ev_arg1, map_ev_arg2, map_ev_arg3; logic [31:0] map_ev_flags; iop_memory_map_stub u_iop_map ( .clk(clk), .rst_n(rst_n), .iop_rd_en(iop_rd_en), .iop_rd_addr(iop_rd_addr), .iop_rd_data(iop_rd_data), .iop_rd_valid(iop_rd_valid), .iop_wr_en(iop_wr_en), .iop_wr_addr(iop_wr_addr), .iop_wr_data(iop_wr_data), .iop_wr_be(iop_wr_be), .master_id(8'd2), // IOP_CPU // Bridge port — unused .bridge_wr_en(1'b0), .bridge_wr_addr(32'd0), .bridge_wr_data(32'd0), .bridge_wr_be(4'd0), .bridge_master_id(8'd0), // DMA read-master port — DMAC's fetches .dma_rd_en(dma_rd_en), .dma_rd_addr(dma_rd_addr), .dma_master_id(dma_master_id), .dma_rd_data(dma_rd_data), .dma_rd_valid(dma_rd_valid), // SIF register-shell port — unused by this TB .sif_rd_en(), .sif_rd_addr(), .sif_rd_data(32'd0), .sif_rd_valid(1'b0), .sif_wr_en(), .sif_wr_addr(), .sif_wr_data(), // DMAC register-shell port .iop_dmac_rd_en(map_dmac_rd_en), .iop_dmac_rd_addr(map_dmac_rd_addr), .iop_dmac_rd_data(map_dmac_rd_data), .iop_dmac_rd_valid(map_dmac_rd_valid), .iop_dmac_wr_en(map_dmac_wr_en), .iop_dmac_wr_addr(map_dmac_wr_addr), .iop_dmac_wr_data(map_dmac_wr_data), // IOP INTC port — unused by this TB .iop_intc_rd_en(), .iop_intc_rd_addr(), .iop_intc_rd_data(32'd0), .iop_intc_rd_valid(1'b0), .iop_intc_wr_en(), .iop_intc_wr_addr(), .iop_intc_wr_data(), .input_p1(32'd0), .input_p2(32'd0), // BIOS ROM port — unused by this TB .bios_rd_en(), .bios_rd_addr(), .bios_rd_data(32'd0), .bios_rd_valid(1'b0), // RAM downstream .ram_rd_en(ram_rd_en), .ram_rd_addr(ram_rd_addr), .ram_rd_data(ram_rd_data), .ram_rd_valid(ram_rd_valid), .ram_wr_en(ram_wr_en), .ram_wr_addr(ram_wr_addr), .ram_wr_data(ram_wr_data), .ram_wr_be(ram_wr_be), .ram_master_id(ram_master_id), .ev_valid(map_ev_valid), .ev_subsys(map_ev_subsys), .ev_event(map_ev_event), .ev_arg0(map_ev_arg0), .ev_arg1(map_ev_arg1), .ev_arg2(map_ev_arg2), .ev_arg3(map_ev_arg3), .ev_flags(map_ev_flags) ); iop_ram_stub #(.SIZE_BYTES(RAM_BYTES)) u_iop_ram ( .clk(clk), .rst_n(rst_n), .rd_en(ram_rd_en), .rd_addr(ram_rd_addr[RAM_ADDR_W-1:0]), .rd_data(ram_rd_data), .rd_valid(ram_rd_valid), .wr_en(ram_wr_en), .wr_addr(ram_wr_addr[RAM_ADDR_W-1:0]), .wr_data(ram_wr_data), .wr_be(ram_wr_be), .master_id(ram_master_id), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); // ------------------------------------------------------------------ // IOP DMAC (ch9) — TB drives ep_ready directly // ------------------------------------------------------------------ logic ep_valid; logic [31:0] ep_data; logic ep_last; logic ep_ready; logic dmac_busy; logic [31:0] dmac_done_count; logic dmac_ev_valid; trace_pkg::subsys_e dmac_ev_subsys; trace_pkg::event_e dmac_ev_event; logic [63:0] dmac_ev_arg0, dmac_ev_arg1, dmac_ev_arg2, dmac_ev_arg3; logic [31:0] dmac_ev_flags; iop_dmac_reg_stub u_dmac ( .clk(clk), .rst_n(rst_n), .reg_wr_en(map_dmac_wr_en), .reg_rd_en(map_dmac_rd_en), .reg_offset(map_dmac_wr_en ? map_dmac_wr_addr : map_dmac_rd_addr), .reg_wr_data(map_dmac_wr_data), .reg_rd_data(map_dmac_rd_data), .reg_rd_valid(map_dmac_rd_valid), // DMAC → IOP map dma_rd_* .mem_rd_en(dma_rd_en), .mem_rd_addr(dma_rd_addr), .mem_master_id(dma_master_id), .mem_rd_data(dma_rd_data), .mem_rd_valid(dma_rd_valid), // Endpoint (TB drives ep_ready) .ep_valid(ep_valid), .ep_data(ep_data), .ep_last(ep_last), .ep_ready(ep_ready), .irq_completion_o(), .busy_o(dmac_busy), .done_count_o(dmac_done_count), .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys), .ev_event(dmac_ev_event), .ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1), .ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3), .ev_flags(dmac_ev_flags) ); // ------------------------------------------------------------------ // Trace sinks // ------------------------------------------------------------------ trace_sink_stub #(.FILENAME("iop_dmac_via_map.trace"), .SINK_LABEL("iop_map")) u_trace_map (.clk(clk), .rst_n(rst_n), .ev_valid(map_ev_valid), .ev_subsys(map_ev_subsys), .ev_event(map_ev_event), .ev_arg0(map_ev_arg0), .ev_arg1(map_ev_arg1), .ev_arg2(map_ev_arg2), .ev_arg3(map_ev_arg3), .ev_flags(map_ev_flags)); trace_sink_stub #(.FILENAME("iop_dmac_reg.trace"), .SINK_LABEL("iop_dmac")) u_trace_dmac (.clk(clk), .rst_n(rst_n), .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys), .ev_event(dmac_ev_event), .ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1), .ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3), .ev_flags(dmac_ev_flags)); // ------------------------------------------------------------------ // Counters + captured beats // ------------------------------------------------------------------ int map_dmac_events; // map events region=IOP_DMAC (reg accesses) int map_ram_dma_events; // map events region=IOP_RAM + master_id=4 int dmac_cfg_events; int dmac_start_events; int dmac_beat_events; int dmac_done_events; int stall_cycles; int errors; logic [31:0] captured_beats [0:31]; int beat_capture_count; initial begin map_dmac_events = 0; map_ram_dma_events = 0; dmac_cfg_events = 0; dmac_start_events = 0; dmac_beat_events = 0; dmac_done_events = 0; stall_cycles = 0; beat_capture_count = 0; errors = 0; for (int i = 0; i < 32; i++) captured_beats[i] = 32'd0; end always_ff @(posedge clk) begin if (rst_n && map_ev_valid && map_ev_subsys == trace_pkg::SUBSYS_IOP) begin if (map_ev_arg3[7:0] == 8'd4) map_dmac_events <= map_dmac_events + 1; if (map_ev_arg3[7:0] == 8'd2 && map_ev_arg2[7:0] == 8'd4) map_ram_dma_events <= map_ram_dma_events + 1; end if (rst_n && dmac_ev_valid && dmac_ev_subsys == trace_pkg::SUBSYS_DMAC) begin case (dmac_ev_event) trace_pkg::EV_DMA_CFG: dmac_cfg_events <= dmac_cfg_events + 1; trace_pkg::EV_DMA_START: dmac_start_events <= dmac_start_events + 1; trace_pkg::EV_DMA_BEAT: dmac_beat_events <= dmac_beat_events + 1; trace_pkg::EV_DMA_DONE: dmac_done_events <= dmac_done_events + 1; default: ; endcase end if (rst_n && dmac_busy && !ep_ready) stall_cycles <= stall_cycles + 1; // Capture beats as they're accepted at the endpoint. if (rst_n && ep_valid && ep_ready && beat_capture_count < 32) begin captured_beats[beat_capture_count] <= ep_data; beat_capture_count <= beat_capture_count + 1; end end // ------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------ task automatic iop_write(input logic [31:0] addr, input logic [31:0] data); @(negedge clk); iop_wr_en = 1'b1; iop_wr_addr = addr; iop_wr_data = data; iop_wr_be = 4'b1111; @(negedge clk); iop_wr_en = 1'b0; iop_wr_addr = 32'd0; iop_wr_data = 32'd0; iop_wr_be = 4'd0; endtask task automatic iop_read_expect(input logic [31:0] addr, input logic [31:0] expected, input string label); @(negedge clk); iop_rd_en = 1'b1; iop_rd_addr = addr; @(negedge clk); iop_rd_en = 1'b0; iop_rd_addr = 32'd0; if (iop_rd_data !== expected || iop_rd_valid !== 1'b1) begin $error("[tb_iop_dmac_via_map] IOP read %s at 0x%08h: got 0x%08h valid=%0b expected 0x%08h", label, addr, iop_rd_data, iop_rd_valid, expected); errors = errors + 1; end endtask // ------------------------------------------------------------------ // Stimulus // ------------------------------------------------------------------ logic [31:0] payload [0:BEATS-1]; int prev_done_count; int prev_beat_count; int stall_observed; initial begin rst_n = 1'b0; iop_rd_en = 1'b0; iop_rd_addr = 32'd0; iop_wr_en = 1'b0; iop_wr_addr = 32'd0; iop_wr_data = 32'd0; iop_wr_be = 4'd0; ep_ready = 1'b1; prev_done_count = 0; prev_beat_count = 0; stall_observed = 0; // Four distinctive words so beat capture is unambiguous. payload[0] = 32'hA5A5_0000; payload[1] = 32'hDEAD_BEEF; payload[2] = 32'hCAFE_F00D; payload[3] = 32'h1234_5678; repeat (4) @(posedge clk); rst_n = 1'b1; repeat (2) @(posedge clk); // -------------------------------------------------------------- // Preload: TB writes the payload words into IOP RAM through the // CPU-side port of the map. Uses the RAM region (phys 0x00...), // 4-byte stride starting at SRC_BASE. // -------------------------------------------------------------- for (int i = 0; i < BEATS; i++) begin iop_write(SRC_BASE + (i << 2), payload[i]); end // -------------------------------------------------------------- // Scenario 1: architectural programming // -------------------------------------------------------------- iop_write(DMAC_MADR_ADDR, SRC_BASE); iop_write(DMAC_BCR_ADDR, 32'(BEATS)); if (dmac_busy !== 1'b0) begin $error("[tb_iop_dmac_via_map] unexpected busy after programming (no start)"); errors = errors + 1; end if (dmac_done_count != 0) begin $error("[tb_iop_dmac_via_map] done_count nonzero before any start: %0d", dmac_done_count); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 2: normal flow with ep_ready high throughout // -------------------------------------------------------------- ep_ready = 1'b1; iop_write(DMAC_CHCR_ADDR, 32'h0000_0001); // Allow enough cycles for the full transfer: each beat takes at // least 2 cycles (fetch + send), plus a few cycles of startup + // wind-down. 6*BEATS is generous. repeat (6 * BEATS) @(posedge clk); if (dmac_done_count != 1) begin $error("[tb_iop_dmac_via_map] done_count expected 1 after normal flow, got %0d", dmac_done_count); errors = errors + 1; end if (beat_capture_count < BEATS) begin $error("[tb_iop_dmac_via_map] captured %0d beats, expected %0d", beat_capture_count, BEATS); errors = errors + 1; end // Payload order check for (int i = 0; i < BEATS; i++) begin if (captured_beats[i] !== payload[i]) begin $error("[tb_iop_dmac_via_map] beat[%0d] got 0x%08h expected 0x%08h", i, captured_beats[i], payload[i]); errors = errors + 1; end end // -------------------------------------------------------------- // Scenario 3: mid-stream stall // -------------------------------------------------------------- prev_done_count = dmac_done_count; prev_beat_count = dmac_beat_events; // Re-program same source+length (transfer already cleared CHCR.0). iop_write(DMAC_MADR_ADDR, SRC_BASE); iop_write(DMAC_BCR_ADDR, 32'(BEATS)); // Start with ep_ready low from the beginning. ep_ready = 1'b0; iop_write(DMAC_CHCR_ADDR, 32'h0000_0001); // Wait window with backpressure held. DMAC should reach // ACTIVE_SEND and wait there — busy high, no DONE. repeat (10) @(posedge clk); if (dmac_busy !== 1'b1) begin $error("[tb_iop_dmac_via_map] busy should be high during mid-stream stall"); errors = errors + 1; end if (dmac_done_count != prev_done_count) begin $error("[tb_iop_dmac_via_map] DMA_DONE fired during stall: %0d→%0d", prev_done_count, dmac_done_count); errors = errors + 1; end stall_observed = stall_cycles; if (stall_observed < 5) begin $error("[tb_iop_dmac_via_map] expected >=5 stall cycles, got %0d", stall_observed); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 4: recovery // -------------------------------------------------------------- ep_ready = 1'b1; repeat (6 * BEATS) @(posedge clk); if (dmac_done_count != 2) begin $error("[tb_iop_dmac_via_map] done_count expected 2 after recovery, got %0d", dmac_done_count); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 5: readback via map // -------------------------------------------------------------- iop_read_expect(DMAC_MADR_ADDR, SRC_BASE, "MADR readback"); iop_read_expect(DMAC_BCR_ADDR, 32'(BEATS), "BCR readback"); iop_read_expect(DMAC_CHCR_ADDR, 32'h0000_0000, "CHCR readback (start cleared)"); repeat (4) @(posedge clk); // -------------------------------------------------------------- $display("[tb_iop_dmac_via_map] map_dmac=%0d map_ram_dma=%0d cfg=%0d start=%0d beat=%0d done=%0d stall_cy=%0d errors=%0d", map_dmac_events, map_ram_dma_events, dmac_cfg_events, dmac_start_events, dmac_beat_events, dmac_done_events, stall_observed, errors); // Two transfers × BEATS beats each = 2*BEATS expected DMA_BEAT events if (dmac_beat_events != 2 * BEATS) $error("expected %0d EV_DMA_BEAT events, got %0d", 2 * BEATS, dmac_beat_events); if (dmac_start_events != 2) $error("expected 2 EV_DMA_START, got %0d", dmac_start_events); if (dmac_done_events != 2) $error("expected 2 EV_DMA_DONE, got %0d", dmac_done_events); if (map_ram_dma_events < BEATS) $error("expected >=%0d map RAM reads by DMA master, got %0d", BEATS, map_ram_dma_events); if (errors == 0 && dmac_beat_events == 2 * BEATS && dmac_start_events == 2 && dmac_done_events == 2 && map_ram_dma_events >= BEATS) $display("[tb_iop_dmac_via_map] PASS"); else $display("[tb_iop_dmac_via_map] FAIL"); $finish; end initial begin #200000; $error("[tb_iop_dmac_via_map] timeout"); $finish; end endmodule : tb_iop_dmac_via_map