// retroDE_ps2 — tb_iop_dmac_intc // // First IOP-side interrupt flow. Proves: // - IOP DMAC ch9 completion surfaces into an IOP-local INTC via a // one-cycle pulse, latching the pending bit. // - The INTC is reached through the real IOP map at 0x1F80_1070 // (PS2-faithful IOP INTC placement). Both the doorbell-check read // and the W1C ack write go through the map. // - Mask / pending / clear semantics hold, including the "no false // reassertion after clear unless a new transfer" invariant. // // Chain: // iop_ram_stub (source, preloaded via CPU map writes) // └─► iop_memory_map_stub (dma_rd_* for source, CPU for registers) // └─► iop_dmac_reg_stub (ch9) — irq_completion_o pulse // │ // ▼ // intc_stub (IOP instance) — bit 0 = DMAC ch9 completion // // TB (as IOP CPU) reaches INTC_STAT / INTC_MASK through the real // IOP map at: // 0x1F80_1070 INTC_STAT (W1C on write) // 0x1F80_1080 INTC_MASK (plain write) // // DMAC endpoint drained by TB-held ep_ready=1 (no bridge for this // focused TB; payload on the floor). // // Scenarios: // 1. Pre-transfer quiescence: INTC_STAT reads 0, cpu_irq = 0. // 2. First transfer: program MADR/BCR/CHCR, wait for DONE. // INTC_STAT[0] reads 1 through the map; cpu_irq still 0 (mask=0). // 3. Mask on via map: cpu_irq goes 1. // 4. Ack via map (W1C to INTC_STAT): cpu_irq falls, INTC_STAT=0. // 5. Quiescent hold: INTC_STAT stays 0 for N cycles. // 6. Second transfer: INTC_STAT re-latches. Ack clears. Done. `timescale 1ns/1ps module tb_iop_dmac_intc; localparam int IOP_RAM_BYTES = 4 * 1024; localparam int IOP_RAM_ADDR_W = $clog2(IOP_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; // Real PS2 IOP INTC placement: I_STAT at 0x1F80_1070, I_MASK at // 0x1F80_1074 (4-byte stride, NOT the 16-byte stride the EE INTC // uses). The intc_stub is parameterized accordingly on the IOP // instance. localparam logic [31:0] INTC_STAT_ADDR = 32'h1F80_1070; localparam logic [31:0] INTC_MASK_ADDR = 32'h1F80_1074; localparam logic [7:0] INTC_STAT_OFFSET = 8'h70; localparam logic [7:0] INTC_MASK_OFFSET = 8'h74; localparam logic [31:0] SRC_BASE = 32'h0000_0200; localparam int BEATS = 4; // small + fast logic clk; logic rst_n; initial clk = 1'b0; always #5 clk = ~clk; // ------------------------------------------------------------------ // IOP map + IOP RAM // ------------------------------------------------------------------ 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 iop_ram_rd_en; logic [20:0] iop_ram_rd_addr; logic [31:0] iop_ram_rd_data; logic iop_ram_rd_valid; logic iop_ram_wr_en; logic [20:0] iop_ram_wr_addr; logic [31:0] iop_ram_wr_data; logic [3:0] iop_ram_wr_be; logic [7:0] iop_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; logic map_intc_rd_en; logic [7:0] map_intc_rd_addr; logic [31:0] map_intc_rd_data; logic map_intc_rd_valid; logic map_intc_wr_en; logic [7:0] map_intc_wr_addr; logic [31:0] map_intc_wr_data; 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 iop_map_ev_valid; trace_pkg::subsys_e iop_map_ev_subsys; trace_pkg::event_e iop_map_ev_event; logic [63:0] iop_map_ev_arg0, iop_map_ev_arg1, iop_map_ev_arg2, iop_map_ev_arg3; logic [31:0] iop_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), .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_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_rd_en(), .sif_rd_addr(), .sif_rd_data(32'd0), .sif_rd_valid(1'b0), .sif_wr_en(), .sif_wr_addr(), .sif_wr_data(), .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_rd_en(map_intc_rd_en), .iop_intc_rd_addr(map_intc_rd_addr), .iop_intc_rd_data(map_intc_rd_data), .iop_intc_rd_valid(map_intc_rd_valid), .iop_intc_wr_en(map_intc_wr_en), .iop_intc_wr_addr(map_intc_wr_addr), .iop_intc_wr_data(map_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_rd_en(iop_ram_rd_en), .ram_rd_addr(iop_ram_rd_addr), .ram_rd_data(iop_ram_rd_data), .ram_rd_valid(iop_ram_rd_valid), .ram_wr_en(iop_ram_wr_en), .ram_wr_addr(iop_ram_wr_addr), .ram_wr_data(iop_ram_wr_data), .ram_wr_be(iop_ram_wr_be), .ram_master_id(iop_ram_master_id), .ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys), .ev_event(iop_map_ev_event), .ev_arg0(iop_map_ev_arg0), .ev_arg1(iop_map_ev_arg1), .ev_arg2(iop_map_ev_arg2), .ev_arg3(iop_map_ev_arg3), .ev_flags(iop_map_ev_flags) ); iop_ram_stub #(.SIZE_BYTES(IOP_RAM_BYTES)) u_iop_ram ( .clk(clk), .rst_n(rst_n), .rd_en(iop_ram_rd_en), .rd_addr(iop_ram_rd_addr[IOP_RAM_ADDR_W-1:0]), .rd_data(iop_ram_rd_data), .rd_valid(iop_ram_rd_valid), .wr_en(iop_ram_wr_en), .wr_addr(iop_ram_wr_addr[IOP_RAM_ADDR_W-1:0]), .wr_data(iop_ram_wr_data), .wr_be(iop_ram_wr_be), .master_id(iop_ram_master_id), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); // ------------------------------------------------------------------ // IOP DMAC ch9 — payload-on-the-floor endpoint // ------------------------------------------------------------------ logic ep_valid; logic [31:0] ep_data; logic ep_last; logic ep_ready; assign ep_ready = 1'b1; logic dmac_irq_completion; 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), .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), .ep_valid(ep_valid), .ep_data(ep_data), .ep_last(ep_last), .ep_ready(ep_ready), .irq_completion_o(dmac_irq_completion), .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) ); // ------------------------------------------------------------------ // IOP INTC — intc_stub reused; bit 0 wired to DMAC ch9 completion // ------------------------------------------------------------------ logic [15:0] iop_irq_src; assign iop_irq_src = {15'd0, dmac_irq_completion}; logic iop_cpu_irq; logic intc_ev_valid; trace_pkg::subsys_e intc_ev_subsys; trace_pkg::event_e intc_ev_event; logic [63:0] intc_ev_arg0, intc_ev_arg1, intc_ev_arg2, intc_ev_arg3; logic [31:0] intc_ev_flags; intc_stub #( .INTC_STAT_OFFSET(INTC_STAT_OFFSET), .INTC_MASK_OFFSET(INTC_MASK_OFFSET) ) u_intc ( .clk(clk), .rst_n(rst_n), .reg_wr_en(map_intc_wr_en), .reg_rd_en(map_intc_rd_en), .reg_addr(map_intc_wr_en ? map_intc_wr_addr : map_intc_rd_addr), .reg_wr_data(map_intc_wr_data), .reg_rd_data(map_intc_rd_data), .reg_rd_valid(map_intc_rd_valid), .irq_src(iop_irq_src), .cpu_irq(iop_cpu_irq), .ev_valid(intc_ev_valid), .ev_subsys(intc_ev_subsys), .ev_event(intc_ev_event), .ev_arg0(intc_ev_arg0), .ev_arg1(intc_ev_arg1), .ev_arg2(intc_ev_arg2), .ev_arg3(intc_ev_arg3), .ev_flags(intc_ev_flags) ); // ------------------------------------------------------------------ // Trace sinks // ------------------------------------------------------------------ trace_sink_stub #(.FILENAME("iop_intc_iop_map.trace"), .SINK_LABEL("iop_map")) u_trace_iop_map (.clk(clk), .rst_n(rst_n), .ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys), .ev_event(iop_map_ev_event), .ev_arg0(iop_map_ev_arg0), .ev_arg1(iop_map_ev_arg1), .ev_arg2(iop_map_ev_arg2), .ev_arg3(iop_map_ev_arg3), .ev_flags(iop_map_ev_flags)); trace_sink_stub #(.FILENAME("iop_intc_dmac.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)); trace_sink_stub #(.FILENAME("iop_intc_intc.trace"), .SINK_LABEL("iop_intc")) u_trace_intc (.clk(clk), .rst_n(rst_n), .ev_valid(intc_ev_valid), .ev_subsys(intc_ev_subsys), .ev_event(intc_ev_event), .ev_arg0(intc_ev_arg0), .ev_arg1(intc_ev_arg1), .ev_arg2(intc_ev_arg2), .ev_arg3(intc_ev_arg3), .ev_flags(intc_ev_flags)); // ------------------------------------------------------------------ // Counters // ------------------------------------------------------------------ int dmac_done_events; int intc_assert_events; int intc_ack_events; int map_intc_events; int errors; initial begin dmac_done_events = 0; intc_assert_events = 0; intc_ack_events = 0; map_intc_events = 0; errors = 0; end always_ff @(posedge clk) begin if (rst_n && dmac_ev_valid && dmac_ev_subsys == trace_pkg::SUBSYS_DMAC && dmac_ev_event == trace_pkg::EV_DMA_DONE) dmac_done_events <= dmac_done_events + 1; if (rst_n && intc_ev_valid && intc_ev_subsys == trace_pkg::SUBSYS_INTC && intc_ev_event == trace_pkg::EV_IRQ) begin if (intc_ev_arg3 == 64'd0 && (intc_ev_flags & 32'h1) == 32'd0) intc_assert_events <= intc_assert_events + 1; else if (intc_ev_arg3 == 64'd1) intc_ack_events <= intc_ack_events + 1; end if (rst_n && iop_map_ev_valid && iop_map_ev_subsys == trace_pkg::SUBSYS_IOP && iop_map_ev_arg3[7:0] == 8'd5) // REGION_IOP_INTC map_intc_events <= map_intc_events + 1; end // ------------------------------------------------------------------ // Helpers — all INTC access flows through the real IOP map // ------------------------------------------------------------------ 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(input logic [31:0] addr, output logic [31:0] data); @(negedge clk); iop_rd_en = 1'b1; iop_rd_addr = addr; @(negedge clk); iop_rd_en = 1'b0; iop_rd_addr = 32'd0; data = iop_rd_data; endtask task automatic run_ch9_dma_and_wait; iop_write(DMAC_MADR_ADDR, SRC_BASE); iop_write(DMAC_BCR_ADDR, 32'(BEATS)); iop_write(DMAC_CHCR_ADDR, 32'h0000_0001); repeat (10 * BEATS) @(posedge clk); endtask // ------------------------------------------------------------------ // Stimulus // ------------------------------------------------------------------ logic [31:0] stat_val; logic [31:0] payload [0:BEATS-1]; 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; for (int i = 0; i < BEATS; i++) payload[i] = 32'hBEEF_0000 | i[31:0]; repeat (4) @(posedge clk); rst_n = 1'b1; repeat (2) @(posedge clk); // Preload IOP RAM for (int i = 0; i < BEATS; i++) begin iop_write(SRC_BASE + (i << 2), payload[i]); end // -------------------------------------------------------------- // Scenario 1: pre-transfer quiescence // -------------------------------------------------------------- iop_read(INTC_STAT_ADDR, stat_val); if (stat_val !== 32'd0) begin $error("[tb_iop_dmac_intc] pre-transfer INTC_STAT nonzero: 0x%08h", stat_val); errors = errors + 1; end if (iop_cpu_irq !== 1'b0) begin $error("[tb_iop_dmac_intc] pre-transfer iop_cpu_irq asserted"); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 2: first transfer latches INTC_STAT[0] // -------------------------------------------------------------- run_ch9_dma_and_wait(); if (dmac_done_events != 1) begin $error("[tb_iop_dmac_intc] expected 1 DMA_DONE after first run, got %0d", dmac_done_events); errors = errors + 1; end iop_read(INTC_STAT_ADDR, stat_val); if ((stat_val & 32'h1) == 32'd0) begin $error("[tb_iop_dmac_intc] INTC_STAT bit 0 not set post-DMA: 0x%08h", stat_val); errors = errors + 1; end if (iop_cpu_irq !== 1'b0) begin $error("[tb_iop_dmac_intc] iop_cpu_irq asserted with mask=0"); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 3: mask on via map — iop_cpu_irq rises // -------------------------------------------------------------- iop_write(INTC_MASK_ADDR, 32'h0000_0001); @(posedge clk); if (iop_cpu_irq !== 1'b1) begin $error("[tb_iop_dmac_intc] iop_cpu_irq not asserted after unmasking"); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 4: ack via map (W1C) // -------------------------------------------------------------- iop_write(INTC_STAT_ADDR, 32'h0000_0001); @(posedge clk); iop_read(INTC_STAT_ADDR, stat_val); if (stat_val !== 32'd0) begin $error("[tb_iop_dmac_intc] INTC_STAT not cleared after ack: 0x%08h", stat_val); errors = errors + 1; end if (iop_cpu_irq !== 1'b0) begin $error("[tb_iop_dmac_intc] iop_cpu_irq still high after ack"); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 5: quiescent hold // -------------------------------------------------------------- repeat (30) @(posedge clk); iop_read(INTC_STAT_ADDR, stat_val); if (stat_val !== 32'd0) begin $error("[tb_iop_dmac_intc] INTC_STAT reasserted without new DMA: 0x%08h", stat_val); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 6: second transfer re-latches // -------------------------------------------------------------- run_ch9_dma_and_wait(); if (dmac_done_events != 2) begin $error("[tb_iop_dmac_intc] expected 2 DMA_DONE after second run, got %0d", dmac_done_events); errors = errors + 1; end iop_read(INTC_STAT_ADDR, stat_val); if ((stat_val & 32'h1) == 32'd0) begin $error("[tb_iop_dmac_intc] second DMA did not re-latch STAT[0]: 0x%08h", stat_val); errors = errors + 1; end if (iop_cpu_irq !== 1'b1) begin $error("[tb_iop_dmac_intc] iop_cpu_irq not high (mask=1, stat=1)"); errors = errors + 1; end iop_write(INTC_STAT_ADDR, 32'h0000_0001); @(posedge clk); iop_read(INTC_STAT_ADDR, stat_val); if (stat_val !== 32'd0) begin $error("[tb_iop_dmac_intc] second ack didn't clear STAT"); errors = errors + 1; end repeat (4) @(posedge clk); $display("[tb_iop_dmac_intc] done=%0d asserts=%0d acks=%0d map_intc_events=%0d errors=%0d", dmac_done_events, intc_assert_events, intc_ack_events, map_intc_events, errors); if (dmac_done_events != 2) $error("expected 2 DMA_DONE, got %0d", dmac_done_events); if (intc_assert_events != 2) $error("expected 2 INTC asserts, got %0d", intc_assert_events); if (intc_ack_events != 2) $error("expected 2 INTC acks, got %0d", intc_ack_events); // Map should have seen at least: 4 reads of STAT + 1 mask write + 2 ack writes = 7 if (map_intc_events < 7) $error("expected >=7 map events tagged IOP_INTC, got %0d", map_intc_events); if (errors == 0 && dmac_done_events == 2 && intc_assert_events == 2 && intc_ack_events == 2 && map_intc_events >= 7) $display("[tb_iop_dmac_intc] PASS"); else $display("[tb_iop_dmac_intc] FAIL"); $finish; end initial begin #500000; $error("[tb_iop_dmac_intc] timeout"); $finish; end endmodule : tb_iop_dmac_intc