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,91 @@
|
||||
# rtl/sif
|
||||
|
||||
EE↔IOP subsystem interface. Matches `docs/contracts/sif.md`.
|
||||
|
||||
## Current contents
|
||||
|
||||
- `sif_mailbox_stub.sv` — minimal four-register mailbox/flag shell
|
||||
(MSCOM / SMCOM / MSFLG / SMFLG). Independent EE-side and IOP-side register
|
||||
ports. Directional set/clear semantics deferred; this phase only proves
|
||||
that both sides observe consistent storage and that side-of-origin is
|
||||
trace-visible. Per-register write arbitration: EE wins on same-register
|
||||
collision, independent writes to different registers coexist.
|
||||
- `sif_dma_stub.sv` — receive-side DMA endpoint. Accepts qwords from a
|
||||
DMAC channel's `ep_*` port into a small internal buffer (default DEPTH=8).
|
||||
Capacity-safe: `in_ready` drops when `rx_count >= DEPTH`, `full_o`
|
||||
exposed for testbench observation. TB-controlled `stall_in` input for
|
||||
explicit stall testing. Read port for payload verification. No consume
|
||||
path yet — once full, stays full. NOT an IOP — purely a bounded receive
|
||||
buffer with trace emission per accepted beat.
|
||||
- `sif_mailbox_peer_stub.sv` — tiny active peer used in integration tests
|
||||
to play "the IOP side" of a specific mailbox protocol. Re-armable
|
||||
command-echo state machine (poll MSFLG → read MSCOM → write SMCOM →
|
||||
write SMFLG → wait for TB to clear MSFLG → repeat). Refuses to re-fire
|
||||
while the doorbell bit stays high, so lifecycle is explicit. Exposes
|
||||
`ack_count_o` for testbench synchronisation.
|
||||
Explicitly NOT an IOP core: no code execution, no BIOS bring-up, no
|
||||
implicit flag clearing (re-arm is the TB's responsibility). Kept under
|
||||
`rtl/sif/` precisely so it does not get misread as IOP maturity progress.
|
||||
- `sif_dma_iop_ram_bridge_stub.sv` — width-adapting bridge from a 128-bit
|
||||
SIF DMA endpoint to 32-bit IOP-side writes. Splits each incoming qword
|
||||
into four 32-bit writes at consecutive physical addresses from
|
||||
`DEST_BASE_ADDR`. Little-endian unpacking. Drives the IOP memory map's
|
||||
bridge-write port (`bridge_wr_*`). In-ready drops while the bridge is
|
||||
flushing a qword — natural backpressure to the DMAC.
|
||||
- `sif_dma_ack_peer_stub.sv` — protocol combiner for the first combined
|
||||
control+data SIF milestone. Observes a mailbox doorbell (MSFLG pending
|
||||
bit) AND `sif_dma_stub.last_seen` (payload completion); only emits the
|
||||
ack sequence (SMCOM=cmd + SMFLG=ACK) once both are true. Composes two
|
||||
existing SIF primitives; does not fatten the plain mailbox peer with
|
||||
DMA awareness.
|
||||
Explicitly NOT an IOP.
|
||||
- `sif_dma_ee_ram_bridge_stub.sv` — width-adapting bridge from a 32-bit
|
||||
SIF DMA endpoint (IOP→EE egress) to 128-bit EE-side writes. Mirror of
|
||||
`sif_dma_iop_ram_bridge_stub` in the other direction: accumulates four
|
||||
consecutive 32-bit beats into a qword (little-endian), then issues
|
||||
one write through the EE memory map's bridge write port. Drops
|
||||
`in_ready` during the one-cycle emit for natural back-pressure.
|
||||
Handles partial-quad on `in_last` via byte-enable masking. Exposes
|
||||
`last_seen_o` — a level-held latch that rises when the final beat of
|
||||
a transfer is accepted, so EE-side protocol combiners can gate on
|
||||
"payload fully landed."
|
||||
- `sif_dma_ee_ack_peer_stub.sv` — protocol combiner for the first
|
||||
IOP-driven combined control+data SIF milestone. Polarity mirror of
|
||||
`sif_dma_ack_peer_stub`: observes the mailbox's EE side for an IOP
|
||||
doorbell (SMFLG pending bit), gates on
|
||||
`sif_dma_ee_ram_bridge_stub.last_seen_o`, and only then reads SMCOM
|
||||
and echoes MSCOM + MSFLG=ACK back IOP-ward. One-shot. Explicitly NOT
|
||||
an EE core — purely a composition of two existing SIF primitives.
|
||||
|
||||
## Current status
|
||||
|
||||
The SIF seam is feature-complete for staged bring-up in both directions.
|
||||
Storage, active peer, lifecycle/re-arm, negative-path, EE→IOP DMA, three
|
||||
classes of backpressure (start / mid-transfer / full-stop), EE-driven
|
||||
combined control+data gating, a reverse-direction (IOP→EE) data path
|
||||
with its own stall semantics, AND the matching IOP-driven combined
|
||||
control+data handshake are all proven end-to-end. Further SIF-only work
|
||||
would be symmetry-chasing rather than unlocking new architectural
|
||||
questions.
|
||||
|
||||
## Deferred follow-ons (not gaps)
|
||||
|
||||
These are known extension points, intentionally not pursued yet:
|
||||
|
||||
- **Re-armable combined control+data handshakes.** Both directions are
|
||||
currently one-shot; re-arm mostly composes pieces already proven
|
||||
separately. Nice-to-have.
|
||||
- **Directional write-ownership + flag set/clear semantics.** Currently
|
||||
both sides of the mailbox can write any register with plain replace
|
||||
semantics; real PS2 has directional set/W1C rules.
|
||||
- **Real EE↔IOP coordination.** Arrives once an IOP-side execution
|
||||
primitive exists that can observe SIF as "IOP behaviour," not as a
|
||||
peer stub.
|
||||
|
||||
## Scope boundary
|
||||
|
||||
This directory owns the SIF register shell and DMA-visible coordination.
|
||||
It does **not** own:
|
||||
- IOP CPU execution (`rtl/iop/`, not yet created)
|
||||
- EE-side addressing / kseg stripping for SIF registers (memory-map work)
|
||||
- Interrupt routing to INTC on SIF transitions (Wave 3)
|
||||
@@ -0,0 +1,217 @@
|
||||
// retroDE_ps2 — boot_install_agent_stub (Ch55 / Ch56)
|
||||
//
|
||||
// Minimal external producer that streams a coordinated low-RAM handler
|
||||
// image into EE RAM through the SIF EE-RAM bridge. Emits 32-bit beats
|
||||
// on a ready/valid handshake compatible with sif_dma_ee_ram_bridge_stub.
|
||||
//
|
||||
// NOT an IOP, NOT a full boot firmware. This is the thinnest possible
|
||||
// stand-in for "whatever on real PS2 populates EE useg [0x80..0x1FF]
|
||||
// with exception-entry + safe-return stubs before the EE starts
|
||||
// faulting" (IOP→EE SIF DMA, BootROM/CDVD handoff, etc.). The point
|
||||
// is to validate the transport path and the coordinated-install
|
||||
// thesis, not to model the producer's identity.
|
||||
//
|
||||
// Payload source (Ch56):
|
||||
// USE_IMAGE_FILE=0 (default) — built-in Ch54 image, hardcoded below
|
||||
// USE_IMAGE_FILE=1 — $readmemh(IMAGE_FILE, payload) once
|
||||
// at sim start, expects TOTAL_WORDS
|
||||
// hex words
|
||||
// Transport (timing, handshake, trace) is identical across both modes.
|
||||
//
|
||||
// Built-in image (USE_IMAGE_FILE=0):
|
||||
// word[0..3] → AdES handler at useg 0x80..0x8C:
|
||||
// MFC0 $26, $14 (32'h401A7000)
|
||||
// ADDIU $26, $26, 4 (32'h275A0004)
|
||||
// JR $26 (32'h03400008)
|
||||
// RFE (32'h42000010)
|
||||
// word[4..95] → 46× (JR $31; NOP) safe-return pairs covering
|
||||
// useg 0x90..0x1FC.
|
||||
//
|
||||
// Downstream contract (matches sif_dma_ee_ram_bridge_stub upstream):
|
||||
// out_valid / out_data[31:0] / out_last / out_ready
|
||||
// out_last asserted on the final word. One-beat-per-cycle while
|
||||
// out_ready is high.
|
||||
//
|
||||
// Trace:
|
||||
// SUBSYS_SIF / EV_DMA_START once on go.
|
||||
// SUBSYS_SIF / EV_DMA_BEAT per accepted beat.
|
||||
// arg0 = word index, arg1 = word data, arg2 = MASTER_ID,
|
||||
// arg3 = TOTAL_WORDS, flags bit0 = out_last.
|
||||
// SUBSYS_SIF / EV_DMA_DONE once on completion.
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module boot_install_agent_stub
|
||||
import trace_pkg::*;
|
||||
#(
|
||||
parameter int TOTAL_WORDS = 96,
|
||||
parameter logic [7:0] MASTER_ID = 8'd6, // install agent
|
||||
parameter bit USE_IMAGE_FILE = 1'b0, // 0: built-in ROM, 1: $readmemh
|
||||
parameter string IMAGE_FILE = ""
|
||||
) (
|
||||
input logic clk,
|
||||
input logic rst_n,
|
||||
|
||||
input logic go_i,
|
||||
|
||||
output logic out_valid,
|
||||
output logic [31:0] out_data,
|
||||
output logic out_last,
|
||||
input logic out_ready,
|
||||
|
||||
output logic busy_o,
|
||||
output logic done_o,
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Payload ROM
|
||||
// ------------------------------------------------------------------
|
||||
// The 4-word exception-return stub at [0..3] plus (JR $31; NOP)
|
||||
// pairs filling the rest requires TOTAL_WORDS to be even and at
|
||||
// least 4. Guard explicitly (Ch55 audit low-1): odd values would
|
||||
// walk past the array end in the pair loop.
|
||||
initial begin
|
||||
if (TOTAL_WORDS < 4 || (TOTAL_WORDS & 1) != 0) begin
|
||||
$fatal(1, "boot_install_agent_stub: TOTAL_WORDS must be even and >= 4, got %0d",
|
||||
TOTAL_WORDS);
|
||||
end
|
||||
end
|
||||
|
||||
logic [31:0] payload [0:TOTAL_WORDS-1];
|
||||
|
||||
initial begin
|
||||
if (USE_IMAGE_FILE) begin
|
||||
if (IMAGE_FILE == "") begin
|
||||
$fatal(1, "boot_install_agent_stub: USE_IMAGE_FILE=1 but IMAGE_FILE is empty");
|
||||
end
|
||||
$readmemh(IMAGE_FILE, payload);
|
||||
end else begin
|
||||
payload[0] = 32'h401A7000; // MFC0 $26, $14
|
||||
payload[1] = 32'h275A0004; // ADDIU $26, $26, 4
|
||||
payload[2] = 32'h03400008; // JR $26
|
||||
payload[3] = 32'h42000010; // RFE (delay slot)
|
||||
for (int i = 4; i < TOTAL_WORDS; i = i + 2) begin
|
||||
payload[i] = 32'h03E00008; // JR $31
|
||||
payload[i + 1] = 32'h00000000; // NOP
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Streaming FSM
|
||||
// ------------------------------------------------------------------
|
||||
typedef enum logic [1:0] {
|
||||
S_IDLE = 2'd0,
|
||||
S_STREAM = 2'd1,
|
||||
S_DONE = 2'd2
|
||||
} state_e;
|
||||
|
||||
state_e state;
|
||||
logic [31:0] idx; // next word to emit
|
||||
|
||||
logic accept_beat;
|
||||
assign accept_beat = out_valid && out_ready;
|
||||
|
||||
assign out_valid = (state == S_STREAM);
|
||||
assign out_data = (state == S_STREAM) ? payload[idx[$clog2(TOTAL_WORDS)-1:0]]
|
||||
: 32'd0;
|
||||
assign out_last = (state == S_STREAM) && (idx == TOTAL_WORDS - 1);
|
||||
assign busy_o = (state == S_STREAM);
|
||||
assign done_o = (state == S_DONE);
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
state <= S_IDLE;
|
||||
idx <= 32'd0;
|
||||
end else begin
|
||||
unique case (state)
|
||||
S_IDLE: begin
|
||||
if (go_i) begin
|
||||
state <= S_STREAM;
|
||||
idx <= 32'd0;
|
||||
end
|
||||
end
|
||||
S_STREAM: begin
|
||||
if (accept_beat) begin
|
||||
if (idx == TOTAL_WORDS - 1) state <= S_DONE;
|
||||
else idx <= idx + 32'd1;
|
||||
end
|
||||
end
|
||||
S_DONE: ; // terminal
|
||||
default: state <= S_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Trace
|
||||
// ------------------------------------------------------------------
|
||||
// START fires combinationally on the cycle the caller pulses go_i
|
||||
// while we're still in S_IDLE. That cycle has out_valid=0 and
|
||||
// accept_beat=0, so the event doesn't compete with a BEAT event
|
||||
// in the priority if-else below (the bug pre-fix: flopping
|
||||
// go_latched delayed START onto the same cycle as beat 0, dropping
|
||||
// one of the two).
|
||||
logic go_pulse;
|
||||
assign go_pulse = (state == S_IDLE) && go_i;
|
||||
|
||||
logic done_edge;
|
||||
state_e state_prev;
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) state_prev <= S_IDLE;
|
||||
else state_prev <= state;
|
||||
end
|
||||
assign done_edge = (state == S_DONE) && (state_prev != S_DONE);
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
ev_valid <= 1'b0;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_DMA_START;
|
||||
ev_arg0 <= 64'd0;
|
||||
ev_arg1 <= 64'd0;
|
||||
ev_arg2 <= 64'd0;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'd0;
|
||||
end else if (go_pulse) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_DMA_START;
|
||||
ev_arg0 <= 64'd0;
|
||||
ev_arg1 <= 64'd0;
|
||||
ev_arg2 <= {56'd0, MASTER_ID};
|
||||
ev_arg3 <= 64'(TOTAL_WORDS);
|
||||
ev_flags <= 32'd0;
|
||||
end else if (accept_beat) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_DMA_BEAT;
|
||||
ev_arg0 <= {32'd0, idx};
|
||||
ev_arg1 <= {32'd0, out_data};
|
||||
ev_arg2 <= {56'd0, MASTER_ID};
|
||||
ev_arg3 <= 64'(TOTAL_WORDS);
|
||||
ev_flags <= {31'd0, out_last};
|
||||
end else if (done_edge) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_DMA_DONE;
|
||||
ev_arg0 <= 64'(TOTAL_WORDS);
|
||||
ev_arg1 <= 64'd0;
|
||||
ev_arg2 <= {56'd0, MASTER_ID};
|
||||
ev_arg3 <= 64'(TOTAL_WORDS);
|
||||
ev_flags <= 32'd0;
|
||||
end else begin
|
||||
ev_valid <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule : boot_install_agent_stub
|
||||
@@ -0,0 +1,181 @@
|
||||
// retroDE_ps2 — sif_dma_ack_peer_stub
|
||||
//
|
||||
// Protocol combiner for the first combined control+data SIF milestone.
|
||||
// Observes a mailbox command doorbell on one seam and the SIF DMA receive
|
||||
// endpoint's payload-complete indication on the other; only issues the
|
||||
// mailbox ack sequence once BOTH are true.
|
||||
//
|
||||
// Explicitly NOT an IOP. This module has no code execution, no bus master,
|
||||
// and no capability beyond composing two existing SIF primitives. Kept
|
||||
// under `rtl/sif/` with the other SIF scaffolding.
|
||||
//
|
||||
// Contract refs:
|
||||
// docs/contracts/sif.md
|
||||
//
|
||||
// Layering:
|
||||
// sif_mailbox_stub — storage primitive
|
||||
// sif_mailbox_peer_stub — mailbox-only active peer (no DMA awareness)
|
||||
// sif_dma_stub — data-plane receive endpoint
|
||||
// sif_dma_ack_peer_stub — THIS module. Wires the two together.
|
||||
//
|
||||
// Protocol (one-shot):
|
||||
// 1. EE writes MSCOM = cmd
|
||||
// 2. EE writes MSFLG = CMD_PENDING_BIT (request doorbell)
|
||||
// 3. DMAC transfers bounded payload into sif_dma_stub
|
||||
// 4. sif_dma_stub asserts last_seen once the final beat arrives
|
||||
// 5. this peer observes (MSFLG & CMD_PENDING_BIT) AND last_seen
|
||||
// 6. peer reads MSCOM
|
||||
// 7. peer writes SMCOM = cmd
|
||||
// 8. peer writes SMFLG = CMD_ACK_BIT
|
||||
// 9. terminal DONE (one-shot for this milestone)
|
||||
//
|
||||
// The peer does NOT clear MSFLG or SMFLG — lifecycle is the TB's
|
||||
// responsibility, consistent with sif_mailbox_peer_stub's guardrail.
|
||||
//
|
||||
// Ports connect to:
|
||||
// obs_* → sif_mailbox_stub iop_rd_* (peer reads MSFLG, then MSCOM)
|
||||
// resp_* → sif_mailbox_stub iop_wr_* (peer writes SMCOM, then SMFLG)
|
||||
// payload_complete ← sif_dma_stub.last_seen
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module sif_dma_ack_peer_stub
|
||||
#(
|
||||
parameter logic [7:0] MSCOM_OFF = 8'h00,
|
||||
parameter logic [7:0] SMCOM_OFF = 8'h10,
|
||||
parameter logic [7:0] MSFLG_OFF = 8'h20,
|
||||
parameter logic [7:0] SMFLG_OFF = 8'h30,
|
||||
parameter logic [31:0] CMD_PENDING_BIT = 32'h0000_0001,
|
||||
parameter logic [31:0] CMD_ACK_BIT = 32'h0000_0002
|
||||
) (
|
||||
input logic clk,
|
||||
input logic rst_n,
|
||||
|
||||
// Mailbox observation (IOP-side read port)
|
||||
output logic obs_rd_en,
|
||||
output logic [7:0] obs_rd_addr,
|
||||
input logic [31:0] obs_rd_data,
|
||||
input logic obs_rd_valid,
|
||||
|
||||
// Mailbox response (IOP-side write port)
|
||||
output logic resp_wr_en,
|
||||
output logic [7:0] resp_wr_addr,
|
||||
output logic [31:0] resp_wr_data,
|
||||
|
||||
// Payload completion indication from sif_dma_stub (level)
|
||||
input logic payload_complete,
|
||||
|
||||
// Status
|
||||
output logic done_o,
|
||||
output logic [31:0] ack_count_o
|
||||
);
|
||||
|
||||
typedef enum logic [2:0] {
|
||||
S_POLL_REQ = 3'd0, // pulse rd_en for MSFLG
|
||||
S_POLL_WAIT = 3'd1, // wait for rd_valid, gate on BOTH conditions
|
||||
S_MSCOM_REQ = 3'd2, // pulse rd_en for MSCOM
|
||||
S_MSCOM_WAIT = 3'd3, // wait for rd_valid, latch cmd
|
||||
S_WRITE_SMCOM = 3'd4, // drive wr_en, addr=SMCOM, data=cmd
|
||||
S_WRITE_SMFLG = 3'd5, // drive wr_en, addr=SMFLG, data=ACK
|
||||
S_DONE = 3'd6 // terminal (one-shot for this milestone)
|
||||
} state_e;
|
||||
|
||||
state_e state;
|
||||
logic [31:0] latched_cmd;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// State machine — advance to MSCOM_REQ only when MSFLG pending is set
|
||||
// AND payload_complete is observed simultaneously. This is the
|
||||
// load-bearing guarantee of the whole combiner.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
state <= S_POLL_REQ;
|
||||
latched_cmd <= 32'd0;
|
||||
end else begin
|
||||
unique case (state)
|
||||
S_POLL_REQ: state <= S_POLL_WAIT;
|
||||
|
||||
S_POLL_WAIT: begin
|
||||
if (obs_rd_valid) begin
|
||||
if (((obs_rd_data & CMD_PENDING_BIT) != 32'd0) &&
|
||||
payload_complete)
|
||||
state <= S_MSCOM_REQ;
|
||||
else
|
||||
state <= S_POLL_REQ; // keep polling
|
||||
end
|
||||
end
|
||||
|
||||
S_MSCOM_REQ: state <= S_MSCOM_WAIT;
|
||||
|
||||
S_MSCOM_WAIT: begin
|
||||
if (obs_rd_valid) begin
|
||||
latched_cmd <= obs_rd_data;
|
||||
state <= S_WRITE_SMCOM;
|
||||
end
|
||||
end
|
||||
|
||||
S_WRITE_SMCOM: state <= S_WRITE_SMFLG;
|
||||
|
||||
S_WRITE_SMFLG: state <= S_DONE;
|
||||
|
||||
S_DONE: state <= S_DONE;
|
||||
|
||||
default: state <= S_POLL_REQ;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Output drive (combinational, one-hot on state)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_comb begin
|
||||
obs_rd_en = 1'b0;
|
||||
obs_rd_addr = 8'd0;
|
||||
resp_wr_en = 1'b0;
|
||||
resp_wr_addr = 8'd0;
|
||||
resp_wr_data = 32'd0;
|
||||
|
||||
unique case (state)
|
||||
S_POLL_REQ: begin
|
||||
obs_rd_en = 1'b1;
|
||||
obs_rd_addr = MSFLG_OFF;
|
||||
end
|
||||
S_MSCOM_REQ: begin
|
||||
obs_rd_en = 1'b1;
|
||||
obs_rd_addr = MSCOM_OFF;
|
||||
end
|
||||
S_WRITE_SMCOM: begin
|
||||
resp_wr_en = 1'b1;
|
||||
resp_wr_addr = SMCOM_OFF;
|
||||
resp_wr_data = latched_cmd;
|
||||
end
|
||||
S_WRITE_SMFLG: begin
|
||||
resp_wr_en = 1'b1;
|
||||
resp_wr_addr = SMFLG_OFF;
|
||||
resp_wr_data = CMD_ACK_BIT;
|
||||
end
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Ack bookkeeping
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
ack_count_o <= 32'd0;
|
||||
done_o <= 1'b0;
|
||||
end else if (state == S_WRITE_SMFLG) begin
|
||||
// S_WRITE_SMFLG is a single-cycle state: unconditionally
|
||||
// transitions to S_DONE on the next edge, so this observes
|
||||
// exactly one completion.
|
||||
ack_count_o <= ack_count_o + 32'd1;
|
||||
done_o <= 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule : sif_dma_ack_peer_stub
|
||||
@@ -0,0 +1,185 @@
|
||||
// retroDE_ps2 — sif_dma_ee_ack_peer_stub
|
||||
//
|
||||
// Protocol combiner for the first reverse-direction (IOP→EE) combined
|
||||
// control+data SIF milestone. Mirror of sif_dma_ack_peer_stub with
|
||||
// polarity swapped to observe the IOP's doorbell from the EE side and
|
||||
// respond back IOP-ward through the mailbox.
|
||||
//
|
||||
// Explicitly NOT an EE. This module has no code execution, no bus
|
||||
// master, no capability beyond composing two existing SIF primitives.
|
||||
// Kept under `rtl/sif/` with the other protocol scaffolding so it
|
||||
// does not get misread as EE maturity progress.
|
||||
//
|
||||
// Contract refs:
|
||||
// docs/contracts/sif.md
|
||||
//
|
||||
// Layering:
|
||||
// sif_mailbox_stub — storage primitive
|
||||
// sif_dma_ee_ram_bridge_stub — reverse-direction data-plane landing
|
||||
// sif_dma_ee_ack_peer_stub — THIS module. Ties them together on
|
||||
// the EE side.
|
||||
//
|
||||
// Protocol (one-shot, reverse direction):
|
||||
// 1. IOP writes SMCOM = cmd (what the IOP wants to say)
|
||||
// 2. IOP writes SMFLG = CMD_PENDING_BIT (doorbell IOP→EE)
|
||||
// 3. IOP DMAC ch9 transfers bounded payload through the SIF egress
|
||||
// bridge, which lands qwords in EE RAM. Bridge's last_seen_o rises
|
||||
// on the final beat and stays high.
|
||||
// 4. this peer observes (SMFLG & CMD_PENDING_BIT) AND payload_complete
|
||||
// 5. peer reads SMCOM (captures the command)
|
||||
// 6. peer writes MSCOM = cmd (echo back IOP-ward)
|
||||
// 7. peer writes MSFLG = CMD_ACK_BIT (ack back IOP-ward)
|
||||
// 8. terminal DONE (one-shot for this milestone)
|
||||
//
|
||||
// The peer does NOT clear SMFLG or MSFLG — lifecycle is the TB's
|
||||
// responsibility, consistent with sif_mailbox_peer_stub's guardrail.
|
||||
//
|
||||
// Ordering guarantee: the load-bearing behaviour is that the peer will
|
||||
// not advance to the ack write sequence unless BOTH the doorbell AND
|
||||
// payload_complete are observed simultaneously. The milestone is about
|
||||
// verifying that the ack is gated on data arriving, not just control.
|
||||
//
|
||||
// Ports connect to:
|
||||
// obs_* → sif_mailbox_stub ee_rd_* (peer reads SMFLG, then SMCOM)
|
||||
// resp_* → sif_mailbox_stub ee_wr_* (peer writes MSCOM, then MSFLG)
|
||||
// payload_complete ← sif_dma_ee_ram_bridge_stub.last_seen_o
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module sif_dma_ee_ack_peer_stub
|
||||
#(
|
||||
parameter logic [7:0] MSCOM_OFF = 8'h00,
|
||||
parameter logic [7:0] SMCOM_OFF = 8'h10,
|
||||
parameter logic [7:0] MSFLG_OFF = 8'h20,
|
||||
parameter logic [7:0] SMFLG_OFF = 8'h30,
|
||||
parameter logic [31:0] CMD_PENDING_BIT = 32'h0000_0001,
|
||||
parameter logic [31:0] CMD_ACK_BIT = 32'h0000_0002
|
||||
) (
|
||||
input logic clk,
|
||||
input logic rst_n,
|
||||
|
||||
// Mailbox observation (EE-side read port)
|
||||
output logic obs_rd_en,
|
||||
output logic [7:0] obs_rd_addr,
|
||||
input logic [31:0] obs_rd_data,
|
||||
input logic obs_rd_valid,
|
||||
|
||||
// Mailbox response (EE-side write port)
|
||||
output logic resp_wr_en,
|
||||
output logic [7:0] resp_wr_addr,
|
||||
output logic [31:0] resp_wr_data,
|
||||
|
||||
// Payload completion indication from sif_dma_ee_ram_bridge_stub (level)
|
||||
input logic payload_complete,
|
||||
|
||||
// Status
|
||||
output logic done_o,
|
||||
output logic [31:0] ack_count_o
|
||||
);
|
||||
|
||||
typedef enum logic [2:0] {
|
||||
S_POLL_REQ = 3'd0, // pulse rd_en for SMFLG
|
||||
S_POLL_WAIT = 3'd1, // wait for rd_valid, gate on BOTH conditions
|
||||
S_SMCOM_REQ = 3'd2, // pulse rd_en for SMCOM
|
||||
S_SMCOM_WAIT = 3'd3, // wait for rd_valid, latch cmd
|
||||
S_WRITE_MSCOM = 3'd4, // drive wr_en, addr=MSCOM, data=cmd
|
||||
S_WRITE_MSFLG = 3'd5, // drive wr_en, addr=MSFLG, data=ACK
|
||||
S_DONE = 3'd6 // terminal (one-shot for this milestone)
|
||||
} state_e;
|
||||
|
||||
state_e state;
|
||||
logic [31:0] latched_cmd;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// State machine — advance to SMCOM_REQ only when SMFLG pending is set
|
||||
// AND payload_complete is observed simultaneously. This is the
|
||||
// load-bearing guarantee of the whole combiner.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
state <= S_POLL_REQ;
|
||||
latched_cmd <= 32'd0;
|
||||
end else begin
|
||||
unique case (state)
|
||||
S_POLL_REQ: state <= S_POLL_WAIT;
|
||||
|
||||
S_POLL_WAIT: begin
|
||||
if (obs_rd_valid) begin
|
||||
if (((obs_rd_data & CMD_PENDING_BIT) != 32'd0) &&
|
||||
payload_complete)
|
||||
state <= S_SMCOM_REQ;
|
||||
else
|
||||
state <= S_POLL_REQ; // keep polling
|
||||
end
|
||||
end
|
||||
|
||||
S_SMCOM_REQ: state <= S_SMCOM_WAIT;
|
||||
|
||||
S_SMCOM_WAIT: begin
|
||||
if (obs_rd_valid) begin
|
||||
latched_cmd <= obs_rd_data;
|
||||
state <= S_WRITE_MSCOM;
|
||||
end
|
||||
end
|
||||
|
||||
S_WRITE_MSCOM: state <= S_WRITE_MSFLG;
|
||||
|
||||
S_WRITE_MSFLG: state <= S_DONE;
|
||||
|
||||
S_DONE: state <= S_DONE;
|
||||
|
||||
default: state <= S_POLL_REQ;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Output drive (combinational, one-hot on state)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_comb begin
|
||||
obs_rd_en = 1'b0;
|
||||
obs_rd_addr = 8'd0;
|
||||
resp_wr_en = 1'b0;
|
||||
resp_wr_addr = 8'd0;
|
||||
resp_wr_data = 32'd0;
|
||||
|
||||
unique case (state)
|
||||
S_POLL_REQ: begin
|
||||
obs_rd_en = 1'b1;
|
||||
obs_rd_addr = SMFLG_OFF;
|
||||
end
|
||||
S_SMCOM_REQ: begin
|
||||
obs_rd_en = 1'b1;
|
||||
obs_rd_addr = SMCOM_OFF;
|
||||
end
|
||||
S_WRITE_MSCOM: begin
|
||||
resp_wr_en = 1'b1;
|
||||
resp_wr_addr = MSCOM_OFF;
|
||||
resp_wr_data = latched_cmd;
|
||||
end
|
||||
S_WRITE_MSFLG: begin
|
||||
resp_wr_en = 1'b1;
|
||||
resp_wr_addr = MSFLG_OFF;
|
||||
resp_wr_data = CMD_ACK_BIT;
|
||||
end
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Ack bookkeeping
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
ack_count_o <= 32'd0;
|
||||
done_o <= 1'b0;
|
||||
end else if (state == S_WRITE_MSFLG) begin
|
||||
ack_count_o <= ack_count_o + 32'd1;
|
||||
done_o <= 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule : sif_dma_ee_ack_peer_stub
|
||||
@@ -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
|
||||
@@ -0,0 +1,139 @@
|
||||
// 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
|
||||
@@ -0,0 +1,154 @@
|
||||
// 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
|
||||
@@ -0,0 +1,184 @@
|
||||
// retroDE_ps2 — sif_mailbox_peer_stub
|
||||
//
|
||||
// Re-armable active peer for the SIF mailbox. Second step on the two-actor
|
||||
// coordination track (A'' — lifecycle). Observes one mailbox/flag pattern
|
||||
// and responds with a known acknowledgement pattern. NOT an IOP — does not
|
||||
// execute code, does not boot anything, does not claim to be a CPU.
|
||||
//
|
||||
// Contract refs:
|
||||
// docs/contracts/sif.md (mailbox/flag-only SIF stub)
|
||||
// docs/stub_module_plan.md (Wave 2 SIF track)
|
||||
//
|
||||
// Canonical command-echo protocol:
|
||||
// 1. EE writes MSCOM = cmd
|
||||
// 2. EE writes MSFLG = CMD_PENDING_BIT (doorbell rising edge)
|
||||
// 3. peer polls MSFLG; when it sees CMD_PENDING_BIT set AND it has not
|
||||
// already responded to the current request, it reads MSCOM
|
||||
// 4. peer writes SMCOM = <the cmd it just read>
|
||||
// 5. peer writes SMFLG = CMD_ACK_BIT
|
||||
// 6. peer latches `responded` and resumes polling; it will NOT respond
|
||||
// again until the TB (or EE) clears CMD_PENDING_BIT in MSFLG
|
||||
// 7. when the peer observes CMD_PENDING_BIT cleared, `responded` clears
|
||||
// and the next rising edge of CMD_PENDING_BIT triggers a fresh echo
|
||||
//
|
||||
// The peer still does NOT clear any mailbox state itself. Re-arm is the
|
||||
// TB's responsibility; the peer just refuses to double-fire while the
|
||||
// doorbell bit is still high.
|
||||
//
|
||||
// Ports connect directly to sif_mailbox_stub's IOP-side register port:
|
||||
// obs_* → mailbox iop_rd_* (peer reads MSFLG then MSCOM)
|
||||
// resp_* → mailbox iop_wr_* (peer writes SMCOM then SMFLG)
|
||||
//
|
||||
// All peer activity is visible through the mailbox's own trace output
|
||||
// (side_id=IOP=1). The peer does not emit its own trace; `ack_count_o`
|
||||
// provides a testbench synchronisation point.
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module sif_mailbox_peer_stub
|
||||
#(
|
||||
parameter logic [7:0] MSCOM_OFF = 8'h00,
|
||||
parameter logic [7:0] SMCOM_OFF = 8'h10,
|
||||
parameter logic [7:0] MSFLG_OFF = 8'h20,
|
||||
parameter logic [7:0] SMFLG_OFF = 8'h30,
|
||||
parameter logic [31:0] CMD_PENDING_BIT = 32'h0000_0001,
|
||||
parameter logic [31:0] CMD_ACK_BIT = 32'h0000_0002
|
||||
) (
|
||||
input logic clk,
|
||||
input logic rst_n,
|
||||
|
||||
// Observation — connects to mailbox iop_rd_*
|
||||
output logic obs_rd_en,
|
||||
output logic [7:0] obs_rd_addr,
|
||||
input logic [31:0] obs_rd_data,
|
||||
input logic obs_rd_valid,
|
||||
|
||||
// Response — connects to mailbox iop_wr_*
|
||||
output logic resp_wr_en,
|
||||
output logic [7:0] resp_wr_addr,
|
||||
output logic [31:0] resp_wr_data,
|
||||
|
||||
// Status
|
||||
output logic done_o, // latched high after the first ack
|
||||
output logic [31:0] ack_count_o // monotonic count of completed acks
|
||||
);
|
||||
|
||||
typedef enum logic [2:0] {
|
||||
S_POLL_REQ = 3'd0, // drive rd_en for MSFLG
|
||||
S_POLL_WAIT = 3'd1, // wait for obs_rd_valid, decide
|
||||
S_MSCOM_REQ = 3'd2, // drive rd_en for MSCOM
|
||||
S_MSCOM_WAIT = 3'd3, // wait for obs_rd_valid, latch cmd
|
||||
S_WRITE_SMCOM = 3'd4, // drive wr_en, addr=SMCOM, data=cmd
|
||||
S_WRITE_SMFLG = 3'd5 // drive wr_en, addr=SMFLG, data=ACK
|
||||
} state_e;
|
||||
|
||||
state_e state;
|
||||
logic [31:0] latched_cmd;
|
||||
logic responded; // peer has already acked the current
|
||||
// doorbell assertion; suppresses re-fire
|
||||
// until the doorbell is observed low
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// State machine
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
state <= S_POLL_REQ;
|
||||
latched_cmd <= 32'd0;
|
||||
responded <= 1'b0;
|
||||
end else begin
|
||||
unique case (state)
|
||||
S_POLL_REQ: state <= S_POLL_WAIT;
|
||||
|
||||
S_POLL_WAIT: begin
|
||||
if (obs_rd_valid) begin
|
||||
if (responded) begin
|
||||
// Waiting for the TB to clear CMD_PENDING_BIT
|
||||
// before we arm again.
|
||||
if ((obs_rd_data & CMD_PENDING_BIT) == 32'd0)
|
||||
responded <= 1'b0;
|
||||
state <= S_POLL_REQ;
|
||||
end else begin
|
||||
if ((obs_rd_data & CMD_PENDING_BIT) != 32'd0)
|
||||
state <= S_MSCOM_REQ;
|
||||
else
|
||||
state <= S_POLL_REQ;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
S_MSCOM_REQ: state <= S_MSCOM_WAIT;
|
||||
|
||||
S_MSCOM_WAIT: begin
|
||||
if (obs_rd_valid) begin
|
||||
latched_cmd <= obs_rd_data;
|
||||
state <= S_WRITE_SMCOM;
|
||||
end
|
||||
end
|
||||
|
||||
S_WRITE_SMCOM: state <= S_WRITE_SMFLG;
|
||||
|
||||
S_WRITE_SMFLG: begin
|
||||
responded <= 1'b1; // refuse to re-fire until MSFLG
|
||||
// clears
|
||||
state <= S_POLL_REQ;
|
||||
end
|
||||
|
||||
default: state <= S_POLL_REQ;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Output drive (combinational, one-hot on state)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_comb begin
|
||||
obs_rd_en = 1'b0;
|
||||
obs_rd_addr = 8'd0;
|
||||
resp_wr_en = 1'b0;
|
||||
resp_wr_addr = 8'd0;
|
||||
resp_wr_data = 32'd0;
|
||||
|
||||
unique case (state)
|
||||
S_POLL_REQ: begin
|
||||
obs_rd_en = 1'b1;
|
||||
obs_rd_addr = MSFLG_OFF;
|
||||
end
|
||||
S_MSCOM_REQ: begin
|
||||
obs_rd_en = 1'b1;
|
||||
obs_rd_addr = MSCOM_OFF;
|
||||
end
|
||||
S_WRITE_SMCOM: begin
|
||||
resp_wr_en = 1'b1;
|
||||
resp_wr_addr = SMCOM_OFF;
|
||||
resp_wr_data = latched_cmd;
|
||||
end
|
||||
S_WRITE_SMFLG: begin
|
||||
resp_wr_en = 1'b1;
|
||||
resp_wr_addr = SMFLG_OFF;
|
||||
resp_wr_data = CMD_ACK_BIT;
|
||||
end
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Ack bookkeeping
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
ack_count_o <= 32'd0;
|
||||
done_o <= 1'b0;
|
||||
end else if (state == S_WRITE_SMFLG) begin
|
||||
// S_WRITE_SMFLG is a single-cycle state: the state machine
|
||||
// unconditionally transitions to S_POLL_REQ on the next edge,
|
||||
// so this branch is observed exactly once per completed ack.
|
||||
ack_count_o <= ack_count_o + 32'd1;
|
||||
done_o <= 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule : sif_mailbox_peer_stub
|
||||
@@ -0,0 +1,230 @@
|
||||
// retroDE_ps2 — sif_mailbox_stub
|
||||
//
|
||||
// Minimal EE↔IOP subsystem-interface mailbox shell. First stub on the SIF
|
||||
// track. Standalone unit — does not yet integrate with any live IOP core.
|
||||
// Testbenches drive both the EE-side port and the IOP-side port directly,
|
||||
// playing both roles, to prove the register semantics without requiring
|
||||
// a full dual-CPU bring-up.
|
||||
//
|
||||
// Contract refs:
|
||||
// docs/stub_module_plan.md (Wave 2, item 10)
|
||||
// docs/contracts/sif.md (mailbox/flag-only stub is allowed here)
|
||||
//
|
||||
// Register surface (offsets within the SIF block):
|
||||
// 0x00 MSCOM — 32-bit mailbox, conventionally EE→IOP
|
||||
// 0x10 SMCOM — 32-bit mailbox, conventionally IOP→EE
|
||||
// 0x20 MSFLG — 32-bit flag word, conventionally EE-owned for set,
|
||||
// IOP-owned for clear (directional semantics deferred)
|
||||
// 0x30 SMFLG — 32-bit flag word, conventionally IOP-owned for set,
|
||||
// EE-owned for clear (directional semantics deferred)
|
||||
//
|
||||
// Wave 2 scope intentionally does NOT enforce direction or set/clear
|
||||
// semantics. Both ports can read and write any register with plain
|
||||
// replace-on-write. The trace records which side initiated each access
|
||||
// (side_id in arg2) so future-wave work can layer directional rules on
|
||||
// top without changing the storage model.
|
||||
//
|
||||
// Port semantics:
|
||||
// Each side (EE / IOP) has an independent register port:
|
||||
// wr_en, rd_en, addr[7:0], wr_data[31:0], rd_data[31:0], rd_valid
|
||||
// Reads have 1-cycle latency to match the existing stub ecosystem.
|
||||
//
|
||||
// Write arbitration (per-register):
|
||||
// - EE and IOP writes to *different* registers on the same cycle both
|
||||
// land. Storage is not serialized across independent registers.
|
||||
// - EE and IOP writes to the *same* register on the same cycle: EE
|
||||
// wins, IOP write is dropped that cycle.
|
||||
// - Trace is limited to one event per cycle by the shared trace bus
|
||||
// (priority EE > IOP). An IOP write that lands silently when EE is
|
||||
// driving a different register will not be traced this wave — future
|
||||
// waves can add a second trace output port if that becomes a gap.
|
||||
//
|
||||
// Trace payload schema (SUBSYS_SIF, existing EV_READ/EV_WRITE codes):
|
||||
// SIF WRITE arg0=offset arg1=data arg2=side_id arg3=0 flags[0]=1
|
||||
// SIF READ arg0=offset arg1=data arg2=side_id arg3=0 flags[0]=0
|
||||
// side_id: 0 = EE, 1 = IOP
|
||||
//
|
||||
// Trace priority on same cycle: EE write > IOP write > EE read > IOP read.
|
||||
// In practice TBs drive at most one operation per cycle.
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module sif_mailbox_stub
|
||||
import trace_pkg::*;
|
||||
(
|
||||
input logic clk,
|
||||
input logic rst_n,
|
||||
|
||||
// EE-side register port
|
||||
input logic ee_wr_en,
|
||||
input logic ee_rd_en,
|
||||
input logic [7:0] ee_addr,
|
||||
input logic [31:0] ee_wr_data,
|
||||
output logic [31:0] ee_rd_data,
|
||||
output logic ee_rd_valid,
|
||||
|
||||
// IOP-side register port
|
||||
input logic iop_wr_en,
|
||||
input logic iop_rd_en,
|
||||
input logic [7:0] iop_addr,
|
||||
input logic [31:0] iop_wr_data,
|
||||
output logic [31:0] iop_rd_data,
|
||||
output logic iop_rd_valid,
|
||||
|
||||
// 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 logic [7:0] MSCOM_OFF = 8'h00;
|
||||
localparam logic [7:0] SMCOM_OFF = 8'h10;
|
||||
localparam logic [7:0] MSFLG_OFF = 8'h20;
|
||||
localparam logic [7:0] SMFLG_OFF = 8'h30;
|
||||
|
||||
localparam logic [63:0] SIDE_EE = 64'd0;
|
||||
localparam logic [63:0] SIDE_IOP = 64'd1;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Register file
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
logic [31:0] mscom;
|
||||
logic [31:0] smcom;
|
||||
logic [31:0] msflg;
|
||||
logic [31:0] smflg;
|
||||
|
||||
function automatic logic [31:0] select_reg(input logic [7:0] offset,
|
||||
input logic [31:0] mscom_v,
|
||||
input logic [31:0] smcom_v,
|
||||
input logic [31:0] msflg_v,
|
||||
input logic [31:0] smflg_v);
|
||||
case (offset)
|
||||
MSCOM_OFF: select_reg = mscom_v;
|
||||
SMCOM_OFF: select_reg = smcom_v;
|
||||
MSFLG_OFF: select_reg = msflg_v;
|
||||
SMFLG_OFF: select_reg = smflg_v;
|
||||
default: select_reg = 32'hDEAD_BEEF;
|
||||
endcase
|
||||
endfunction
|
||||
|
||||
// Per-register write arbitration: EE wins on same-register collision,
|
||||
// but writes to different registers land independently.
|
||||
logic ee_hits_mscom, ee_hits_smcom, ee_hits_msflg, ee_hits_smflg;
|
||||
logic iop_hits_mscom, iop_hits_smcom, iop_hits_msflg, iop_hits_smflg;
|
||||
|
||||
assign ee_hits_mscom = ee_wr_en && (ee_addr == MSCOM_OFF);
|
||||
assign ee_hits_smcom = ee_wr_en && (ee_addr == SMCOM_OFF);
|
||||
assign ee_hits_msflg = ee_wr_en && (ee_addr == MSFLG_OFF);
|
||||
assign ee_hits_smflg = ee_wr_en && (ee_addr == SMFLG_OFF);
|
||||
|
||||
assign iop_hits_mscom = iop_wr_en && (iop_addr == MSCOM_OFF);
|
||||
assign iop_hits_smcom = iop_wr_en && (iop_addr == SMCOM_OFF);
|
||||
assign iop_hits_msflg = iop_wr_en && (iop_addr == MSFLG_OFF);
|
||||
assign iop_hits_smflg = iop_wr_en && (iop_addr == SMFLG_OFF);
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
mscom <= 32'd0;
|
||||
smcom <= 32'd0;
|
||||
msflg <= 32'd0;
|
||||
smflg <= 32'd0;
|
||||
end else begin
|
||||
if (ee_hits_mscom) mscom <= ee_wr_data;
|
||||
else if (iop_hits_mscom) mscom <= iop_wr_data;
|
||||
|
||||
if (ee_hits_smcom) smcom <= ee_wr_data;
|
||||
else if (iop_hits_smcom) smcom <= iop_wr_data;
|
||||
|
||||
if (ee_hits_msflg) msflg <= ee_wr_data;
|
||||
else if (iop_hits_msflg) msflg <= iop_wr_data;
|
||||
|
||||
if (ee_hits_smflg) smflg <= ee_wr_data;
|
||||
else if (iop_hits_smflg) smflg <= iop_wr_data;
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Reads (1-cycle latency, both ports independent)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
ee_rd_data <= 32'd0;
|
||||
ee_rd_valid <= 1'b0;
|
||||
iop_rd_data <= 32'd0;
|
||||
iop_rd_valid <= 1'b0;
|
||||
end else begin
|
||||
ee_rd_valid <= ee_rd_en;
|
||||
if (ee_rd_en)
|
||||
ee_rd_data <= select_reg(ee_addr, mscom, smcom, msflg, smflg);
|
||||
|
||||
iop_rd_valid <= iop_rd_en;
|
||||
if (iop_rd_en)
|
||||
iop_rd_data <= select_reg(iop_addr, mscom, smcom, msflg, smflg);
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Trace emission — priority EE_wr > IOP_wr > EE_rd > IOP_rd.
|
||||
// Reads emit with the data that will be delivered next cycle, keeping
|
||||
// the trace line self-consistent.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (!rst_n) begin
|
||||
ev_valid <= 1'b0;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_READ;
|
||||
ev_arg0 <= 64'd0;
|
||||
ev_arg1 <= 64'd0;
|
||||
ev_arg2 <= 64'd0;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'd0;
|
||||
end else if (ee_wr_en) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_WRITE;
|
||||
ev_arg0 <= {56'd0, ee_addr};
|
||||
ev_arg1 <= {32'd0, ee_wr_data};
|
||||
ev_arg2 <= SIDE_EE;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'h0000_0001;
|
||||
end else if (iop_wr_en) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_WRITE;
|
||||
ev_arg0 <= {56'd0, iop_addr};
|
||||
ev_arg1 <= {32'd0, iop_wr_data};
|
||||
ev_arg2 <= SIDE_IOP;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'h0000_0001;
|
||||
end else if (ee_rd_en) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_READ;
|
||||
ev_arg0 <= {56'd0, ee_addr};
|
||||
ev_arg1 <= {32'd0, select_reg(ee_addr, mscom, smcom, msflg, smflg)};
|
||||
ev_arg2 <= SIDE_EE;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'd0;
|
||||
end else if (iop_rd_en) begin
|
||||
ev_valid <= 1'b1;
|
||||
ev_subsys <= SUBSYS_SIF;
|
||||
ev_event <= EV_READ;
|
||||
ev_arg0 <= {56'd0, iop_addr};
|
||||
ev_arg1 <= {32'd0, select_reg(iop_addr, mscom, smcom, msflg, smflg)};
|
||||
ev_arg2 <= SIDE_IOP;
|
||||
ev_arg3 <= 64'd0;
|
||||
ev_flags <= 32'd0;
|
||||
end else begin
|
||||
ev_valid <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule : sif_mailbox_stub
|
||||
Reference in New Issue
Block a user