ec82764bef
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>
201 lines
8.6 KiB
Systemverilog
201 lines
8.6 KiB
Systemverilog
// retroDE_ps2 — vram_normalize_pkg (Ch155)
|
|
//
|
|
// Writer-side normalization for `vram_bram_stub`. The new BRAM-friendly
|
|
// VRAM (Ch154) requires word-aligned writes (`write_addr[1:0] == 0`)
|
|
// with payload pre-shifted into the selected byte lane(s) and
|
|
// `write_be` set per byte. Today's writer-side RTL emits at sub-word
|
|
// boundaries for PSMCT16 (halfword), PSMT8 (byte), and PSMT4 (nibble);
|
|
// this package's `normalize_write` function bridges the contract.
|
|
//
|
|
// Codex Ch155 framing: "Add a small helper module or function for
|
|
// VRAM write normalization: input: natural byte address, PSM,
|
|
// pixel/index payload, old byte for PSMT4 if needed; output:
|
|
// word-aligned write_addr, shifted write_data, write_be."
|
|
//
|
|
// Scope (Ch155):
|
|
// - Function is defined + standalone-verified for all 4 PSMs.
|
|
// - NOT yet applied inside `gs_stub.raster_pixel_emit` or
|
|
// `gif_image_xfer_stub`. The PSMT4 case needs a read-then-write
|
|
// pipeline upstream (to source `old_byte`); that's a Ch156+
|
|
// RTL plumbing chapter. CT32/CT16/T8 cases are pure-comb and
|
|
// can be plumbed in as soon as the wiring lands.
|
|
//
|
|
// Pure-comb function — no RTL pipelining inside the function itself.
|
|
// Callers that need a read-then-write pipeline (PSMT4) own that
|
|
// pipelining and pass the read result as `old_byte`.
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
package vram_normalize_pkg;
|
|
|
|
// GS PSM codes (subset relevant to VRAM writes).
|
|
localparam logic [5:0] PSM_PSMCT32 = 6'h00;
|
|
localparam logic [5:0] PSM_PSMCT16 = 6'h02;
|
|
localparam logic [5:0] PSM_PSMT8 = 6'h13;
|
|
localparam logic [5:0] PSM_PSMT4 = 6'h14;
|
|
|
|
typedef struct packed {
|
|
logic [31:0] write_addr; // word-aligned
|
|
logic [31:0] write_data; // payload shifted to lane
|
|
logic [3:0] write_be; // per-byte write enable
|
|
} norm_out_t;
|
|
|
|
// ----------------------------------------------------------------
|
|
// normalize_write — pure-comb writer-side normalization.
|
|
//
|
|
// Inputs:
|
|
// byte_addr — natural byte address as the legacy writers
|
|
// already emit. CT32 callers must already pass
|
|
// a word-aligned address. CT16 callers may pass
|
|
// a halfword address (byte_addr[1] selects low
|
|
// or high halfword). T8/T4 callers may pass any
|
|
// byte address.
|
|
// psm — GS PSM code (use one of the localparams above).
|
|
// payload — payload bits in the LSBs:
|
|
// CT32 → payload[31:0] is the full ABGR word.
|
|
// CT16 → payload[15:0] is the RGB5A1 halfword.
|
|
// T8 → payload[ 7:0] is the byte index.
|
|
// T4 → payload[ 3:0] is the nibble index.
|
|
// nibble_hi — T4 only. 0 = splice payload[3:0] into the LOW
|
|
// nibble of the byte at byte_addr, 1 = HIGH.
|
|
// Ignored for CT32/CT16/T8.
|
|
// old_byte — T4 only. Current value of mem[byte_addr]; the
|
|
// function splices the new nibble into this byte
|
|
// to preserve the other nibble. Ignored for
|
|
// CT32/CT16/T8.
|
|
//
|
|
// Output: word-aligned write_addr + shifted write_data + write_be.
|
|
//
|
|
// For PSMs other than CT32/CT16/T8/T4 the function returns a
|
|
// dropped write (write_be = 4'b0000, write_data = 32'd0); this
|
|
// matches `vram_stub`'s "unsupported PSMs are silent no-ops"
|
|
// posture (Ch95).
|
|
// ----------------------------------------------------------------
|
|
function automatic norm_out_t normalize_write(
|
|
input logic [31:0] byte_addr,
|
|
input logic [5:0] psm,
|
|
input logic [31:0] payload,
|
|
input logic nibble_hi,
|
|
input logic [7:0] old_byte
|
|
);
|
|
norm_out_t r;
|
|
|
|
// Word-aligned base address common to every PSM.
|
|
r.write_addr = byte_addr & ~32'd3;
|
|
|
|
unique case (psm)
|
|
// ------------------------------------------------------
|
|
// PSMCT32 — natural 32-bit-aligned write. byte_addr MUST
|
|
// already be word-aligned; if it isn't, the function
|
|
// produces a dropped write so the BRAM module never sees
|
|
// the misuse.
|
|
// ------------------------------------------------------
|
|
PSM_PSMCT32: begin
|
|
if (byte_addr[1:0] != 2'b00) begin
|
|
r.write_data = 32'd0;
|
|
r.write_be = 4'b0000;
|
|
end else begin
|
|
r.write_data = payload;
|
|
r.write_be = 4'b1111;
|
|
end
|
|
end
|
|
|
|
// ------------------------------------------------------
|
|
// PSMCT16 — halfword write. byte_addr[1] picks low or
|
|
// high halfword; byte_addr[0] MUST be 0.
|
|
// ------------------------------------------------------
|
|
PSM_PSMCT16: begin
|
|
if (byte_addr[0] != 1'b0) begin
|
|
r.write_data = 32'd0;
|
|
r.write_be = 4'b0000;
|
|
end else if (byte_addr[1] == 1'b0) begin
|
|
r.write_data = {16'd0, payload[15:0]};
|
|
r.write_be = 4'b0011;
|
|
end else begin
|
|
r.write_data = {payload[15:0], 16'd0};
|
|
r.write_be = 4'b1100;
|
|
end
|
|
end
|
|
|
|
// ------------------------------------------------------
|
|
// PSMT8 — single byte at any byte address. byte_addr[1:0]
|
|
// selects which of the 4 byte lanes gets the byte.
|
|
// ------------------------------------------------------
|
|
PSM_PSMT8: begin
|
|
unique case (byte_addr[1:0])
|
|
2'b00: begin
|
|
r.write_data = {24'd0, payload[7:0]};
|
|
r.write_be = 4'b0001;
|
|
end
|
|
2'b01: begin
|
|
r.write_data = {16'd0, payload[7:0], 8'd0};
|
|
r.write_be = 4'b0010;
|
|
end
|
|
2'b10: begin
|
|
r.write_data = {8'd0, payload[7:0], 16'd0};
|
|
r.write_be = 4'b0100;
|
|
end
|
|
2'b11: begin
|
|
r.write_data = {payload[7:0], 24'd0};
|
|
r.write_be = 4'b1000;
|
|
end
|
|
endcase
|
|
end
|
|
|
|
// ------------------------------------------------------
|
|
// PSMT4 — nibble splice. The function takes `old_byte` as
|
|
// the current value of mem[byte_addr] and produces a
|
|
// full-byte write at that address containing the new
|
|
// byte: (old_byte & ~nibble_mask) | (new_nibble in lane).
|
|
//
|
|
// The caller is responsible for sourcing `old_byte` —
|
|
// typically a 1-cycle read of mem[byte_addr] before the
|
|
// write fires. Ch156+ inserts that read pipeline inside
|
|
// gs_stub.raster_pixel_emit + gif_image_xfer_stub.
|
|
//
|
|
// byte_addr[1:0] selects the byte lane in the 32-bit
|
|
// word; nibble_hi selects which nibble of that byte gets
|
|
// the new value.
|
|
// ------------------------------------------------------
|
|
PSM_PSMT4: begin
|
|
logic [7:0] new_byte;
|
|
if (nibble_hi)
|
|
new_byte = {payload[3:0], old_byte[3:0]};
|
|
else
|
|
new_byte = {old_byte[7:4], payload[3:0]};
|
|
|
|
unique case (byte_addr[1:0])
|
|
2'b00: begin
|
|
r.write_data = {24'd0, new_byte};
|
|
r.write_be = 4'b0001;
|
|
end
|
|
2'b01: begin
|
|
r.write_data = {16'd0, new_byte, 8'd0};
|
|
r.write_be = 4'b0010;
|
|
end
|
|
2'b10: begin
|
|
r.write_data = {8'd0, new_byte, 16'd0};
|
|
r.write_be = 4'b0100;
|
|
end
|
|
2'b11: begin
|
|
r.write_data = {new_byte, 24'd0};
|
|
r.write_be = 4'b1000;
|
|
end
|
|
endcase
|
|
end
|
|
|
|
// ------------------------------------------------------
|
|
// Unsupported PSM → drop the write. Matches vram_stub's
|
|
// Ch95 stance.
|
|
// ------------------------------------------------------
|
|
default: begin
|
|
r.write_data = 32'd0;
|
|
r.write_be = 4'b0000;
|
|
end
|
|
endcase
|
|
|
|
return r;
|
|
endfunction
|
|
|
|
endpackage : vram_normalize_pkg
|