// retroDE_ps2 — top_psmct32_raster_demo (Ch146) // // First hardware-targeted top wrapper, structured around the Ch123 PSMCT32 // raster end-to-end demo (the simplest direct-color path; see the Ch144 // hardware-readiness report in docs/contracts/gif_gs.md for rationale and // dep-tree audit). This module is the one a board-level synthesis project // would target — board-level concerns (HDMI/VGA PHY, pin constraints, // .mem bake tooling, clock-domain crossings) are deliberately deferred to // later chapters. Ch146's job is to prove the design can be expressed as // a single SystemVerilog module with a sensible top-level shape. // // Topology mirrors the Ch123 TB exactly — the 11 modules in the Ch144 // dep tree, all instantiated here, with hardware-friendly tweaks: // // bios_rom_stub#(.IMAGE_FILE(BIOS_IMAGE_FILE)) — EE bootlet at 0xBFC0_0000 // ee_ram_stub#(.IMAGE_FILE(PAYLOAD_IMAGE_FILE)) — GIF payload at phys 0x100 // ee_memory_map_stub#(.USEG_SHADOW_WORDS_PARAM(1024)) — Ch145 BRAM shrink // ee_core_stub#(.PC_RESET(0xBFC00000)) — MIPS R5900 core // ee_gs_priv_bridge_stub — 32-bit MMIO → 64-bit GS-priv // dmac_reg_stub — DMAC ch2 // gif_packed_stub#(.REAL_AD_REG_MAP(1'b1)) — GIFtag + PACKED A+D parser // gs_stub#(.PSMCT32_SWIZZLE(1'b1)) — GS register file + raster // gif_image_xfer_stub#(.PSMCT32_SWIZZLE(1'b1)) — TRXDIR/IMAGE engine (idle in Ch123) // vram_stub#(.BYTES(8192)) — 8 KiB VRAM (one PSMCT32 page) // gs_pcrtc_stub#(.PSMCT32_SWIZZLE(1'b1)) — PCRTC scanout // // Differences from the Ch123 TB: // - No procedural ee_prog_word() / preload_qword() drives. The BIOS // bootlet and GIF payload are preloaded by `$readmemh` from the // IMAGE_FILE parameters (default empty = synthetic NOP-sled fallback // in bios_rom_stub for a "won't crash on power-up" smoke baseline, // and an all-zeros ee_ram_stub which yields no DMAC payload but a // stable PCRTC frame). // - useg_shadow_mem trimmed to 1024 words (4 KiB) via Ch145 // parameter — no useg traffic in the Ch123 data plane. // - All trace event outputs left open. Status is exposed as a // debug bundle (core_halt, dma_done_seen, frame_seen) that a // board can wire to LEDs. // - The Ch123 TB's collision-check `$error` and observer counters // are TB-only and do not appear here. (Their checks land in the // focused Ch146 TB tb_top_psmct32_raster_demo.sv instead.) // // Top-level ports: // clk, rst_n — single clock domain, active-low synchronous reset // core_go — pulsed high for one cycle to start the EE bootlet // (a board reset-release sequencer can tie it high // after rst_n deasserts) // r/g/b, hsync, vsync, de — 8-bit RGB scanout (PCRTC active region) // core_halt — high once SYSCALL halts the EE // dma_done_seen — sticky: high once DMAC channel-2 fires its DONE event // frame_seen — sticky: high once one full PCRTC frame end-of-frame fires // // Parameters: // H_ACTIVE / V_ACTIVE — PCRTC active region (defaults to the Ch123 16×8) // BIOS_SIZE_BYTES — bios_rom_stub size (default 4 KiB) // RAM_SIZE_BYTES — ee_ram_stub size (default 4 KiB) // VRAM_BYTES — vram_stub size (default 8 KiB) // USEG_SHADOW_WORDS_PARAM — Ch145 useg-shadow size (default 1024 = 4 KiB) // // Macros (NOT parameters — iverilog-12 string-parameter forwarding // limitation forced them to be macros; see the `\`define` block // below the `timescale directive): // TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE — path to bios.mem // (one 32-bit hex word/line) // TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE — path to payload.mem // (one 128-bit hex qword/line) // Both default to "" so the wrapper is still elaborable without // fixtures (synthetic NOP-sled in bios_rom_stub + zero-init // ee_ram_stub, which produces no DMAC payload but a stable PCRTC // frame). On synthesis these become FPGA-tool defines. // // PASS for the Ch146 focused TB matches Ch123 exactly: // dma=(1,24,1) ee_dmac_wr=3 giftags=4 ad_writes=20 xfer_writes=0 // ee_priv_wr=4 bridge_fires=4 core_halt=1 emits=128 frame=16x8 `timescale 1ns/1ps // BIOS / payload image paths are passed via macros (iverilog-12 // limitation: string parameter forwarding through hierarchy // elaborates inconsistently). On synthesis the same macros become // FPGA-tool defines pointing at .mem fixtures or board-specific // files. The macros default to empty strings (synthetic NOP-sled + // zero-RAM fallback in bios_rom_stub / ee_ram_stub) so the wrapper // is still elaborable without bake artifacts present. `ifndef TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE `define TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE "" `endif `ifndef TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE `define TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE "" `endif module top_psmct32_raster_demo import trace_pkg::*; #( parameter int H_ACTIVE = 16, parameter int V_ACTIVE = 8, parameter int BIOS_SIZE_BYTES = 4 * 1024, parameter int RAM_SIZE_BYTES = 4 * 1024, parameter int VRAM_BYTES = 8 * 1024, parameter int unsigned USEG_SHADOW_WORDS_PARAM = 1024, // Brick 1 — PSMCT32 page/block swizzle gate. Default 1 preserves // the Ch123/Ch251 swizzled raster+scanout behavior (and every // existing TB that drives this top). A TEXTURED-sprite demo // fixture sets this to 0 so the linear gs_texel_addr fetch and the // BITBLT upload land in the SAME (linear) VRAM layout — avoiding // the swizzle reconciliation the gs_stub TODO flags. The gate is // forwarded to gs_stub / gif_image_xfer_stub / gs_pcrtc_stub // together so all three VRAM views stay consistent. parameter bit PSMCT32_SWIZZLE = 1'b1 ) ( input logic clk, input logic rst_n, input logic core_go, output logic [7:0] r, output logic [7:0] g, output logic [7:0] b, output logic hsync, output logic vsync, output logic de, output logic core_halt, output logic dma_done_seen, output logic frame_seen, output logic raster_overflow, // Ch174 — event toggles for HPS-visible counters. See the // mirror block in top_psmct32_raster_demo_bram.sv for the full // pulse-CDC contract. Toggle, not pulse — by design. output logic frame_toggle, output logic dma_done_toggle ); localparam int RAM_ADDR_W = $clog2(RAM_SIZE_BYTES); localparam int BIOS_ADDR_W = $clog2(BIOS_SIZE_BYTES); // --------------------------------------------------------------------- // ee_ram_stub — DMAC-side GIF payload // --------------------------------------------------------------------- logic ram_rd_en; logic [RAM_ADDR_W-1:0] ram_rd_addr; logic [127:0] ram_rd_data; logic ram_rd_valid; // Top has no TB-direct write path; the wr_* ports are tied off. logic [7:0] ram_master_id; assign ram_master_id = ram_rd_en ? 8'd1 : 8'd0; ee_ram_stub #( .SIZE_BYTES(RAM_SIZE_BYTES), .IMAGE_FILE(`TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE) ) u_ram ( .clk(clk), .rst_n(rst_n), .rd_en(ram_rd_en), .rd_addr(ram_rd_addr), .rd_data(ram_rd_data), .rd_valid(ram_rd_valid), .wr_en(1'b0), .wr_addr('0), .wr_data(128'd0), .wr_be(16'd0), .master_id(ram_master_id), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); // --------------------------------------------------------------------- // bios_rom_stub — EE bootlet at 0xBFC0_0000 // --------------------------------------------------------------------- logic bios_rd_en; logic [21:0] bios_rd_addr_full; logic [BIOS_ADDR_W-1:0] bios_rd_addr; logic bios_rd_valid; logic [31:0] bios_rd_data; assign bios_rd_addr = bios_rd_addr_full[BIOS_ADDR_W-1:0]; bios_rom_stub #( .SIZE_BYTES(BIOS_SIZE_BYTES), .IMAGE_FILE(`TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE) ) u_bios ( .clk(clk), .rst_n(rst_n), .rd_en(bios_rd_en), .rd_addr(bios_rd_addr), .rd_data(bios_rd_data), .rd_valid(bios_rd_valid), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); // --------------------------------------------------------------------- // dmac_reg_stub — channel-2 NORMAL transfer // --------------------------------------------------------------------- logic dmac_reg_wr_en; logic [7:0] dmac_reg_offset; logic [31:0] dmac_reg_wr_data; logic dmac_mem_rd_en; logic [31:0] dmac_mem_rd_addr; logic dmac_gif_valid; logic [127:0] dmac_gif_data; logic dmac_gif_last; logic dmac_gif_ready; logic dmac_ev_valid; subsys_e dmac_ev_subsys; event_e dmac_ev_event; logic [127:0] map_to_dmac_rd_data; logic map_to_dmac_rd_valid; 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(map_to_dmac_rd_data), .mem_rd_valid(map_to_dmac_rd_valid), .ep_valid(dmac_gif_valid), .ep_data(dmac_gif_data), .ep_last(dmac_gif_last), .ep_ready(dmac_gif_ready), .irq_completion_o(), .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys), .ev_event(dmac_ev_event), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); // --------------------------------------------------------------------- // ee_memory_map_stub — bus arbiter (USEG_SHADOW shrunk per Ch145) // --------------------------------------------------------------------- logic ee_cpu_rd_en; logic [31:0] ee_cpu_rd_addr; logic [31:0] ee_cpu_rd_data; logic ee_cpu_rd_valid; logic ee_cpu_wr_en; logic [31:0] ee_cpu_wr_addr; logic [31:0] ee_cpu_wr_data; logic [3:0] ee_cpu_wr_be; logic map_gs_priv_wr_en; logic [15:0] map_gs_priv_wr_addr; logic [31:0] map_gs_priv_wr_data; logic [3:0] map_gs_priv_wr_be; logic map_ram_rd_en; logic [24:0] map_ram_rd_addr; ee_memory_map_stub #( .USEG_SHADOW_WORDS_PARAM(USEG_SHADOW_WORDS_PARAM) ) u_map ( .clk(clk), .rst_n(rst_n), .ee_rd_en (ee_cpu_rd_en), .ee_rd_addr(ee_cpu_rd_addr), .ee_rd_data(ee_cpu_rd_data), .ee_rd_valid(ee_cpu_rd_valid), .ee_wr_en (ee_cpu_wr_en), .ee_wr_addr(ee_cpu_wr_addr), .ee_wr_data(ee_cpu_wr_data), .ee_wr_be (ee_cpu_wr_be), .dmac_rd_en(dmac_mem_rd_en), .dmac_rd_addr(dmac_mem_rd_addr), .dmac_rd_data(map_to_dmac_rd_data), .dmac_rd_valid(map_to_dmac_rd_valid), .bios_rd_en (bios_rd_en), .bios_rd_addr(bios_rd_addr_full), .bios_rd_data(bios_rd_data), .bios_rd_valid(bios_rd_valid), .ram_rd_en(map_ram_rd_en), .ram_rd_addr(map_ram_rd_addr), .ram_rd_data(ram_rd_data), .ram_rd_valid(ram_rd_valid), .bridge_wr_en(1'b0), .bridge_wr_addr(32'd0), .bridge_wr_data(128'd0), .bridge_wr_be(16'd0), .bridge_master_id(8'd0), .ram_wr_en(), .ram_wr_addr(), .ram_wr_data(), .ram_wr_be(), .ram_master_id(), .ee_dmac_ch2_wr_en (dmac_reg_wr_en), .ee_dmac_ch2_wr_addr(dmac_reg_offset), .ee_dmac_ch2_wr_data(dmac_reg_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), .ee_gs_priv_wr_en (map_gs_priv_wr_en), .ee_gs_priv_wr_addr(map_gs_priv_wr_addr), .ee_gs_priv_wr_data(map_gs_priv_wr_data), .ee_gs_priv_wr_be (map_gs_priv_wr_be), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); assign ram_rd_en = map_ram_rd_en; assign ram_rd_addr = map_ram_rd_addr[RAM_ADDR_W-1:0]; // --------------------------------------------------------------------- // ee_core_stub // --------------------------------------------------------------------- logic [31:0] core_pc; logic core_trap; ee_core_stub #( .PC_RESET(32'hBFC0_0000), .STRICT_UNSUPPORTED(1'b0) ) u_core ( .clk(clk), .rst_n(rst_n), .go_i(core_go), .map_rd_en (ee_cpu_rd_en), .map_rd_addr(ee_cpu_rd_addr), .map_rd_data(ee_cpu_rd_data), .map_rd_valid(ee_cpu_rd_valid), .map_wr_en (ee_cpu_wr_en), .map_wr_addr(ee_cpu_wr_addr), .map_wr_data(ee_cpu_wr_data), .map_wr_be (ee_cpu_wr_be), .cpu_irq(1'b0), .halt_o(core_halt), .pc_o (core_pc), .trap_o(core_trap), .trap_pc_o(), .trap_instr_o(), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); // --------------------------------------------------------------------- // gif_packed_stub // --------------------------------------------------------------------- logic gif_in_ready; logic [7:0] gif_gif_reg_num; logic gif_gif_reg_wr_en; logic [63:0] gif_gif_reg_data; logic gif_image_data_valid; logic [127:0] gif_image_data; logic gif_image_data_last; logic xfer_data_ready; // Ch172 — raster FIFO full from gs_stub, fed back into gif_packed_stub. logic gs_raster_fifo_full; gif_packed_stub #(.REAL_AD_REG_MAP(1'b1)) u_gif ( .clk(clk), .rst_n(rst_n), .in_valid(dmac_gif_valid), .in_data(dmac_gif_data), .in_last(dmac_gif_last), .in_ready(gif_in_ready), .image_data_valid(gif_image_data_valid), .image_data(gif_image_data), .image_data_last(gif_image_data_last), .image_data_ready(xfer_data_ready), .raster_fifo_full(gs_raster_fifo_full), .gs_wr_en(), .gs_wr_addr(), .gs_wr_data(), .gif_reg_wr_en(gif_gif_reg_wr_en), .gif_reg_num(gif_gif_reg_num), .gif_reg_data(gif_gif_reg_data), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); // DMAC ready follows gif_packed_stub's in_ready directly (Ch110 // image-xfer backpressure propagates through gif_packed_stub). assign dmac_gif_ready = gif_in_ready; // --------------------------------------------------------------------- // gs_stub — PSMCT32 raster, swizzled // --------------------------------------------------------------------- logic priv_reg_wr_en; logic [15:0] priv_reg_wr_addr; logic [63:0] priv_reg_wr_data; logic [63:0] pmode_q, dispfb1_q, display1_q; logic [63:0] bitbltbuf_q, trxpos_q, trxreg_q, trxdir_q; logic trxdir_wr_q; logic raster_pixel_emit; logic [63:0] raster_pixel_color_q; logic [31:0] raster_pixel_fb_addr_q; logic [3:0] raster_pixel_be_q; logic [31:0] raster_pixel_mask_q; // Brick 1 — texture-sampler read port out of gs_stub. Wired to // vram_stub's SECOND read port (read2) below. In this top there is // no clut_loader_stub instantiated (clut_enable=0 at the PCRTC), so // read2 is dedicated to the texel fetch; the mux contract (CLUT load // at TEX0 commit vs texel fetch during scan) is documented at the // read2 wiring site. logic gs_tex_rd_en; logic [31:0] gs_tex_rd_addr; logic [31:0] gs_tex_rd_data; // Brick 2a — dest-framebuffer read port for alpha blending. Wired // to vram_stub.read2 below, arbitrated with the texel-fetch port. // vram_stub.read2 is COMBINATIONAL, so FB_RD_REGISTERED defaults to // 0 (dest data valid the same cycle the S2 address is presented). logic gs_fb_rd_en; logic [31:0] gs_fb_rd_addr; logic [31:0] gs_fb_rd_data; // Brick 2b — Z-buffer stored-Z read port. Wired to vram_stub.read2 // below, arbitrated with the texel-fetch + alpha dest-fb ports. // vram_stub.read2 is COMBINATIONAL, so Z_RD_REGISTERED defaults to 0. logic gs_z_rd_en; logic [31:0] gs_z_rd_addr; logic [31:0] gs_z_rd_data; gs_stub #( .PSMCT32_SWIZZLE(PSMCT32_SWIZZLE) ) u_gs ( .clk(clk), .rst_n(rst_n), .reg_wr_en (priv_reg_wr_en), .reg_wr_addr(priv_reg_wr_addr), .reg_wr_data(priv_reg_wr_data), .gif_reg_wr_en(gif_gif_reg_wr_en), .gif_reg_num (gif_gif_reg_num), .gif_reg_data (gif_gif_reg_data), .bg_r(), .bg_g(), .bg_b(), .pmode_q(pmode_q), .dispfb1_q(dispfb1_q), .display1_q(display1_q), .prim_q(), .rgbaq_q(), .xyz2_q(), .xyzf2_q(), .frame_1_q(), .zbuf_1_q(), .tex0_1_q(), .tex0_1_cbp_q(), .tex0_1_cpsm_q(), .tex0_1_csm_q(), .tex0_1_csa_q(), .tex0_1_cld_q(), .tex0_1_wr_q(), .bitbltbuf_q(bitbltbuf_q), .trxpos_q(trxpos_q), .trxreg_q(trxreg_q), .trxdir_q(trxdir_q), .trxdir_wr_q(trxdir_wr_q), .prim_complete(), .prim_complete_count(), .prim_v0_q(), .prim_v1_q(), .prim_v2_q(), .prim_color_q(), .prim_color_v0_q(), .prim_color_v1_q(), .prim_color_v2_q(), .prim_v0_decoded_q(), .prim_v1_decoded_q(), .prim_v2_decoded_q(), .prim_v0_color_decoded_q(), .prim_v1_color_decoded_q(), .prim_v2_color_decoded_q(), .pixel_emit(), .pixel_emit_count(), .pixel_x_q(), .pixel_y_q(), .pixel_color_q(), .pixel_fbp_q(), .pixel_fbw_q(), .pixel_psm_q(), .pixel_fb_addr_q(), .raster_pixel_emit(raster_pixel_emit), .raster_pixel_emit_count(), .raster_pixel_x_q(), .raster_pixel_y_q(), .raster_pixel_color_q(raster_pixel_color_q), .raster_pixel_fb_addr_q(raster_pixel_fb_addr_q), .raster_pixel_be_q(raster_pixel_be_q), .raster_pixel_mask_q(raster_pixel_mask_q), .raster_pixel_psm_q(), .raster_active(), .raster_overflow(raster_overflow), .raster_fifo_full(gs_raster_fifo_full), .raster_degenerate(), .tex_rd_en (gs_tex_rd_en), .tex_rd_addr(gs_tex_rd_addr), .tex_rd_data(gs_tex_rd_data), // Ch296 — PSMCT32-only top: no CLUT instantiated, the PSMT8 index // path is never selected (s1_tex_active gates it on PSM==0x13). // Tie the lookup data to 0; leave the index output open. .clut_rd_idx (), .clut_rd_data(32'd0), .clut_load_busy(1'b0), .fb_rd_en (gs_fb_rd_en), .fb_rd_addr(gs_fb_rd_addr), .fb_rd_data(gs_fb_rd_data), .z_rd_en (gs_z_rd_en), .z_rd_addr(gs_z_rd_addr), .z_rd_data(gs_z_rd_data), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); // --------------------------------------------------------------------- // ee_gs_priv_bridge_stub // --------------------------------------------------------------------- ee_gs_priv_bridge_stub u_priv_bridge ( .clk(clk), .rst_n(rst_n), .ee_wr_en (map_gs_priv_wr_en), .ee_wr_addr(map_gs_priv_wr_addr), .ee_wr_data(map_gs_priv_wr_data), .ee_wr_be (map_gs_priv_wr_be), .gs_reg_wr_en (priv_reg_wr_en), .gs_reg_wr_addr(priv_reg_wr_addr), .gs_reg_wr_data(priv_reg_wr_data) ); // --------------------------------------------------------------------- // gif_image_xfer_stub — idle in Ch123 (no TRXDIR/IMAGE), but // instantiated for symmetry. The TRXDIR-driven Ch124 demo would // turn it load-bearing. // --------------------------------------------------------------------- logic xfer_we; logic [31:0] xfer_waddr; logic [31:0] xfer_wdata; logic [3:0] xfer_wbe; logic [31:0] xfer_wmask; logic xfer_busy; gif_image_xfer_stub #( .PSMCT32_SWIZZLE(PSMCT32_SWIZZLE) ) u_xfer ( .clk(clk), .rst_n(rst_n), .trxdir_wr_pulse(trxdir_wr_q), .trxdir(trxdir_q), .bitbltbuf(bitbltbuf_q), .trxpos(trxpos_q), .trxreg(trxreg_q), .data_valid(gif_image_data_valid), .data_qword(gif_image_data), .data_last (gif_image_data_last), .data_ready(xfer_data_ready), .vram_we (xfer_we), .vram_waddr(xfer_waddr), .vram_wdata(xfer_wdata), .vram_wbe (xfer_wbe), .vram_wmask(xfer_wmask), .busy (xfer_busy) ); // --------------------------------------------------------------------- // VRAM mux: xfer-OWNED when xfer.busy, raster-OWNED otherwise. // (Sequenced: in Ch123 raster fills exclusively; xfer never fires. // In a future TRXDIR variant the mux still works — payload upload // finishes before raster starts.) // --------------------------------------------------------------------- logic vram_we_mux; logic [31:0] vram_waddr_mux; logic [31:0] vram_wdata_mux; logic [3:0] vram_wbe_mux; logic [31:0] vram_wmask_mux; assign vram_we_mux = xfer_busy ? xfer_we : raster_pixel_emit; assign vram_waddr_mux = xfer_busy ? xfer_waddr : raster_pixel_fb_addr_q; assign vram_wdata_mux = xfer_busy ? xfer_wdata : raster_pixel_color_q[31:0]; assign vram_wbe_mux = xfer_busy ? xfer_wbe : raster_pixel_be_q; assign vram_wmask_mux = xfer_busy ? xfer_wmask : raster_pixel_mask_q; logic [31:0] vram_raddr; logic [31:0] vram_rdata; // --------------------------------------------------------------------- // Brick 1 — read2 (second VRAM read port) MUX. // // read2 is shared between two consumers that are sequenced in time: // - clut_loader_stub : VRAM→CLUT copy at TEX0 commit (BEFORE the // raster scan). NOT instantiated in this top // (PCRTC clut_enable=0), so it never drives // read2 here. // - gs_stub texel fetch : during the raster SCAN, one read per // inside pixel of a textured SPRITE. // Because CLUT load completes before scanout begins, a simple // gs_tex_rd_en select is collision-free. When a future variant adds // clut_loader, extend this select: read2_addr = clut_active ? // clut_rd_addr : gs_tex_rd_addr. // vram_stub's read2 is COMBINATIONAL; gs_stub presents the address // from a registered S1 stage and consumes tex_rd_data one cycle // later, so the effective latency matches TEX_RD_LATENCY=1. // --------------------------------------------------------------------- // // Brick 2a — THIRD potential read2 consumer: the alpha-blend // dest-fb read (gs_fb_rd_en/gs_fb_rd_addr). A flat alpha-blended // SPRITE never textures, so gs_tex_rd_en and gs_fb_rd_en are // mutually exclusive (gs_stub.new_abe_active requires // !close_tme_effective). The combinational read2_data is fanned out // to both consumers; only the active one's address selects the mux. // // Brick 2b — FOURTH potential read2 consumer: the Z-buffer stored-Z // read (gs_z_rd_en/gs_z_rd_addr). A flat Z-tested SPRITE never // textures and never alpha-blends (gs_stub.new_zte_active requires // !close_tme_effective && !new_abe_active), so the four read2 // consumers are mutually exclusive by feature. logic [31:0] vram_read2_addr; logic [31:0] vram_read2_data; assign vram_read2_addr = gs_tex_rd_en ? gs_tex_rd_addr : gs_fb_rd_en ? gs_fb_rd_addr : gs_z_rd_en ? gs_z_rd_addr : 32'd0; assign gs_tex_rd_data = vram_read2_data; assign gs_fb_rd_data = vram_read2_data; assign gs_z_rd_data = vram_read2_data; // synthesis translate_off always_ff @(posedge clk) begin if (rst_n && gs_tex_rd_en && gs_fb_rd_en) $error("Brick2a: read2 overlap @%0t — texel fetch and alpha dest-fb read both active; one read is being dropped (must be mutually exclusive by texturing).", $time); if (rst_n && gs_z_rd_en && (gs_tex_rd_en || gs_fb_rd_en)) $error("Brick2b: read2 overlap @%0t — Z-buffer read collides with another consumer; one read is being dropped (Z-tested flat sprite must be mutually exclusive with texel/alpha).", $time); end // synthesis translate_on vram_stub #(.BYTES(VRAM_BYTES)) u_vram ( .clk(clk), .rst_n(rst_n), .write_en (vram_we_mux), .write_addr(vram_waddr_mux), .write_data(vram_wdata_mux), .write_be (vram_wbe_mux), .write_mask(vram_wmask_mux), .read_addr (vram_raddr), .read_data (vram_rdata), .read2_addr(vram_read2_addr), .read2_data(vram_read2_data) ); // --------------------------------------------------------------------- // gs_pcrtc_stub — PSMCT32 swizzled scanout // --------------------------------------------------------------------- logic end_of_frame; gs_pcrtc_stub #( .H_ACTIVE(H_ACTIVE), .H_FRONT(1), .H_SYNC(1), .H_BACK(1), .V_ACTIVE(V_ACTIVE), .V_FRONT(1), .V_SYNC(1), .V_BACK(1), .PSMCT32_SWIZZLE(PSMCT32_SWIZZLE) ) u_pcrtc ( .clk(clk), .rst_n(rst_n), .pmode_q (pmode_q), .dispfb1_q (dispfb1_q), .display1_q (display1_q), .vram_read_addr(vram_raddr), .vram_read_data(vram_rdata), .clut_enable (1'b0), .clut_csa (5'd0), .clut_read_idx (), .clut_read_data(32'd0), .hsync(hsync), .vsync(vsync), .de(de), .r(r), .g(g), .b(b), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); // gs_pcrtc_stub doesn't expose end_of_frame as a port; the Ch123 TB // taps it via hierarchical ref. For the top wrapper we synthesize an // equivalent edge by watching vsync rise. logic vsync_d; always_ff @(posedge clk) begin if (!rst_n) vsync_d <= 1'b0; else vsync_d <= vsync; end assign end_of_frame = vsync && !vsync_d; // --------------------------------------------------------------------- // Sticky status outputs. // --------------------------------------------------------------------- logic dma_done_seen_q; logic frame_seen_q; always_ff @(posedge clk) begin if (!rst_n) begin dma_done_seen_q <= 1'b0; frame_seen_q <= 1'b0; end else begin if (dmac_ev_valid && (dmac_ev_event == EV_DMA_DONE)) dma_done_seen_q <= 1'b1; if (end_of_frame) frame_seen_q <= 1'b1; end end assign dma_done_seen = dma_done_seen_q; assign frame_seen = frame_seen_q; // --------------------------------------------------------------------- // Ch174 — event toggles for HPS-visible counters. // --------------------------------------------------------------------- logic frame_toggle_q; logic dma_done_toggle_q; always_ff @(posedge clk) begin if (!rst_n) begin frame_toggle_q <= 1'b0; dma_done_toggle_q <= 1'b0; end else begin if (end_of_frame) frame_toggle_q <= ~frame_toggle_q; if (dmac_ev_valid && (dmac_ev_event == EV_DMA_DONE)) dma_done_toggle_q <= ~dma_done_toggle_q; end end assign frame_toggle = frame_toggle_q; assign dma_done_toggle = dma_done_toggle_q; endmodule : top_psmct32_raster_demo