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>
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
// retroDE_ps2 — sif_dma_ee_ram_bridge_stub
|
||||
//
|
||||
// Width-adapting bridge from a 32-bit SIF DMA endpoint (IOP→EE egress)
|
||||
// to the 128-bit EE memory map. Accumulates four incoming 32-bit beats
|
||||
// into a qword and issues one qword write through ee_memory_map_stub's
|
||||
// bridge write port.
|
||||
//
|
||||
// Mirror of sif_dma_iop_ram_bridge_stub, but in the other direction
|
||||
// (words → qword, EE-side landing).
|
||||
//
|
||||
// Contract refs:
|
||||
// docs/contracts/sif.md (DMA-linked data movement endpoints)
|
||||
// docs/contracts/memory.md (EE RAM is 128-bit qword-aligned)
|
||||
//
|
||||
// Handshake (upstream, from DMAC ep_* port or equivalent):
|
||||
// in_valid / in_data[31:0] / in_last / in_ready
|
||||
// Bridge asserts in_ready while it's accumulating (up to the 3rd beat
|
||||
// of a quad, inclusive). It drops in_ready during the one-cycle emit
|
||||
// that follows the 4th beat, so the DMAC naturally stalls with
|
||||
// back-pressure for a single cycle between qwords.
|
||||
//
|
||||
// Handshake (downstream, to ee_memory_map_stub bridge-write port):
|
||||
// bridge_wr_en / bridge_wr_addr[31:0] / bridge_wr_data[127:0] /
|
||||
// bridge_wr_be[15:0] / bridge_master_id[7:0]
|
||||
//
|
||||
// Data layout (little-endian):
|
||||
// beat 0 → bridge_wr_data[31:0]
|
||||
// beat 1 → bridge_wr_data[63:32]
|
||||
// beat 2 → bridge_wr_data[95:64]
|
||||
// beat 3 → bridge_wr_data[127:96]
|
||||
// qword address advances DEST_BASE_ADDR by 16 per emit.
|
||||
//
|
||||
// Partial quad on in_last:
|
||||
// If `in_last` arrives before the 4th beat of a quad, the bridge
|
||||
// emits the partial qword with wr_be masked to cover only the bytes
|
||||
// that were actually accepted. Not exercised by the current TB (BCR
|
||||
// is chosen to be a multiple of 4), but kept defensively.
|
||||
//
|
||||
// Payload-complete indication (last_seen_o):
|
||||
// Level-held output, set when `in_last && accept_beat` fires on the
|
||||
// upstream handshake. Intended for EE-side protocol combiners that
|
||||
// need to gate an ack on "payload fully moved" independently of when
|
||||
// the IOP posted a control doorbell. Latch stays high until reset —
|
||||
// this mirrors sif_dma_stub.last_seen.
|
||||
//
|
||||
// Parameters:
|
||||
// DEST_BASE_ADDR — byte offset where the first qword lands. Advances
|
||||
// by 16 per emit for the life of the transfer.
|
||||
// MASTER_ID — bridge's identity for MEM / EE-map trace attribution
|
||||
// (default 5 = SIF EE-side bridge).
|
||||
//
|
||||
// Non-goals:
|
||||
// - multiple in-flight qwords
|
||||
// - arbitration against other bridge writers on the EE map's write path
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module sif_dma_ee_ram_bridge_stub
|
||||
#(
|
||||
parameter logic [31:0] DEST_BASE_ADDR = 32'h0000_0000,
|
||||
parameter logic [7:0] MASTER_ID = 8'd5
|
||||
) (
|
||||
input logic clk,
|
||||
input logic rst_n,
|
||||
|
||||
// Upstream (DMAC endpoint side)
|
||||
input logic in_valid,
|
||||
input logic [31:0] in_data,
|
||||
input logic in_last,
|
||||
output logic in_ready,
|
||||
|
||||
// Downstream (EE map bridge-write port)
|
||||
output logic bridge_wr_en,
|
||||
output logic [31:0] bridge_wr_addr,
|
||||
output logic [127:0] bridge_wr_data,
|
||||
output logic [15:0] bridge_wr_be,
|
||||
output logic [7:0] bridge_master_id,
|
||||
|
||||
// Payload-complete indication (level, latched). Consumers gate on
|
||||
// "full payload landed" without needing to count beats.
|
||||
output logic last_seen_o,
|
||||
|
||||
// Ch239 — single-cycle "rewind" pulse. When asserted (and the
|
||||
// bridge is idle in S_ACCUM with no beat in flight), the running
|
||||
// `wr_offset` returns to 0 so the NEXT emit lands at
|
||||
// DEST_BASE_ADDR. Lets a producer that wants single-slot buffer
|
||||
// semantics (e.g. a libpad-style pad packet) overwrite the same
|
||||
// 16-byte slot on every transfer instead of streaming forward.
|
||||
// Existing producers that don't need this leave it tied to 1'b0
|
||||
// and the bridge keeps its streaming behaviour exactly as before.
|
||||
// Pulse must be asserted between transfers; firing mid-transfer
|
||||
// (`state==S_EMIT` or `pos != 0`) is illegal and logged as a
|
||||
// sim-only `$error` (no defensive RTL gating — keeps the path
|
||||
// single-purpose). See `docs/contracts/sio2_pad.md` Ch239.
|
||||
input logic rewind_i = 1'b0
|
||||
);
|
||||
|
||||
typedef enum logic [0:0] {
|
||||
S_ACCUM = 1'b0,
|
||||
S_EMIT = 1'b1
|
||||
} state_e;
|
||||
|
||||
state_e state;
|
||||
logic [127:0] acc_data;
|
||||
logic [15:0] acc_be;
|
||||
logic [1:0] pos; // 0..3 within qword
|
||||
logic [31:0] wr_offset; // running byte offset
|
||||
|
||||
assign in_ready = (state == S_ACCUM);
|
||||
assign bridge_master_id = MASTER_ID;
|
||||
|
||||
logic accept_beat;
|
||||
assign accept_beat = in_valid && in_ready;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Accumulator / state machine
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
state <= S_ACCUM;
|
||||
acc_data <= 128'd0;
|
||||
acc_be <= 16'd0;
|
||||
pos <= 2'd0;
|
||||
wr_offset <= 32'd0;
|
||||
end else begin
|
||||
// Ch239 — between-transfer rewind. Resets only the
|
||||
// streaming offset; `acc_data`/`acc_be`/`pos` are
|
||||
// already 0 after every emit's tail. Misuse (rewind
|
||||
// pulse during a transfer) is reported via sim $error
|
||||
// below; the RTL still applies the rewind because the
|
||||
// guard would otherwise hide producer-side bugs.
|
||||
if (rewind_i) wr_offset <= 32'd0;
|
||||
|
||||
unique case (state)
|
||||
S_ACCUM: begin
|
||||
if (accept_beat) begin
|
||||
// Place the incoming word in slot `pos` and mark
|
||||
// its four bytes enabled.
|
||||
acc_data[pos*32 +: 32] <= in_data;
|
||||
acc_be[pos*4 +: 4] <= 4'b1111;
|
||||
|
||||
if (pos == 2'd3 || in_last) begin
|
||||
state <= S_EMIT;
|
||||
end else begin
|
||||
pos <= pos + 2'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
S_EMIT: begin
|
||||
// Single-cycle emit; bridge_wr_en is combinationally
|
||||
// tied to state. Advance qword offset, reset slot /
|
||||
// accumulator for the next quad. The Ch239 rewind
|
||||
// above runs first, so a `rewind_i` pulse coincident
|
||||
// with an emit cycle leaves wr_offset at 0 (no +16
|
||||
// increment) — but that combination is the illegal
|
||||
// "rewind mid-transfer" case and the $error below
|
||||
// catches it for the producer to fix.
|
||||
wr_offset <= wr_offset + 32'd16;
|
||||
acc_data <= 128'd0;
|
||||
acc_be <= 16'd0;
|
||||
pos <= 2'd0;
|
||||
state <= S_ACCUM;
|
||||
end
|
||||
|
||||
default: state <= S_ACCUM;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
`ifndef SYNTHESIS
|
||||
// Misuse detector — `rewind_i` while a transfer is in flight is
|
||||
// a producer-side bug. Caught here so the path stays clean.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n && rewind_i && (state != S_ACCUM || pos != 2'd0)) begin
|
||||
$error("[sif_dma_ee_ram_bridge_stub] illegal rewind_i mid-transfer (state=%0d pos=%0d)",
|
||||
state, pos);
|
||||
end
|
||||
end
|
||||
`endif
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Downstream write-port drive (combinational on state)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
assign bridge_wr_en = (state == S_EMIT);
|
||||
assign bridge_wr_addr = DEST_BASE_ADDR + wr_offset;
|
||||
assign bridge_wr_data = acc_data;
|
||||
assign bridge_wr_be = acc_be;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// last_seen_o: set once the upstream asserts in_last on a beat that
|
||||
// is actually accepted. Level-held until reset.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) last_seen_o <= 1'b0;
|
||||
else if (accept_beat && in_last) last_seen_o <= 1'b1;
|
||||
end
|
||||
|
||||
endmodule : sif_dma_ee_ram_bridge_stub
|
||||
Reference in New Issue
Block a user