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>
203 lines
8.1 KiB
Systemverilog
203 lines
8.1 KiB
Systemverilog
// 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
|