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