// 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
