// 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