// retroDE_ps2 — tb_sif_combined_ctrl_data // // First combined control+data SIF protocol proof. EE raises a mailbox // doorbell and kicks off a bounded DMA transfer; sif_dma_ack_peer_stub // gates the mailbox ack on payload completion. Two seams composed into // one PS2-shaped handshake. // // Scope (narrow, per Codex): // - one direction (EE → SIF receive side) // - DMAC channel 5, QWC=4, direct-to-RAM payload // - sif_dma_ack_peer_stub as a dedicated combiner (not a fattened // mailbox peer) // - no whitebox peeks — interface + trace only // // Key interface-visible properties: // 1. No ack traffic observed between doorbell-up and DMA-done. // 2. Ack traffic appears only AFTER `last_seen` has gone high. // 3. SMCOM after ack == the command EE wrote into MSCOM. // 4. SMFLG after ack == CMD_ACK_BIT. // 5. All four expected payload qwords land in sif_dma_stub in order. `timescale 1ns/1ps module tb_sif_combined_ctrl_data; logic clk; logic rst_n; initial clk = 1'b0; always #5 clk = ~clk; localparam int QWC_VAL = 4; localparam int RAM_SIZE_BYTES = 4 * 1024; localparam int RAM_ADDR_W = $clog2(RAM_SIZE_BYTES); localparam int SIF_DEPTH = 8; localparam int SIF_IDX_W = $clog2(SIF_DEPTH); localparam logic [31:0] PAYLOAD_MADR = 32'h0000_0500; localparam logic [31:0] CMD_VALUE = 32'h5ABBA5AB; localparam logic [31:0] CMD_PENDING_BIT = 32'h0000_0001; localparam logic [31:0] CMD_ACK_BIT = 32'h0000_0002; localparam logic [7:0] MSCOM = 8'h00; localparam logic [7:0] SMCOM = 8'h10; localparam logic [7:0] MSFLG = 8'h20; localparam logic [7:0] SMFLG = 8'h30; // ------------------------------------------------------------------ // ee_ram_stub // ------------------------------------------------------------------ logic ram_rd_en; logic [RAM_ADDR_W-1:0] ram_rd_addr; logic [127:0] ram_rd_data; logic ram_rd_valid; logic ram_wr_en; logic [RAM_ADDR_W-1:0] ram_wr_addr; logic [127:0] ram_wr_data; logic [15:0] ram_wr_be; logic [7:0] ram_master_id; logic ram_ev_valid; trace_pkg::subsys_e ram_ev_subsys; trace_pkg::event_e ram_ev_event; logic [63:0] ram_ev_arg0, ram_ev_arg1, ram_ev_arg2, ram_ev_arg3; logic [31:0] ram_ev_flags; ee_ram_stub #(.SIZE_BYTES(RAM_SIZE_BYTES)) u_ram ( .clk(clk), .rst_n(rst_n), .rd_en(ram_rd_en), .rd_addr(ram_rd_addr), .rd_data(ram_rd_data), .rd_valid(ram_rd_valid), .wr_en(ram_wr_en), .wr_addr(ram_wr_addr), .wr_data(ram_wr_data), .wr_be(ram_wr_be), .master_id(ram_master_id), .ev_valid(ram_ev_valid), .ev_subsys(ram_ev_subsys), .ev_event(ram_ev_event), .ev_arg0(ram_ev_arg0), .ev_arg1(ram_ev_arg1), .ev_arg2(ram_ev_arg2), .ev_arg3(ram_ev_arg3), .ev_flags(ram_ev_flags) ); assign ram_master_id = ram_rd_en ? 8'd1 : 8'd0; // ------------------------------------------------------------------ // DMAC (channel 5) // ------------------------------------------------------------------ logic dmac_reg_wr_en; logic [7:0] dmac_reg_offset; logic [31:0] dmac_reg_wr_data; logic dmac_mem_rd_en; logic [31:0] dmac_mem_rd_addr; logic [127:0] map_to_dmac_rd_data; logic map_to_dmac_rd_valid; logic dmac_ep_valid; logic [127:0] dmac_ep_data; logic dmac_ep_last; logic dmac_ep_ready; logic dmac_ev_valid; trace_pkg::subsys_e dmac_ev_subsys; trace_pkg::event_e dmac_ev_event; logic [63:0] dmac_ev_arg0, dmac_ev_arg1, dmac_ev_arg2, dmac_ev_arg3; logic [31:0] dmac_ev_flags; dmac_reg_stub #(.CHANNEL(4'd5), .PATH_ID(4'd5)) u_dmac ( .clk(clk), .rst_n(rst_n), .reg_wr_en(dmac_reg_wr_en), .reg_offset(dmac_reg_offset), .reg_wr_data(dmac_reg_wr_data), .reg_rd_en(1'b0), .reg_rd_data(), .reg_rd_valid(), .mem_rd_en(dmac_mem_rd_en), .mem_rd_addr(dmac_mem_rd_addr), .mem_rd_data(map_to_dmac_rd_data), .mem_rd_valid(map_to_dmac_rd_valid), .ep_valid(dmac_ep_valid), .ep_data(dmac_ep_data), .ep_last(dmac_ep_last), .ep_ready(dmac_ep_ready), .irq_completion_o(), .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys), .ev_event(dmac_ev_event), .ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1), .ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3), .ev_flags(dmac_ev_flags) ); // ------------------------------------------------------------------ // ee_memory_map_stub // ------------------------------------------------------------------ logic map_ram_rd_en; logic [24:0] map_ram_rd_addr; ee_memory_map_stub u_map ( .clk(clk), .rst_n(rst_n), .ee_rd_en(1'b0), .ee_rd_addr(32'd0), .ee_rd_data(), .ee_rd_valid(), .ee_wr_en(1'b0), .ee_wr_addr(32'd0), .ee_wr_data(32'd0), .ee_wr_be(4'd0), .dmac_rd_en(dmac_mem_rd_en), .dmac_rd_addr(dmac_mem_rd_addr), .dmac_rd_data(map_to_dmac_rd_data), .dmac_rd_valid(map_to_dmac_rd_valid), .bios_rd_en(), .bios_rd_addr(), .bios_rd_data(32'd0), .bios_rd_valid(1'b0), .ram_rd_en(map_ram_rd_en), .ram_rd_addr(map_ram_rd_addr), .ram_rd_data(ram_rd_data), .ram_rd_valid(ram_rd_valid), .bridge_wr_en(1'b0), .bridge_wr_addr(32'd0), .bridge_wr_data(128'd0), .bridge_wr_be(16'd0), .bridge_master_id(8'd0), .ram_wr_en(), .ram_wr_addr(), .ram_wr_data(), .ram_wr_be(), .ram_master_id(), .ee_dmac_ch2_wr_en(), .ee_dmac_ch2_wr_addr(), .ee_dmac_ch2_wr_data(), .ee_dmac_ch2_rd_en(), .ee_dmac_ch2_rd_addr(), .ee_dmac_ch2_rd_data(32'd0), .ee_dmac_ch2_rd_valid(1'b0), .ee_intc_wr_en(), .ee_intc_wr_addr(), .ee_intc_wr_data(), .ee_intc_rd_en(), .ee_intc_rd_addr(), .ee_intc_rd_data(32'd0), .ee_intc_rd_valid(1'b0), .ee_misc_mmio_wr_en(), .ee_misc_mmio_wr_addr(), .ee_misc_mmio_wr_data(), .ee_misc_mmio_wr_be(), .ee_misc_mmio_rd_en(), .ee_misc_mmio_rd_addr(), .ee_misc_mmio_rd_data(32'd0), .ee_misc_mmio_rd_valid(1'b0), .ee_biu_wr_en(), .ee_biu_wr_addr(), .ee_biu_wr_data(), .ee_biu_wr_be(), .ee_biu_rd_en(), .ee_biu_rd_addr(), .ee_biu_rd_data(32'd0), .ee_biu_rd_valid(1'b0), .ev_valid(), .ev_subsys(), .ev_event(), .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags() ); assign ram_rd_en = map_ram_rd_en; assign ram_rd_addr = map_ram_rd_addr[RAM_ADDR_W-1:0]; // ------------------------------------------------------------------ // sif_dma_stub (receive side) // ------------------------------------------------------------------ logic sif_rd_en; logic [SIF_IDX_W-1:0] sif_rd_idx; logic [127:0] sif_rd_data; logic sif_rd_valid; logic [31:0] sif_rx_count; logic sif_last_seen; logic sif_full; logic sif_dma_ev_valid; trace_pkg::subsys_e sif_dma_ev_subsys; trace_pkg::event_e sif_dma_ev_event; logic [63:0] sif_dma_ev_arg0, sif_dma_ev_arg1, sif_dma_ev_arg2, sif_dma_ev_arg3; logic [31:0] sif_dma_ev_flags; sif_dma_stub #(.DEPTH(SIF_DEPTH)) u_sif_dma ( .clk(clk), .rst_n(rst_n), .in_valid(dmac_ep_valid), .in_data(dmac_ep_data), .in_last(dmac_ep_last), .in_ready(dmac_ep_ready), .rd_en(sif_rd_en), .rd_idx(sif_rd_idx), .rd_data(sif_rd_data), .rd_valid(sif_rd_valid), .stall_in(1'b0), .rx_count(sif_rx_count), .last_seen(sif_last_seen), .full_o(sif_full), .ev_valid(sif_dma_ev_valid), .ev_subsys(sif_dma_ev_subsys), .ev_event(sif_dma_ev_event), .ev_arg0(sif_dma_ev_arg0), .ev_arg1(sif_dma_ev_arg1), .ev_arg2(sif_dma_ev_arg2), .ev_arg3(sif_dma_ev_arg3), .ev_flags(sif_dma_ev_flags) ); // ------------------------------------------------------------------ // sif_mailbox_stub (control plane storage) // ------------------------------------------------------------------ logic mbx_ee_wr_en, mbx_ee_rd_en; logic [7:0] mbx_ee_addr; logic [31:0] mbx_ee_wr_data; logic [31:0] mbx_ee_rd_data; logic mbx_ee_rd_valid; logic peer_rd_en; logic [7:0] peer_rd_addr; logic [31:0] peer_rd_data; logic peer_rd_valid; logic peer_wr_en; logic [7:0] peer_wr_addr; logic [31:0] peer_wr_data; logic mbx_ev_valid; trace_pkg::subsys_e mbx_ev_subsys; trace_pkg::event_e mbx_ev_event; logic [63:0] mbx_ev_arg0, mbx_ev_arg1, mbx_ev_arg2, mbx_ev_arg3; logic [31:0] mbx_ev_flags; sif_mailbox_stub u_mailbox ( .clk(clk), .rst_n(rst_n), .ee_wr_en(mbx_ee_wr_en), .ee_rd_en(mbx_ee_rd_en), .ee_addr(mbx_ee_addr), .ee_wr_data(mbx_ee_wr_data), .ee_rd_data(mbx_ee_rd_data), .ee_rd_valid(mbx_ee_rd_valid), .iop_wr_en(peer_wr_en), .iop_rd_en(peer_rd_en), .iop_addr(peer_wr_en ? peer_wr_addr : peer_rd_addr), .iop_wr_data(peer_wr_data), .iop_rd_data(peer_rd_data), .iop_rd_valid(peer_rd_valid), .ev_valid(mbx_ev_valid), .ev_subsys(mbx_ev_subsys), .ev_event(mbx_ev_event), .ev_arg0(mbx_ev_arg0), .ev_arg1(mbx_ev_arg1), .ev_arg2(mbx_ev_arg2), .ev_arg3(mbx_ev_arg3), .ev_flags(mbx_ev_flags) ); // ------------------------------------------------------------------ // sif_dma_ack_peer_stub (the combiner under test) // ------------------------------------------------------------------ logic peer_done; logic [31:0] peer_ack_count; sif_dma_ack_peer_stub u_peer ( .clk(clk), .rst_n(rst_n), .obs_rd_en(peer_rd_en), .obs_rd_addr(peer_rd_addr), .obs_rd_data(peer_rd_data), .obs_rd_valid(peer_rd_valid), .resp_wr_en(peer_wr_en), .resp_wr_addr(peer_wr_addr), .resp_wr_data(peer_wr_data), .payload_complete(sif_last_seen), .done_o(peer_done), .ack_count_o(peer_ack_count) ); // ------------------------------------------------------------------ // Trace sinks (just the ones we reason about) // ------------------------------------------------------------------ trace_sink_stub #(.FILENAME("sif_combined_dmac.trace"), .SINK_LABEL("dmac")) u_trace_dmac (.clk(clk), .rst_n(rst_n), .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys), .ev_event(dmac_ev_event), .ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1), .ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3), .ev_flags(dmac_ev_flags)); trace_sink_stub #(.FILENAME("sif_combined_sif_dma.trace"), .SINK_LABEL("sif_dma")) u_trace_sifdma (.clk(clk), .rst_n(rst_n), .ev_valid(sif_dma_ev_valid), .ev_subsys(sif_dma_ev_subsys), .ev_event(sif_dma_ev_event), .ev_arg0(sif_dma_ev_arg0), .ev_arg1(sif_dma_ev_arg1), .ev_arg2(sif_dma_ev_arg2), .ev_arg3(sif_dma_ev_arg3), .ev_flags(sif_dma_ev_flags)); trace_sink_stub #(.FILENAME("sif_combined_mailbox.trace"), .SINK_LABEL("mbx")) u_trace_mbx (.clk(clk), .rst_n(rst_n), .ev_valid(mbx_ev_valid), .ev_subsys(mbx_ev_subsys), .ev_event(mbx_ev_event), .ev_arg0(mbx_ev_arg0), .ev_arg1(mbx_ev_arg1), .ev_arg2(mbx_ev_arg2), .ev_arg3(mbx_ev_arg3), .ev_flags(mbx_ev_flags)); // ------------------------------------------------------------------ // Interface-visible cycle counter + ordering observer // ------------------------------------------------------------------ longint unsigned cycle_count; logic sif_last_seen_d1; longint unsigned first_last_seen_cycle; longint unsigned first_peer_smcom_write_cycle; int dma_done_count; int peer_writes_iop_side; int errors; initial begin cycle_count = 0; sif_last_seen_d1 = 1'b0; first_last_seen_cycle = 64'hFFFF_FFFF_FFFF_FFFF; first_peer_smcom_write_cycle = 64'hFFFF_FFFF_FFFF_FFFF; dma_done_count = 0; peer_writes_iop_side = 0; errors = 0; end always_ff @(posedge clk) begin cycle_count <= cycle_count + 64'd1; sif_last_seen_d1 <= sif_last_seen; // First rising edge of last_seen. if (!sif_last_seen_d1 && sif_last_seen && first_last_seen_cycle == 64'hFFFF_FFFF_FFFF_FFFF) first_last_seen_cycle <= cycle_count; // First IOP-side (peer) WRITE to SMCOM on the mailbox trace. if (mbx_ev_valid && mbx_ev_event == trace_pkg::EV_WRITE && mbx_ev_arg2[7:0] == 8'd1 && // side_id == IOP (peer) mbx_ev_arg0[7:0] == SMCOM && first_peer_smcom_write_cycle == 64'hFFFF_FFFF_FFFF_FFFF) first_peer_smcom_write_cycle <= cycle_count; // Count peer's IOP-side mailbox writes (expect 2: SMCOM, SMFLG). if (mbx_ev_valid && mbx_ev_event == trace_pkg::EV_WRITE && mbx_ev_arg2[7:0] == 8'd1) peer_writes_iop_side <= peer_writes_iop_side + 1; if (dmac_ev_valid && dmac_ev_event == trace_pkg::EV_DMA_DONE) dma_done_count <= dma_done_count + 1; end // ------------------------------------------------------------------ // Payload + helpers // ------------------------------------------------------------------ logic [127:0] expected_payload [0:QWC_VAL-1]; initial begin expected_payload[0] = 128'h0A0A_0A0A_1010_1010_0A0A_0A0A_1010_1010; expected_payload[1] = 128'h0B0B_0B0B_2020_2020_0B0B_0B0B_2020_2020; expected_payload[2] = 128'h0C0C_0C0C_3030_3030_0C0C_0C0C_3030_3030; expected_payload[3] = 128'h0D0D_0D0D_4040_4040_0D0D_0D0D_4040_4040; end task automatic write_dmac(input logic [7:0] offset, input logic [31:0] data); @(negedge clk); dmac_reg_wr_en = 1'b1; dmac_reg_offset = offset; dmac_reg_wr_data = data; @(negedge clk); dmac_reg_wr_en = 1'b0; dmac_reg_offset = 8'd0; dmac_reg_wr_data = 32'd0; endtask task automatic preload_ram_qword(input logic [RAM_ADDR_W-1:0] addr, input logic [127:0] data); @(negedge clk); ram_wr_en = 1'b1; ram_wr_addr = addr; ram_wr_data = data; ram_wr_be = 16'hFFFF; @(negedge clk); ram_wr_en = 1'b0; ram_wr_addr = '0; ram_wr_data = 128'd0; ram_wr_be = 16'd0; endtask task automatic ee_mbx_write(input logic [7:0] addr, input logic [31:0] data); @(negedge clk); mbx_ee_wr_en = 1'b1; mbx_ee_addr = addr; mbx_ee_wr_data = data; @(negedge clk); mbx_ee_wr_en = 1'b0; mbx_ee_addr = 8'd0; mbx_ee_wr_data = 32'd0; endtask task automatic ee_mbx_read_expect(input logic [7:0] addr, input logic [31:0] expected, input string label); @(negedge clk); mbx_ee_rd_en = 1'b1; mbx_ee_addr = addr; @(negedge clk); mbx_ee_rd_en = 1'b0; mbx_ee_addr = 8'd0; if (mbx_ee_rd_data !== expected || mbx_ee_rd_valid !== 1'b1) begin $error("[tb_sif_combined_ctrl_data] EE read %s: got 0x%08h valid=%0b expected 0x%08h", label, mbx_ee_rd_data, mbx_ee_rd_valid, expected); errors = errors + 1; end endtask task automatic sif_read_slot(input logic [SIF_IDX_W-1:0] idx, output logic [127:0] data); @(negedge clk); sif_rd_en = 1'b1; sif_rd_idx = idx; @(negedge clk); sif_rd_en = 1'b0; sif_rd_idx = '0; data = sif_rd_data; endtask // ------------------------------------------------------------------ // Stimulus // ------------------------------------------------------------------ int ack_before_dma_count; logic [127:0] rb; initial begin rst_n = 1'b0; dmac_reg_wr_en = 1'b0; dmac_reg_offset = 8'd0; dmac_reg_wr_data = 32'd0; ram_wr_en = 1'b0; ram_wr_addr = '0; ram_wr_data = 128'd0; ram_wr_be = 16'd0; sif_rd_en = 1'b0; sif_rd_idx = '0; mbx_ee_wr_en = 1'b0; mbx_ee_rd_en = 1'b0; mbx_ee_addr = 8'd0; mbx_ee_wr_data = 32'd0; repeat (4) @(posedge clk); rst_n = 1'b1; repeat (2) @(posedge clk); // Preload RAM payload. begin : do_preload logic [31:0] a; for (int i = 0; i < QWC_VAL; i++) begin a = PAYLOAD_MADR + (i * 32'd16); preload_ram_qword(a[RAM_ADDR_W-1:0], expected_payload[i]); end end // 1. EE publishes command and raises the doorbell BEFORE kicking DMA. ee_mbx_write(MSCOM, CMD_VALUE); ee_mbx_write(MSFLG, CMD_PENDING_BIT); // 2. Observation window: doorbell is up, DMA has NOT started. The // peer must not ack yet (payload_complete is still 0). repeat (40) @(posedge clk); ack_before_dma_count = peer_ack_count; if (ack_before_dma_count !== 0) begin $error("[tb_sif_combined_ctrl_data] peer acked before DMA even started (count=%0d)", ack_before_dma_count); errors = errors + 1; end // 3. Kick off the DMA transfer. write_dmac(8'h10, PAYLOAD_MADR); write_dmac(8'h20, 32'd4); write_dmac(8'h00, 32'h0000_0001); // 4. Wait for the peer to ack (should happen only after last_seen). begin : wait_for_ack int spin; spin = 0; while (peer_ack_count < 32'd1 && spin < 400) begin @(posedge clk); spin = spin + 1; end if (peer_ack_count < 32'd1) begin $error("[tb_sif_combined_ctrl_data] peer never acked"); errors = errors + 1; end end // 5. Ordering property: peer's first SMCOM write must happen AFTER // last_seen's rising edge, observed interface-side only. if (first_last_seen_cycle == 64'hFFFF_FFFF_FFFF_FFFF) begin $error("[tb_sif_combined_ctrl_data] last_seen never went high"); errors = errors + 1; end if (first_peer_smcom_write_cycle == 64'hFFFF_FFFF_FFFF_FFFF) begin $error("[tb_sif_combined_ctrl_data] peer never wrote SMCOM"); errors = errors + 1; end if (first_peer_smcom_write_cycle <= first_last_seen_cycle) begin $error("[tb_sif_combined_ctrl_data] peer wrote SMCOM at cycle %0d, not after last_seen (cycle %0d)", first_peer_smcom_write_cycle, first_last_seen_cycle); errors = errors + 1; end // 6. EE reads SMCOM and SMFLG — interface-visible ack arrived. repeat (2) @(posedge clk); ee_mbx_read_expect(SMCOM, CMD_VALUE, "SMCOM after combined ack"); ee_mbx_read_expect(SMFLG, CMD_ACK_BIT, "SMFLG after combined ack"); // 7. Payload integrity — all 4 qwords landed in order. for (int i = 0; i < QWC_VAL; i++) begin sif_read_slot(SIF_IDX_W'(i), rb); if (rb !== expected_payload[i]) begin $error("[tb_sif_combined_ctrl_data] slot[%0d]: got 0x%032h expected 0x%032h", i, rb, expected_payload[i]); errors = errors + 1; end end repeat (4) @(posedge clk); // ------------------------------------------------------------------ $display("[tb_sif_combined_ctrl_data] pre_dma_acks=%0d ack_count=%0d dma_done=%0d peer_writes=%0d last_seen_cy=%0d smcom_wr_cy=%0d errors=%0d", ack_before_dma_count, peer_ack_count, dma_done_count, peer_writes_iop_side, first_last_seen_cycle, first_peer_smcom_write_cycle, errors); if (errors == 0 && ack_before_dma_count == 0 && peer_ack_count == 32'd1 && dma_done_count == 1 && peer_writes_iop_side == 2 && first_peer_smcom_write_cycle > first_last_seen_cycle) $display("[tb_sif_combined_ctrl_data] PASS"); else $display("[tb_sif_combined_ctrl_data] FAIL"); $finish; end initial begin #400000; $error("[tb_sif_combined_ctrl_data] timeout"); $finish; end endmodule : tb_sif_combined_ctrl_data