Files
retroDE_ps2/rtl/gif_gs/ee_gs_priv_bridge_stub.sv
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

132 lines
5.5 KiB
Systemverilog

// retroDE_ps2 — ee_gs_priv_bridge_stub (Ch111)
//
// Bridges 32-bit EE-MMIO writes targeting the GS privileged-
// register window at 0x1200_0000 into the 64-bit gs_stub.reg_wr_*
// port. Real PS2 driver code reaches PMODE / DISPFB1 / DISPLAY1
// (etc.) via 64-bit MIPS `sd` instructions; the EE microarch
// breaks each `sd` into a pair of 32-bit `sw` operations to the
// low+high halves of the 8-byte register slot. This bridge does
// the inverse — it watches the 32-bit EE write stream, latches a
// 64-bit shadow per 8-byte slot, and fires a gs_stub.reg_wr_*
// pulse on EVERY half-write with the running 64-bit shadow value.
//
// Scope:
// - One shared 64-bit shadow + an offset[15:3] tag identifying
// the currently-tracked 8-byte slot. Sequential writes to the
// SAME slot accumulate (low first, then high → final shadow
// has both halves correct on the second fire). Switching
// slots resets the shadow to zero so partial-half writes to
// a fresh slot don't carry stale data from a different reg.
// - Each EE half-write fires a gs_stub.reg_wr_* pulse with the
// 8-byte-aligned offset (`{ee_wr_addr[15:3], 3'b000}`) and
// the FULL 64-bit shadow. Single-half writes (e.g. PMODE
// where only the low byte matters) work because the high
// half stays zero and gs_stub's latch sees the right value.
// - 32-bit EE write width (matches ee_memory_map_stub's
// ee_wr_*-port surface). **Full-word writes only**:
// `ee_wr_be` MUST be 4'b1111 on every accepted write. Byte-
// lane merging into the 64-bit shadow is intentionally NOT
// modelled here — control-plane GS registers (PMODE/
// DISPFB1/DISPLAY1/etc.) are always written as full 32-bit
// halves of an `sd`, and constraining the contract keeps the
// shadow + commit logic small. A simulation-time `$error`
// fires if a non-full be is presented; a future chapter can
// widen the bridge to per-byte merge if/when a real driver
// pattern needs sub-word writes here.
//
// Wiring contract (TB-level for Ch111):
// ee_wr_en ← TB EE-MMIO write strobe at 0x12000000+offset
// ee_wr_addr ← 16-bit offset within the GS priv window (= EE
// phys addr [15:0]; the upper EE-window decode
// lives in the test bench / memory map)
// ee_wr_data ← 32-bit EE data (one of two halves of a 64-bit
// GS register)
// ee_wr_be ← 4-bit per-byte enable (typically 4'b1111)
//
// The bridge does NOT participate in EE reads. The gs_stub
// privileged-register port is write-only at this scope, matching
// the limited read coverage of the GS priv block in the rest of
// the design.
`timescale 1ns/1ps
module ee_gs_priv_bridge_stub
(
input logic clk,
input logic rst_n,
// EE-MMIO write port (32-bit data, 16-bit offset within
// 0x1200_0000 window).
input logic ee_wr_en,
input logic [15:0] ee_wr_addr,
input logic [31:0] ee_wr_data,
input logic [3:0] ee_wr_be,
// gs_stub privileged-register port (16-bit offset, 64-bit data).
output logic gs_reg_wr_en,
output logic [15:0] gs_reg_wr_addr,
output logic [63:0] gs_reg_wr_data
);
// Shared 64-bit shadow + the 13-bit offset[15:3] tag of the
// currently-tracked slot. Resets to zero on rst_n or on a
// switch to a different slot.
logic [63:0] shadow;
logic [12:0] shadow_tag;
logic shadow_valid;
logic [12:0] cur_tag;
logic cur_is_high;
logic [63:0] new_shadow;
assign cur_tag = ee_wr_addr[15:3];
assign cur_is_high = ee_wr_addr[2];
always_comb begin
logic [63:0] base_shadow;
// If the EE write hits the same 8-byte slot we're already
// tracking, merge into the existing shadow. Otherwise start
// a fresh shadow at zero (the un-touched half stays 0 — that's
// safe for the demo where we always write the half that
// matters first; high-only writes are not used in this TB
// family).
base_shadow = (shadow_valid && shadow_tag == cur_tag) ? shadow
: 64'd0;
if (cur_is_high)
new_shadow = {ee_wr_data, base_shadow[31:0]};
else
new_shadow = {base_shadow[63:32], ee_wr_data};
end
always_ff @(posedge clk) begin
if (!rst_n) begin
shadow <= 64'd0;
shadow_tag <= 13'd0;
shadow_valid <= 1'b0;
gs_reg_wr_en <= 1'b0;
gs_reg_wr_addr <= 16'd0;
gs_reg_wr_data <= 64'd0;
end else begin
gs_reg_wr_en <= 1'b0;
if (ee_wr_en) begin
// Contract: full-word writes only. Sub-word
// (per-byte) merging into the 64-bit shadow is
// out of scope at Ch111. Catch contract violations
// loudly so a future driver pattern that needs
// byte-lane writes is forced to widen the bridge.
if (ee_wr_be !== 4'b1111) begin
$error("ee_gs_priv_bridge_stub: ee_wr_be=%b — only 4'b1111 supported (full-word writes); offset=0x%04x data=0x%08h",
ee_wr_be, ee_wr_addr, ee_wr_data);
end
shadow <= new_shadow;
shadow_tag <= cur_tag;
shadow_valid <= 1'b1;
gs_reg_wr_en <= 1'b1;
gs_reg_wr_addr <= {cur_tag, 3'b000};
gs_reg_wr_data <= new_shadow;
end
end
end
endmodule : ee_gs_priv_bridge_stub