// retroDE_ps2 — tb_top_psmct32_raster_demo (Ch146) // // Focused TB for the first hardware-targeted top wrapper. Drives the // top with the static .mem fixtures generated by sim/data/ // top_psmct32_raster_demo/bake.py and verifies the same 16×8 PCRTC // frame Ch123 produces. The point of this TB is NOT to re-verify // every Ch123 invariant — that's already covered by // tb_gs_demo_psmct32_swizzle_e2e — but to prove: // (1) The top wrapper compiles standalone with no hierarchical // refs into TB scopes. // (2) `$readmemh` of the static fixtures produces identical // behavior to the procedural Ch123 preload. // (3) The status bundle (core_halt + dma_done_seen + frame_seen) // fires in the expected order. // (4) The active PCRTC region matches Ch123's per-quadrant // grayscale exactly — so a board-level bring-up that wires // r/g/b/hsync/vsync/de to a video PHY would display the same // image the sim produces. `timescale 1ns/1ps module tb_top_psmct32_raster_demo; localparam int H_ACTIVE = 16; localparam int V_ACTIVE = 8; logic clk; logic rst_n; initial clk = 1'b0; always #5 clk = ~clk; logic core_go; logic [7:0] r, g, b; logic hsync, vsync, de; logic core_halt; logic dma_done_seen; logic frame_seen; // BIOS / payload image paths are passed to the top wrapper via // pre-defined macros (the Ch146 build rule sets // TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE + // TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE via -D, then the // wrapper expands them at the bios_rom_stub / ee_ram_stub // instances). top_psmct32_raster_demo #( .H_ACTIVE (H_ACTIVE), .V_ACTIVE (V_ACTIVE) ) dut ( .clk(clk), .rst_n(rst_n), .core_go(core_go), .r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync), .de(de), .core_halt(core_halt), .dma_done_seen(dma_done_seen), .frame_seen(frame_seen) ); // ---------------------------------------------------------------- // Per-quadrant expected RGB (matches Ch123 / bake.py). // ---------------------------------------------------------------- function automatic int quadrant_idx(input int x, input int y); int qx, qy; qx = (x < 8) ? 0 : 1; qy = (y < 4) ? 0 : 1; return qy * 2 + qx; endfunction function automatic logic [7:0] expected_r(input int q); case (q) 0: return 8'h55; 1: return 8'h66; 2: return 8'h77; default: return 8'h88; endcase endfunction function automatic logic [7:0] expected_g(input int q); case (q) 0: return 8'hAA; 1: return 8'hBB; 2: return 8'h33; default: return 8'h44; endcase endfunction function automatic logic [7:0] expected_b(input int q); case (q) 0: return 8'hCC; 1: return 8'hDD; 2: return 8'h99; default: return 8'h22; endcase endfunction // ---------------------------------------------------------------- // Frame capture. // ---------------------------------------------------------------- logic [7:0] cap_r [0:V_ACTIVE-1][0:H_ACTIVE-1]; logic [7:0] cap_g [0:V_ACTIVE-1][0:H_ACTIVE-1]; logic [7:0] cap_b [0:V_ACTIVE-1][0:H_ACTIVE-1]; logic cap_de[0:V_ACTIVE-1][0:H_ACTIVE-1]; bit capture_armed; initial begin for (int y = 0; y < V_ACTIVE; y++) for (int x = 0; x < H_ACTIVE; x++) begin cap_r[y][x] = 8'd0; cap_g[y][x] = 8'd0; cap_b[y][x] = 8'd0; cap_de[y][x] = 1'b0; end capture_armed = 1'b0; end always_ff @(posedge clk) begin if (rst_n && capture_armed && de && (dut.u_pcrtc.vcnt < V_ACTIVE) && (dut.u_pcrtc.hcnt < H_ACTIVE)) begin cap_r [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= r; cap_g [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= g; cap_b [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= b; cap_de[dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= 1'b1; end end int errors; initial errors = 0; initial begin rst_n = 1'b0; core_go = 1'b0; repeat (4) @(posedge clk); rst_n = 1'b1; repeat (8) @(posedge clk); // Pulse core_go for one cycle. On hardware a board reset- // release sequencer can do the same. @(negedge clk); core_go = 1'b1; @(negedge clk); core_go = 1'b0; // Wait for the EE to halt (SYSCALL halt_o), then for DMAC // DONE event to be sticky-latched. wait (core_halt == 1'b1); repeat (4) @(posedge clk); wait (dma_done_seen == 1'b1); repeat (10) @(posedge clk); if (dut.xfer_busy == 1'b1) wait (dut.xfer_busy == 1'b0); if (dut.u_gs.raster_active == 1'b1) wait (dut.u_gs.raster_active == 1'b0); repeat (10) @(posedge clk); @(posedge dut.u_pcrtc.end_of_frame); @(posedge clk); capture_armed = 1'b1; @(posedge dut.u_pcrtc.end_of_frame); @(posedge clk); capture_armed = 1'b0; // Phase 1 — scanout assertion. Per-pixel per-quadrant RGB. for (int y = 0; y < V_ACTIVE; y++) begin for (int x = 0; x < H_ACTIVE; x++) begin int q; logic [7:0] er, eg, eb; q = quadrant_idx(x, y); er = expected_r(q); eg = expected_g(q); eb = expected_b(q); if (!cap_de[y][x]) begin $error("(%0d,%0d) DE never asserted", x, y); errors = errors + 1; end if (cap_r[y][x] !== er || cap_g[y][x] !== eg || cap_b[y][x] !== eb) begin $error("[scanout] (%0d,%0d) got (%02x,%02x,%02x) expected (%02x,%02x,%02x) sprite=%0d", x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x], er, eg, eb, q); errors = errors + 1; end end end // Phase 2 — status bundle. core_halt, dma_done_seen, frame_seen // all latched. if (!core_halt) begin $error("core_halt low at end of test"); errors = errors + 1; end if (!dma_done_seen) begin $error("dma_done_seen never latched"); errors = errors + 1; end if (!frame_seen) begin $error("frame_seen never latched"); errors = errors + 1; end $display("[tb_top_psmct32_raster_demo] core_halt=%0b dma_done_seen=%0b frame_seen=%0b raster_emits=%0d errors=%0d", core_halt, dma_done_seen, frame_seen, dut.u_gs.raster_pixel_emit_count, errors); if (errors == 0) $display("[tb_top_psmct32_raster_demo] PASS"); else $display("[tb_top_psmct32_raster_demo] FAIL"); $finish; end // Watchdog. initial begin #20000000; $error("[tb_top_psmct32_raster_demo] TIMEOUT"); $finish; end endmodule : tb_top_psmct32_raster_demo