Files
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

155 lines
5.2 KiB
Systemverilog

// retroDE_ps2 — sif_dma_stub
//
// Minimal SIF DMA receive-side endpoint. First data-plane step on the SIF
// seam. NOT an IOP — this is a bounded receive buffer that accepts qwords
// from a DMAC channel and exposes them to the TB via a small read port.
// No IOP CPU, no live peer logic, no directional policy beyond "incoming
// qwords land in sequential slots."
//
// Contract refs:
// docs/contracts/sif.md (DMA-linked data movement endpoints)
//
// Receive interface (connects to DMAC's ep_* endpoint):
// in_valid / in_data / in_last / in_ready
// One-cycle accept per beat when in_ready is high. in_last observed
// alongside the final qword of a transfer.
//
// Read interface (TB-side verification):
// rd_en pulses with rd_idx; rd_data / rd_valid return the stored qword
// one cycle later.
//
// Stall input:
// stall_in (level) forces in_ready low while asserted. Used by the
// negative-path test to prove that a not-ready receiver does not let
// the DMAC spuriously complete.
//
// Buffer:
// Small internal array (DEPTH qwords). Full detection is tracked from
// `rx_count`: once `rx_count >= DEPTH` the buffer is full and `in_ready`
// drops so the DMAC stalls in ACTIVE_SEND. No silent wrap. There is no
// consume path yet — once full, the buffer stays full (intentional for
// the current scope). `full_o` is exposed for testbench observation.
//
// Trace:
// One SIF EV_WRITE per accepted beat (one event per cycle).
// arg0 = slot index into the receive buffer
// arg1 = data[63:0] (low half)
// arg2 = source id (hard-wired to 8'd1 = DMAC for Wave 3)
// arg3 = 0
// flags bit 0 = in_last value for this beat
// flags bit 1 = 1 (distinguishes DMA-receive writes from mailbox writes
// if both subsystems are ever instantiated together)
`timescale 1ns/1ps
module sif_dma_stub
import trace_pkg::*;
#(
parameter int DEPTH = 8 // max qwords buffered
) (
input logic clk,
input logic rst_n,
// DMAC-facing receive
input logic in_valid,
input logic [127:0] in_data,
input logic in_last,
output logic in_ready,
// TB verification read port
input logic rd_en,
input logic [$clog2(DEPTH)-1:0] rd_idx,
output logic [127:0] rd_data,
output logic rd_valid,
// Negative-path control
input logic stall_in,
// Status
output logic [31:0] rx_count, // monotonic accepted-beat count
output logic last_seen, // sticky: in_last observed
output logic full_o, // buffer full, in_ready=0
// Trace
output logic ev_valid,
output subsys_e ev_subsys,
output event_e ev_event,
output logic [63:0] ev_arg0,
output logic [63:0] ev_arg1,
output logic [63:0] ev_arg2,
output logic [63:0] ev_arg3,
output logic [31:0] ev_flags
);
localparam int IDX_W = $clog2(DEPTH);
logic [127:0] buf_mem [0:DEPTH-1];
logic [IDX_W-1:0] wr_ptr;
logic beat_accepted;
assign full_o = (rx_count >= DEPTH);
assign in_ready = !stall_in && !full_o;
assign beat_accepted = in_valid && in_ready;
// ------------------------------------------------------------------
// Receive path
// ------------------------------------------------------------------
always_ff @(posedge clk) begin
if (!rst_n) begin
wr_ptr <= '0;
rx_count <= 32'd0;
last_seen <= 1'b0;
for (int i = 0; i < DEPTH; i++) buf_mem[i] <= 128'd0;
end else if (beat_accepted) begin
buf_mem[wr_ptr] <= in_data;
wr_ptr <= wr_ptr + IDX_W'(1);
rx_count <= rx_count + 32'd1;
if (in_last) last_seen <= 1'b1;
end
end
// ------------------------------------------------------------------
// Read port (1-cycle latency)
// ------------------------------------------------------------------
always_ff @(posedge clk) begin
if (!rst_n) begin
rd_data <= 128'd0;
rd_valid <= 1'b0;
end else begin
rd_valid <= rd_en;
if (rd_en) rd_data <= buf_mem[rd_idx];
end
end
// ------------------------------------------------------------------
// Trace — one event per accepted beat
// ------------------------------------------------------------------
always_ff @(posedge clk) begin
if (!rst_n) begin
ev_valid <= 1'b0;
ev_subsys <= SUBSYS_SIF;
ev_event <= EV_WRITE;
ev_arg0 <= 64'd0;
ev_arg1 <= 64'd0;
ev_arg2 <= 64'd0;
ev_arg3 <= 64'd0;
ev_flags <= 32'd0;
end else if (beat_accepted) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_SIF;
ev_event <= EV_WRITE;
ev_arg0 <= {{(64-IDX_W){1'b0}}, wr_ptr};
ev_arg1 <= in_data[63:0];
ev_arg2 <= 64'd1; // DMAC
ev_arg3 <= 64'd0;
ev_flags <= {30'd0, 1'b1, in_last}; // bit1=DMA, bit0=in_last
end else begin
ev_valid <= 1'b0;
end
end
endmodule : sif_dma_stub