// retroDE_ps2 — tb_ee_dmac_intc // // First completion-visible EE-side interrupt flow. Wires an EE DMAC // completion pulse into intc_stub as a real interrupt source, and // proves the full mask / pending / ack / re-assertion semantics by // driving two back-to-back transfers. // // Chain: // ee_memory_map_stub (DMAC read master, RAM region) // └─► ee_ram_stub (payload source, preloaded by TB) // // dmac_reg_stub (ch2) — minimal QWC=1 transfer // ep_* drained by a TB-level always-ready sink (no GIF in scope) // irq_completion_o (1-cycle pulse on S_DONE) // │ // ▼ // intc_stub.irq_src[0] (sticky latch into INTC_STAT[0]) // // TB drives INTC register port directly — the EE map does not route // INTC addresses in current scope, per the deferred-follow-on list in // rtl/sif/README.md. // // Scenarios: // 1. Pre-DMA quiescence: INTC_STAT = 0, cpu_irq = 0. // 2. First transfer: DMA runs, DMA_DONE pulses irq_src[0], // INTC_STAT[0] latches to 1. cpu_irq still // 0 because INTC_MASK[0] is 0. // 3. Mask on: Write INTC_MASK = 0x1. cpu_irq becomes 1. // 4. Ack: Write INTC_STAT = 0x1 (W1C). INTC_STAT // reads 0, cpu_irq falls to 0. // 5. Quiescent hold: Wait N cycles. INTC_STAT stays 0 — no // false re-assertion without a new transfer. // 6. Second transfer: DMA runs again. INTC_STAT[0] re-latches. // Ack clears; quiescent again. // // Success = every assertion above holds and the trace files are // consistent (one IRQ event on each assertion, one on each ack). `timescale 1ns/1ps module tb_ee_dmac_intc; localparam int EE_RAM_BYTES = 4 * 1024; localparam int EE_RAM_ADDR_W = $clog2(EE_RAM_BYTES); localparam logic [31:0] SRC_BASE = 32'h0000_0100; // qword-aligned localparam logic [7:0] DMAC_CHCR_OFF = 8'h00; localparam logic [7:0] DMAC_MADR_OFF = 8'h10; localparam logic [7:0] DMAC_QWC_OFF = 8'h20; localparam logic [7:0] INTC_STAT_OFF = 8'h00; localparam logic [7:0] INTC_MASK_OFF = 8'h10; logic clk; logic rst_n; initial clk = 1'b0; always #5 clk = ~clk; // ------------------------------------------------------------------ // EE map + EE RAM (TB also preloads RAM through the map's fetch // write path — but the existing ee_memory_map_stub has no bridge // for the EE side separate from the new 128-bit bridge port. For // this TB we preload the EE RAM through the new bridge_wr_* port.) // ------------------------------------------------------------------ logic dmac_mem_rd_en; logic [31:0] dmac_mem_rd_addr; logic [127:0] dmac_mem_rd_data; logic dmac_mem_rd_valid; logic ee_map_ram_rd_en; logic [24:0] ee_map_ram_rd_addr; logic [127:0] ee_ram_rd_data; logic ee_ram_rd_valid; logic ee_map_ram_wr_en; logic [24:0] ee_map_ram_wr_addr; logic [127:0] ee_map_ram_wr_data; logic [15:0] ee_map_ram_wr_be; logic [7:0] ee_map_ram_master_id; // TB drives the bridge_wr_* port directly to preload RAM logic tb_preload_wr_en; logic [31:0] tb_preload_wr_addr; logic [127:0] tb_preload_wr_data; logic [15:0] tb_preload_wr_be; ee_memory_map_stub u_ee_map ( .clk(clk), .rst_n(rst_n), .ee_rd_en(1'b0), .ee_rd_addr(32'd0), .ee_rd_data(), .ee_rd_valid(), .ee_wr_en(1'b0), .ee_wr_addr(32'd0), .ee_wr_data(32'd0), .ee_wr_be(4'd0), .dmac_rd_en(dmac_mem_rd_en), .dmac_rd_addr(dmac_mem_rd_addr), .dmac_rd_data(dmac_mem_rd_data), .dmac_rd_valid(dmac_mem_rd_valid), .bios_rd_en(), .bios_rd_addr(), .bios_rd_data(32'd0), .bios_rd_valid(1'b0), .ram_rd_en(ee_map_ram_rd_en), .ram_rd_addr(ee_map_ram_rd_addr), .ram_rd_data(ee_ram_rd_data), .ram_rd_valid(ee_ram_rd_valid), .bridge_wr_en(tb_preload_wr_en), .bridge_wr_addr(tb_preload_wr_addr), .bridge_wr_data(tb_preload_wr_data), .bridge_wr_be(tb_preload_wr_be), .bridge_master_id(8'd0), // TB direct .ram_wr_en(ee_map_ram_wr_en), .ram_wr_addr(ee_map_ram_wr_addr), .ram_wr_data(ee_map_ram_wr_data), .ram_wr_be(ee_map_ram_wr_be), .ram_master_id(ee_map_ram_master_id), .ee_dmac_ch2_wr_en(), .ee_dmac_ch2_wr_addr(), .ee_dmac_ch2_wr_data(), .ee_dmac_ch2_rd_en(), .ee_dmac_ch2_rd_addr(), .ee_dmac_ch2_rd_data(32'd0), .ee_dmac_ch2_rd_valid(1'b0), .ee_intc_wr_en(), .ee_intc_wr_addr(), .ee_intc_wr_data(), .ee_intc_rd_en(), .ee_intc_rd_addr(), .ee_intc_rd_data(32'd0), .ee_intc_rd_valid(1'b0), .ee_misc_mmio_wr_en(), .ee_misc_mmio_wr_addr(), .ee_misc_mmio_wr_data(), .ee_misc_mmio_wr_be(), .ee_misc_mmio_rd_en(), .ee_misc_mmio_rd_addr(), .ee_misc_mmio_rd_data(32'd0), .ee_misc_mmio_rd_valid(1'b0), .ee_biu_wr_en(), .ee_biu_wr_addr(), .ee_biu_wr_data(), .ee_biu_wr_be(), .ee_biu_rd_en(), .ee_biu_rd_addr(), .ee_biu_rd_data(32'd0), .ee_biu_rd_valid(1'b0), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); ee_ram_stub #(.SIZE_BYTES(EE_RAM_BYTES)) u_ee_ram ( .clk(clk), .rst_n(rst_n), .rd_en(ee_map_ram_rd_en), .rd_addr(ee_map_ram_rd_addr[EE_RAM_ADDR_W-1:0]), .rd_data(ee_ram_rd_data), .rd_valid(ee_ram_rd_valid), .wr_en(ee_map_ram_wr_en), .wr_addr(ee_map_ram_wr_addr[EE_RAM_ADDR_W-1:0]), .wr_data(ee_map_ram_wr_data), .wr_be(ee_map_ram_wr_be), .master_id(ee_map_ram_master_id), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); // ------------------------------------------------------------------ // DMAC (ch2). Endpoint drained by TB-level always-ready sink. // ------------------------------------------------------------------ logic dmac_reg_wr_en; logic [7:0] dmac_reg_offset; logic [31:0] dmac_reg_wr_data; logic dmac_ep_valid; logic [127:0] dmac_ep_data; logic dmac_ep_last; logic dmac_ep_ready; assign dmac_ep_ready = 1'b1; // immediately drain logic dmac_irq_completion; 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; dmac_reg_stub u_dmac ( .clk(clk), .rst_n(rst_n), .reg_wr_en(dmac_reg_wr_en), .reg_offset(dmac_reg_offset), .reg_wr_data(dmac_reg_wr_data), .reg_rd_en(1'b0), .reg_rd_data(), .reg_rd_valid(), .mem_rd_en(dmac_mem_rd_en), .mem_rd_addr(dmac_mem_rd_addr), .mem_rd_data(dmac_mem_rd_data), .mem_rd_valid(dmac_mem_rd_valid), .ep_valid(dmac_ep_valid), .ep_data(dmac_ep_data), .ep_last(dmac_ep_last), .ep_ready(dmac_ep_ready), .irq_completion_o(dmac_irq_completion), .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) ); // ------------------------------------------------------------------ // INTC — bit 0 wired to DMAC completion, rest tied low // ------------------------------------------------------------------ logic intc_reg_wr_en; logic intc_reg_rd_en; logic [7:0] intc_reg_addr; logic [31:0] intc_reg_wr_data; logic [31:0] intc_reg_rd_data; logic intc_reg_rd_valid; logic [15:0] intc_irq_src; assign intc_irq_src = {15'd0, dmac_irq_completion}; logic 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 u_intc ( .clk(clk), .rst_n(rst_n), .reg_wr_en(intc_reg_wr_en), .reg_rd_en(intc_reg_rd_en), .reg_addr(intc_reg_addr), .reg_wr_data(intc_reg_wr_data), .reg_rd_data(intc_reg_rd_data), .reg_rd_valid(intc_reg_rd_valid), .irq_src(intc_irq_src), .cpu_irq(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("ee_dmac_intc_dmac.trace"), .SINK_LABEL("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("ee_dmac_intc_intc.trace"), .SINK_LABEL("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 errors; initial begin dmac_done_events = 0; intc_assert_events = 0; intc_ack_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 // intc_stub labels: arg3=1 is ack (W1C); arg3=0 can be either // a new source assertion OR a mask-register write. flags bit 0 // distinguishes writes (mask wr) from source-driven assertions. 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 end // ------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------ task automatic preload_qword(input logic [31:0] addr, input logic [127:0] data); @(negedge clk); tb_preload_wr_en = 1'b1; tb_preload_wr_addr = addr; tb_preload_wr_data = data; tb_preload_wr_be = 16'hFFFF; @(negedge clk); tb_preload_wr_en = 1'b0; tb_preload_wr_addr = 32'd0; tb_preload_wr_data = 128'd0; tb_preload_wr_be = 16'd0; endtask task automatic dmac_write(input logic [7:0] off, input logic [31:0] data); @(negedge clk); dmac_reg_wr_en = 1'b1; dmac_reg_offset = off; dmac_reg_wr_data = data; @(negedge clk); dmac_reg_wr_en = 1'b0; dmac_reg_offset = 8'd0; dmac_reg_wr_data = 32'd0; endtask task automatic intc_write(input logic [7:0] addr, input logic [31:0] data); @(negedge clk); intc_reg_wr_en = 1'b1; intc_reg_addr = addr; intc_reg_wr_data = data; @(negedge clk); intc_reg_wr_en = 1'b0; intc_reg_addr = 8'd0; intc_reg_wr_data = 32'd0; endtask task automatic intc_read(input logic [7:0] addr, output logic [31:0] data); @(negedge clk); intc_reg_rd_en = 1'b1; intc_reg_addr = addr; @(negedge clk); intc_reg_rd_en = 1'b0; intc_reg_addr = 8'd0; @(posedge clk); data = intc_reg_rd_data; endtask task automatic run_dma_and_wait; dmac_write(DMAC_MADR_OFF, SRC_BASE); dmac_write(DMAC_QWC_OFF, 32'h0000_0001); dmac_write(DMAC_CHCR_OFF, 32'h0000_0001); repeat (20) @(posedge clk); // ample time for QWC=1 endtask // ------------------------------------------------------------------ // Stimulus // ------------------------------------------------------------------ logic [31:0] stat_val; initial begin rst_n = 1'b0; tb_preload_wr_en = 1'b0; tb_preload_wr_addr = 32'd0; tb_preload_wr_data = 128'd0; tb_preload_wr_be = 16'd0; dmac_reg_wr_en = 1'b0; dmac_reg_offset = 8'd0; dmac_reg_wr_data = 32'd0; intc_reg_wr_en = 1'b0; intc_reg_rd_en = 1'b0; intc_reg_addr = 8'd0; intc_reg_wr_data = 32'd0; repeat (4) @(posedge clk); rst_n = 1'b1; repeat (2) @(posedge clk); // Preload a distinctive qword into EE RAM for the DMAC to fetch. preload_qword(SRC_BASE, 128'hCAFE_BABE_DEAD_BEEF_1234_5678_AABB_CCDD); // -------------------------------------------------------------- // Scenario 1: pre-DMA quiescence // -------------------------------------------------------------- intc_read(INTC_STAT_OFF, stat_val); if (stat_val !== 32'd0) begin $error("[tb_ee_dmac_intc] pre-DMA INTC_STAT nonzero: 0x%08h", stat_val); errors = errors + 1; end if (cpu_irq !== 1'b0) begin $error("[tb_ee_dmac_intc] pre-DMA cpu_irq asserted"); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 2: first DMA — completion latches INTC_STAT[0] // -------------------------------------------------------------- run_dma_and_wait(); if (dmac_done_events != 1) begin $error("[tb_ee_dmac_intc] expected 1 DMA_DONE after first run, got %0d", dmac_done_events); errors = errors + 1; end intc_read(INTC_STAT_OFF, stat_val); if ((stat_val & 32'h1) == 32'd0) begin $error("[tb_ee_dmac_intc] INTC_STAT bit 0 not set after DMA: 0x%08h", stat_val); errors = errors + 1; end // Mask is still 0 → cpu_irq should be 0 if (cpu_irq !== 1'b0) begin $error("[tb_ee_dmac_intc] cpu_irq asserted with mask=0"); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 3: mask on — cpu_irq rises // -------------------------------------------------------------- intc_write(INTC_MASK_OFF, 32'h0000_0001); @(posedge clk); if (cpu_irq !== 1'b1) begin $error("[tb_ee_dmac_intc] cpu_irq not asserted after unmasking"); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 4: ack — W1C clears STAT, cpu_irq falls // -------------------------------------------------------------- intc_write(INTC_STAT_OFF, 32'h0000_0001); @(posedge clk); intc_read(INTC_STAT_OFF, stat_val); if (stat_val !== 32'd0) begin $error("[tb_ee_dmac_intc] INTC_STAT not cleared after ack: 0x%08h", stat_val); errors = errors + 1; end if (cpu_irq !== 1'b0) begin $error("[tb_ee_dmac_intc] cpu_irq still high after ack"); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 5: quiescent hold — no false re-assertion // -------------------------------------------------------------- repeat (30) @(posedge clk); intc_read(INTC_STAT_OFF, stat_val); if (stat_val !== 32'd0) begin $error("[tb_ee_dmac_intc] INTC_STAT reasserted without new DMA: 0x%08h", stat_val); errors = errors + 1; end // -------------------------------------------------------------- // Scenario 6: second DMA re-latches; ack clears again // -------------------------------------------------------------- run_dma_and_wait(); if (dmac_done_events != 2) begin $error("[tb_ee_dmac_intc] expected 2 DMA_DONE after second run, got %0d", dmac_done_events); errors = errors + 1; end intc_read(INTC_STAT_OFF, stat_val); if ((stat_val & 32'h1) == 32'd0) begin $error("[tb_ee_dmac_intc] second DMA did not re-latch INTC_STAT[0]: 0x%08h", stat_val); errors = errors + 1; end if (cpu_irq !== 1'b1) begin $error("[tb_ee_dmac_intc] cpu_irq not high with mask=1 and stat=1"); errors = errors + 1; end intc_write(INTC_STAT_OFF, 32'h0000_0001); @(posedge clk); intc_read(INTC_STAT_OFF, stat_val); if (stat_val !== 32'd0) begin $error("[tb_ee_dmac_intc] second ack didn't clear INTC_STAT"); errors = errors + 1; end repeat (4) @(posedge clk); $display("[tb_ee_dmac_intc] done=%0d asserts=%0d acks=%0d errors=%0d", dmac_done_events, intc_assert_events, intc_ack_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 assertions, got %0d", intc_assert_events); if (intc_ack_events != 2) $error("expected 2 INTC acks, got %0d", intc_ack_events); if (errors == 0 && dmac_done_events == 2 && intc_assert_events == 2 && intc_ack_events == 2) $display("[tb_ee_dmac_intc] PASS"); else $display("[tb_ee_dmac_intc] FAIL"); $finish; end initial begin #500000; $error("[tb_ee_dmac_intc] timeout"); $finish; end endmodule : tb_ee_dmac_intc