Files
retroDE_ps2/rtl/sif/sif_dma_iop_ram_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

140 lines
5.2 KiB
Systemverilog

// retroDE_ps2 — sif_dma_iop_ram_bridge_stub
//
// Width-adapting bridge from a 128-bit SIF DMA endpoint to the 32-bit
// IOP memory map. Splits each incoming qword into four 32-bit writes at
// consecutive physical addresses starting from DEST_BASE_ADDR.
//
// First real coupling between the SIF data-plane and the IOP side. NOT an
// IOP, not a DMAC, not a peer — just a width/ordering adapter.
//
// Contract refs:
// docs/contracts/sif.md (DMA-linked data movement endpoints)
// docs/contracts/iop.md (IOP-local RAM/I/O decode; writes land
// through the IOP memory map)
//
// Handshake (upstream, from DMAC ep_* port or equivalent):
// in_valid / in_data[127:0] / in_last / in_ready
// Bridge asserts in_ready only while idle. During the four-write
// expansion of a qword, in_ready drops — natural backpressure onto
// whatever's producing qwords.
//
// Handshake (downstream, to iop_memory_map_stub's bridge-write port):
// bridge_wr_en / bridge_wr_addr[31:0] / bridge_wr_data[31:0] /
// bridge_wr_be[3:0] / bridge_master_id[7:0]
// Addresses are physical (no kseg stripping) — the IOP map must treat
// this port's addresses differently from its CPU-side port.
//
// Data layout:
// Little-endian unpacking: in_data[31:0] -> DEST_BASE+0
// in_data[63:32] -> DEST_BASE+4
// in_data[95:64] -> DEST_BASE+8
// in_data[127:96] -> DEST_BASE+12
// Subsequent qwords append: DEST_BASE+16, +20, +24, +28, ...
//
// Parameters:
// DEST_BASE_ADDR — where the bridge starts writing. Persistent across
// the life of the transfer; would become a register in
// a later wave where software programs the target.
// MASTER_ID — bridge's identity in MEM / IOP traces (default 3,
// distinct from EE IFETCH=0, DMAC=1, IOP_CPU=2).
//
// Non-goals:
// - multiple in-flight qwords
// - ack back upstream beyond in_ready / in_last observation
// - byte-enable variation per write (all writes are full 32-bit)
// - arbitration against other masters on the map's write path
`timescale 1ns/1ps
module sif_dma_iop_ram_bridge_stub
#(
parameter logic [31:0] DEST_BASE_ADDR = 32'h0000_0000,
parameter logic [7:0] MASTER_ID = 8'd3
) (
input logic clk,
input logic rst_n,
// Upstream (DMAC endpoint side)
input logic in_valid,
input logic [127:0] in_data,
input logic in_last,
output logic in_ready,
// Downstream (IOP map bridge-write port)
output logic bridge_wr_en,
output logic [31:0] bridge_wr_addr,
output logic [31:0] bridge_wr_data,
output logic [3:0] bridge_wr_be,
output logic [7:0] bridge_master_id
);
typedef enum logic [1:0] {
S_IDLE = 2'd0,
S_WRITE = 2'd1
} state_e;
state_e state;
logic [127:0] latched_qword;
logic [1:0] beat_index; // 0..3 across the 4 writes
logic [31:0] wr_offset; // running byte offset
assign in_ready = (state == S_IDLE);
assign bridge_master_id = MASTER_ID;
logic accept_new_qword;
assign accept_new_qword = in_valid && in_ready;
// ------------------------------------------------------------------
// State machine
// ------------------------------------------------------------------
always_ff @(posedge clk) begin
if (!rst_n) begin
state <= S_IDLE;
latched_qword <= 128'd0;
beat_index <= 2'd0;
wr_offset <= 32'd0;
end else begin
unique case (state)
S_IDLE: begin
if (accept_new_qword) begin
latched_qword <= in_data;
beat_index <= 2'd0;
state <= S_WRITE;
end
end
S_WRITE: begin
// Each cycle here drives one 32-bit write. After the
// fourth, go idle. wr_offset advances per write.
wr_offset <= wr_offset + 32'd4;
if (beat_index == 2'd3) begin
state <= S_IDLE;
beat_index <= 2'd0;
end else begin
beat_index <= beat_index + 2'd1;
end
end
default: state <= S_IDLE;
endcase
end
end
// ------------------------------------------------------------------
// Downstream write-port drive (combinational on state)
// ------------------------------------------------------------------
// Indexed part-select picks the 32-bit slice for the current beat.
// Avoids the constant-select-in-always_comb pattern that trips
// portability warnings on some simulators.
logic [31:0] beat_data;
assign beat_data = latched_qword[beat_index*32 +: 32];
assign bridge_wr_en = (state == S_WRITE);
assign bridge_wr_addr = DEST_BASE_ADDR + wr_offset;
assign bridge_wr_data = beat_data;
assign bridge_wr_be = 4'b1111; // full-word writes only
endmodule : sif_dma_iop_ram_bridge_stub