// retroDE_ps2 — tb_ee_dmac_ctrl_stub // // Ch287 — Focused TB for ee_dmac_ctrl_stub. The DMAC global // control/status registers at 0x1000_E000..0x1000_E0FF — D_CTRL at // 0x00, D_STAT at 0x10 (W1C semantics on the low half), D_PCR at // 0x20, D_SQWC at 0x30, D_RBSR at 0x40, D_RBOR at 0x50. // // Test sequence: // 1. Reset-init: every named offset reads 0. // 2. D_CTRL latch round-trip (write/read at 0x00). // 3. D_STAT W1C semantics. Since nothing in the stub *sets* // d_stat bits (real PS2 channels would), we hierarchically // poke d_stat to a known value, then issue a W1C write and // verify only the requested CIS bits clear (low half) while // CIM bits in the high half are *unconditionally* written. // 4. D_PCR / D_SQWC / D_RBSR / D_RBOR round-trip (latched). // 5. Unknown offset (0x80) read returns 0; write doesn't crash // anything and the next valid read still works. // // Strict mode irrelevant here — the stub is driven directly, not // through ee_core_stub. `timescale 1ns/1ps module tb_ee_dmac_ctrl_stub; logic clk; logic rst_n; initial clk = 1'b0; always #5 clk = ~clk; // DUT ports logic reg_wr_en; logic [7:0] reg_offset; logic [31:0] reg_wr_data; logic reg_rd_en; logic [31:0] reg_rd_data; logic reg_rd_valid; logic ev_valid; trace_pkg::subsys_e ev_subsys; trace_pkg::event_e ev_event; logic [63:0] ev_arg0, ev_arg1, ev_arg2, ev_arg3; logic [31:0] ev_flags; ee_dmac_ctrl_stub dut ( .clk(clk), .rst_n(rst_n), .reg_wr_en(reg_wr_en), .reg_offset(reg_offset), .reg_wr_data(reg_wr_data), .reg_rd_en(reg_rd_en), .reg_rd_data(reg_rd_data), .reg_rd_valid(reg_rd_valid), .ev_valid(ev_valid), .ev_subsys(ev_subsys), .ev_event(ev_event), .ev_arg0(ev_arg0), .ev_arg1(ev_arg1), .ev_arg2(ev_arg2), .ev_arg3(ev_arg3), .ev_flags(ev_flags) ); int errors = 0; task automatic do_write(input logic [7:0] off, input logic [31:0] data); @(negedge clk); reg_wr_en = 1'b1; reg_offset = off; reg_wr_data = data; @(negedge clk); reg_wr_en = 1'b0; reg_offset = 8'd0; reg_wr_data = 32'd0; endtask task automatic do_read(input logic [7:0] off, output logic [31:0] data_out); @(negedge clk); reg_rd_en = 1'b1; reg_offset = off; @(negedge clk); reg_rd_en = 1'b0; reg_offset = 8'd0; @(posedge clk); data_out = reg_rd_data; endtask task automatic check(input string tag, input logic [31:0] got, input logic [31:0] exp); if (got !== exp) begin $display("[tb_ee_dmac_ctrl_stub] FAIL %s got=0x%08h exp=0x%08h", tag, got, exp); errors++; end else begin $display("[tb_ee_dmac_ctrl_stub] ok %s = 0x%08h", tag, got); end endtask logic [31:0] rd0; initial begin rst_n = 1'b0; reg_wr_en = 1'b0; reg_offset = 8'd0; reg_wr_data = 32'd0; reg_rd_en = 1'b0; repeat (4) @(posedge clk); rst_n = 1'b1; @(posedge clk); // ===== 1) Reset-init: every named offset reads 0 ===== do_read(8'h00, rd0); check("reset_d_ctrl", rd0, 32'd0); do_read(8'h10, rd0); check("reset_d_stat", rd0, 32'd0); do_read(8'h20, rd0); check("reset_d_pcr", rd0, 32'd0); do_read(8'h30, rd0); check("reset_d_sqwc", rd0, 32'd0); do_read(8'h40, rd0); check("reset_d_rbsr", rd0, 32'd0); do_read(8'h50, rd0); check("reset_d_rbor", rd0, 32'd0); // ===== 2) D_CTRL latch round-trip ===== do_write(8'h00, 32'h1234_5678); do_read (8'h00, rd0); check("d_ctrl_write_read", rd0, 32'h1234_5678); // ===== 3) D_STAT W1C semantics ===== // Pre-load d_stat hierarchically (CIS = 0x00AB, CIM = 0xFFFF // → full word 0xFFFF_00AB). Verify the read sees it. @(negedge clk); dut.d_stat = 32'hFFFF_00AB; @(negedge clk); do_read(8'h10, rd0); check("d_stat_preload", rd0, 32'hFFFF_00AB); // W1C the low byte: write 0x0000_00A0 — should clear bits 7,5,3 in // the low half (0x00AB & ~0x00A0 = 0x000B) and overwrite the high // half (CIM) with the write value (0x0000_0000). do_write(8'h10, 32'h0000_00A0); do_read (8'h10, rd0); check("d_stat_w1c_lo_cim_zero", rd0, 32'h0000_000B); // Re-pre-load to test that high-half write doesn't W1C the low. @(negedge clk); dut.d_stat = 32'h0000_00FF; @(negedge clk); do_write(8'h10, 32'hDEAD_0000); // CIM = 0xDEAD, CIS write = 0 (no clear) do_read (8'h10, rd0); check("d_stat_cim_write_cis_unchanged", rd0, 32'hDEAD_00FF); // ===== 4) D_PCR / D_SQWC / D_RBSR / D_RBOR latch ===== do_write(8'h20, 32'hCAFE_BABE); do_write(8'h30, 32'h1357_9BDF); do_write(8'h40, 32'h2468_ACE0); do_write(8'h50, 32'hFEED_FACE); do_read (8'h20, rd0); check("d_pcr_rt", rd0, 32'hCAFE_BABE); do_read (8'h30, rd0); check("d_sqwc_rt", rd0, 32'h1357_9BDF); do_read (8'h40, rd0); check("d_rbsr_rt", rd0, 32'h2468_ACE0); do_read (8'h50, rd0); check("d_rbor_rt", rd0, 32'hFEED_FACE); // D_CTRL untouched by D_PCR write. do_read (8'h00, rd0); check("d_ctrl_distinct", rd0, 32'h1234_5678); // ===== 5) Unknown offset read returns 0; write doesn't break ===== do_read (8'h80, rd0); check("unknown_offset_reads_zero", rd0, 32'd0); do_write(8'h80, 32'hAAAA_5555); // dropped do_read (8'h80, rd0); check("unknown_offset_still_zero", rd0, 32'd0); // Next valid read still works after an unknown write. do_read (8'h00, rd0); check("d_ctrl_after_unknown_write", rd0, 32'h1234_5678); if (errors == 0) $display("[tb_ee_dmac_ctrl_stub] PASS"); else $display("[tb_ee_dmac_ctrl_stub] FAIL errors=%0d", errors); $finish; end initial begin #5_000_000; $display("[tb_ee_dmac_ctrl_stub] TIMEOUT"); $finish; end endmodule : tb_ee_dmac_ctrl_stub