// retroDE_ps2 — tb_iop_to_sif_via_map
//
// Architectural-addressing milestone (C2/C3). Proves the IOP side can
// reach the SIF mailbox through the REAL IOP memory map at real
// architectural addresses (0x1D000000 block), not through direct ports.
//
// Chain:
//   TB (EE side) ────────────► sif_mailbox_stub (ee_* port)
//
//   TB (IOP CPU impersonation) ─► iop_memory_map_stub
//                                    │
//                             (sif_rd_*/sif_wr_* routed by SIF decode)
//                                    ▼
//                                sif_mailbox_stub (iop_* port)
//
//   iop_memory_map_stub also has iop_ram_stub attached, so the map's
//   multi-region behavior is exercised: TB hits both IOP RAM addresses
//   (phys 0x00xxxxxx) and SIF addresses (0x1D000000) through the same
//   CPU-side port.
//
// Scenarios:
//   1. EE writes MSCOM via ee_* port; IOP reads 0x1D000000 through the
//      map; read returns what EE wrote.
//   2. IOP writes SMCOM via the map (0x1D000010); EE reads via ee_* port;
//      value matches.
//   3. EE writes MSFLG; IOP reads 0x1D000020 via map; matches.
//   4. IOP writes SMFLG via map (0x1D000030); EE reads via ee_* port;
//      matches.
//   5. Cross-seam: IOP also pokes IOP RAM (phys 0x00000100) through the
//      same map, verifying the RAM and SIF regions coexist.
//   6. Unmapped IOP read (out-of-window address) still returns 0xDEADBEEF.
//
// Key trace-visible assertions:
//   - IOP map emits reads/writes tagged region=IOP_RAM (2) or SIF_REGS (3)
//     per access.
//   - SIF mailbox emits its own events tagged side_id=IOP (1) for peer
//     traffic originated through the map.

`timescale 1ns/1ps

module tb_iop_to_sif_via_map;

    localparam int RAM_BYTES    = 4 * 1024;
    localparam int RAM_ADDR_W   = $clog2(RAM_BYTES);

    localparam logic [31:0] SIF_BASE = 32'h1D00_0000;

    // SIF mailbox offsets (within the 0x1D000000 block)
    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;

    logic clk;
    logic rst_n;

    initial clk = 1'b0;
    always #5 clk = ~clk;

    // ------------------------------------------------------------------
    // IOP map — TB drives the CPU-side port
    // ------------------------------------------------------------------

    logic        iop_rd_en;
    logic [31:0] iop_rd_addr;
    logic [31:0] iop_rd_data;
    logic        iop_rd_valid;

    logic        iop_wr_en;
    logic [31:0] iop_wr_addr;
    logic [31:0] iop_wr_data;
    logic [3:0]  iop_wr_be;

    logic        ram_rd_en;
    logic [20:0] ram_rd_addr;
    logic [31:0] ram_rd_data;
    logic        ram_rd_valid;
    logic        ram_wr_en;
    logic [20:0] ram_wr_addr;
    logic [31:0] ram_wr_data;
    logic [3:0]  ram_wr_be;
    logic [7:0]  ram_master_id;

    logic        map_sif_rd_en;
    logic [7:0]  map_sif_rd_addr;
    logic [31:0] map_sif_rd_data;
    logic        map_sif_rd_valid;
    logic        map_sif_wr_en;
    logic [7:0]  map_sif_wr_addr;
    logic [31:0] map_sif_wr_data;

    logic                map_ev_valid;
    trace_pkg::subsys_e  map_ev_subsys;
    trace_pkg::event_e   map_ev_event;
    logic [63:0]         map_ev_arg0, map_ev_arg1, map_ev_arg2, map_ev_arg3;
    logic [31:0]         map_ev_flags;

    iop_memory_map_stub u_iop_map (
        .clk(clk), .rst_n(rst_n),
        .iop_rd_en(iop_rd_en), .iop_rd_addr(iop_rd_addr),
        .iop_rd_data(iop_rd_data), .iop_rd_valid(iop_rd_valid),
        .iop_wr_en(iop_wr_en), .iop_wr_addr(iop_wr_addr),
        .iop_wr_data(iop_wr_data), .iop_wr_be(iop_wr_be),
        .master_id(8'd2),                           // IOP_CPU
        // Bridge port — unused
        .bridge_wr_en(1'b0), .bridge_wr_addr(32'd0),
        .bridge_wr_data(32'd0), .bridge_wr_be(4'd0),
        .bridge_master_id(8'd0),
        // DMA read-master port — unused by this TB
        .dma_rd_en(1'b0), .dma_rd_addr(32'd0),
        .dma_master_id(8'd0),
        .dma_rd_data(), .dma_rd_valid(),
        // SIF register-shell downstream — wired to mailbox
        .sif_rd_en(map_sif_rd_en), .sif_rd_addr(map_sif_rd_addr),
        .sif_rd_data(map_sif_rd_data), .sif_rd_valid(map_sif_rd_valid),
        .sif_wr_en(map_sif_wr_en), .sif_wr_addr(map_sif_wr_addr),
        .sif_wr_data(map_sif_wr_data),
        // IOP DMAC port — unused by this TB
        .iop_dmac_rd_en(), .iop_dmac_rd_addr(),
        .iop_dmac_rd_data(32'd0), .iop_dmac_rd_valid(1'b0),
        .iop_dmac_wr_en(), .iop_dmac_wr_addr(), .iop_dmac_wr_data(),
        // IOP INTC port — unused by this TB
        .iop_intc_rd_en(), .iop_intc_rd_addr(),
        .iop_intc_rd_data(32'd0), .iop_intc_rd_valid(1'b0),
        .iop_intc_wr_en(), .iop_intc_wr_addr(), .iop_intc_wr_data(),
        .input_p1(32'd0), .input_p2(32'd0),
        // BIOS ROM port — unused by this TB
        .bios_rd_en(), .bios_rd_addr(),
        .bios_rd_data(32'd0), .bios_rd_valid(1'b0),
        // RAM downstream
        .ram_rd_en(ram_rd_en), .ram_rd_addr(ram_rd_addr),
        .ram_rd_data(ram_rd_data), .ram_rd_valid(ram_rd_valid),
        .ram_wr_en(ram_wr_en), .ram_wr_addr(ram_wr_addr),
        .ram_wr_data(ram_wr_data), .ram_wr_be(ram_wr_be),
        .ram_master_id(ram_master_id),
        .ev_valid(map_ev_valid), .ev_subsys(map_ev_subsys),
        .ev_event(map_ev_event),
        .ev_arg0(map_ev_arg0), .ev_arg1(map_ev_arg1),
        .ev_arg2(map_ev_arg2), .ev_arg3(map_ev_arg3),
        .ev_flags(map_ev_flags)
    );

    iop_ram_stub #(.SIZE_BYTES(RAM_BYTES)) u_iop_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(ram_rd_en), .rd_addr(ram_rd_addr[RAM_ADDR_W-1:0]),
        .rd_data(ram_rd_data), .rd_valid(ram_rd_valid),
        .wr_en(ram_wr_en), .wr_addr(ram_wr_addr[RAM_ADDR_W-1:0]),
        .wr_data(ram_wr_data), .wr_be(ram_wr_be),
        .master_id(ram_master_id),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    // ------------------------------------------------------------------
    // SIF mailbox — TB drives EE side directly; IOP side is driven by
    // the map's downstream sif_* port.
    // ------------------------------------------------------------------

    logic        ee_wr_en, ee_rd_en;
    logic [7:0]  ee_addr;
    logic [31:0] ee_wr_data;
    logic [31:0] ee_rd_data;
    logic        ee_rd_valid;

    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(ee_wr_en), .ee_rd_en(ee_rd_en),
        .ee_addr(ee_addr), .ee_wr_data(ee_wr_data),
        .ee_rd_data(ee_rd_data), .ee_rd_valid(ee_rd_valid),
        // IOP side driven by the map
        .iop_wr_en(map_sif_wr_en), .iop_rd_en(map_sif_rd_en),
        .iop_addr(map_sif_wr_en ? map_sif_wr_addr : map_sif_rd_addr),
        .iop_wr_data(map_sif_wr_data),
        .iop_rd_data(map_sif_rd_data), .iop_rd_valid(map_sif_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)
    );

    // ------------------------------------------------------------------
    // Trace sinks
    // ------------------------------------------------------------------

    trace_sink_stub #(.FILENAME("iop_to_sif_map.trace"), .SINK_LABEL("iop_map"))
        u_trace_map (.clk(clk), .rst_n(rst_n),
            .ev_valid(map_ev_valid), .ev_subsys(map_ev_subsys),
            .ev_event(map_ev_event), .ev_arg0(map_ev_arg0),
            .ev_arg1(map_ev_arg1), .ev_arg2(map_ev_arg2),
            .ev_arg3(map_ev_arg3), .ev_flags(map_ev_flags));

    trace_sink_stub #(.FILENAME("iop_to_sif_mailbox.trace"), .SINK_LABEL("sif_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));

    // ------------------------------------------------------------------
    // Counters
    // ------------------------------------------------------------------

    int map_sif_events;         // map events tagged region=SIF_REGS
    int map_ram_events;         // map events tagged region=IOP_RAM
    int map_unmapped_events;    // map UNMAPPED events
    int mbx_iop_side_events;    // mailbox events with side_id=IOP (1)
    int errors;

    initial begin
        map_sif_events      = 0;
        map_ram_events      = 0;
        map_unmapped_events = 0;
        mbx_iop_side_events = 0;
        errors              = 0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && map_ev_valid &&
            map_ev_subsys == trace_pkg::SUBSYS_IOP) begin
            if (map_ev_arg3[7:0] == 8'd3) map_sif_events      <= map_sif_events      + 1;
            if (map_ev_arg3[7:0] == 8'd2) map_ram_events      <= map_ram_events      + 1;
            if (map_ev_event == trace_pkg::EV_UNMAPPED)
                                          map_unmapped_events <= map_unmapped_events + 1;
        end
        if (rst_n && mbx_ev_valid && mbx_ev_arg2[7:0] == 8'd1)
            mbx_iop_side_events <= mbx_iop_side_events + 1;
    end

    // ------------------------------------------------------------------
    // Helpers
    // ------------------------------------------------------------------

    task automatic iop_write(input logic [31:0] addr, input logic [31:0] data);
        @(negedge clk);
        iop_wr_en   = 1'b1;
        iop_wr_addr = addr;
        iop_wr_data = data;
        iop_wr_be   = 4'b1111;
        @(negedge clk);
        iop_wr_en   = 1'b0;
        iop_wr_addr = 32'd0;
        iop_wr_data = 32'd0;
        iop_wr_be   = 4'd0;
    endtask

    task automatic iop_read_expect(input logic [31:0] addr,
                                   input logic [31:0] expected,
                                   input string label);
        @(negedge clk);
        iop_rd_en   = 1'b1;
        iop_rd_addr = addr;
        @(negedge clk);
        iop_rd_en   = 1'b0;
        iop_rd_addr = 32'd0;
        if (iop_rd_data !== expected || iop_rd_valid !== 1'b1) begin
            $error("[tb_iop_to_sif_via_map] IOP read %s at 0x%08h: got 0x%08h valid=%0b expected 0x%08h",
                   label, addr, iop_rd_data, iop_rd_valid, expected);
            errors = errors + 1;
        end
    endtask

    task automatic ee_write(input logic [7:0] addr, input logic [31:0] data);
        @(negedge clk);
        ee_wr_en   = 1'b1;
        ee_addr    = addr;
        ee_wr_data = data;
        @(negedge clk);
        ee_wr_en   = 1'b0;
        ee_addr    = 8'd0;
        ee_wr_data = 32'd0;
    endtask

    task automatic ee_read_expect(input logic [7:0] addr,
                                  input logic [31:0] expected,
                                  input string label);
        @(negedge clk);
        ee_rd_en = 1'b1;
        ee_addr  = addr;
        @(negedge clk);
        ee_rd_en = 1'b0;
        ee_addr  = 8'd0;
        if (ee_rd_data !== expected || ee_rd_valid !== 1'b1) begin
            $error("[tb_iop_to_sif_via_map] EE read %s: got 0x%08h valid=%0b expected 0x%08h",
                   label, ee_rd_data, ee_rd_valid, expected);
            errors = errors + 1;
        end
    endtask

    // ------------------------------------------------------------------
    // Stimulus
    // ------------------------------------------------------------------

    initial begin
        rst_n       = 1'b0;
        iop_rd_en   = 1'b0;
        iop_rd_addr = 32'd0;
        iop_wr_en   = 1'b0;
        iop_wr_addr = 32'd0;
        iop_wr_data = 32'd0;
        iop_wr_be   = 4'd0;
        ee_wr_en    = 1'b0;
        ee_rd_en    = 1'b0;
        ee_addr     = 8'd0;
        ee_wr_data  = 32'd0;

        repeat (4) @(posedge clk);
        rst_n = 1'b1;
        repeat (2) @(posedge clk);

        // Scenario 1: EE writes MSCOM; IOP reads at 0x1D000000.
        ee_write(MSCOM_OFF, 32'hCAFE_BABE);
        iop_read_expect(SIF_BASE | 32'h00, 32'hCAFE_BABE,
                        "MSCOM via IOP map (0x1D000000)");

        // Scenario 2: IOP writes SMCOM via map; EE reads.
        iop_write(SIF_BASE | 32'h10, 32'hDEAD_C0DE);
        ee_read_expect(SMCOM_OFF, 32'hDEAD_C0DE, "SMCOM after IOP-via-map write");

        // Scenario 3: EE writes MSFLG; IOP reads via map.
        ee_write(MSFLG_OFF, 32'h0000_00FF);
        iop_read_expect(SIF_BASE | 32'h20, 32'h0000_00FF,
                        "MSFLG via IOP map");

        // Scenario 4: IOP writes SMFLG via map; EE reads.
        iop_write(SIF_BASE | 32'h30, 32'h0000_AA55);
        ee_read_expect(SMFLG_OFF, 32'h0000_AA55, "SMFLG after IOP-via-map write");

        // Scenario 5: IOP pokes IOP RAM through same map.
        iop_write(32'h0000_0100, 32'h1111_2222);
        iop_read_expect(32'h0000_0100, 32'h1111_2222, "IOP RAM round-trip");

        // Scenario 6: IOP reads unmapped address → DEADBEEF.
        iop_read_expect(32'h1000_0000, 32'hDEAD_BEEF, "unmapped IOP read");

        // Scenario 7: EE-observed state unchanged by IOP's RAM/unmapped work.
        ee_read_expect(MSCOM_OFF, 32'hCAFE_BABE, "MSCOM stable after RAM + unmapped");

        repeat (4) @(posedge clk);

        // ------------------------------------------------------------------
        $display("[tb_iop_to_sif_via_map] map_sif=%0d map_ram=%0d map_unmapped=%0d mbx_iop_side=%0d errors=%0d",
                 map_sif_events, map_ram_events, map_unmapped_events,
                 mbx_iop_side_events, errors);

        if (map_sif_events  < 4)  $error("expected >= 4 map events tagged SIF_REGS, got %0d", map_sif_events);
        if (map_ram_events  < 2)  $error("expected >= 2 map events tagged IOP_RAM, got %0d", map_ram_events);
        if (map_unmapped_events < 1)
            $error("expected >= 1 map UNMAPPED event, got %0d", map_unmapped_events);
        if (mbx_iop_side_events < 4)
            $error("expected >= 4 mailbox events with side_id=IOP, got %0d", mbx_iop_side_events);

        if (errors == 0 &&
            map_sif_events  >= 4 &&
            map_ram_events  >= 2 &&
            map_unmapped_events >= 1 &&
            mbx_iop_side_events >= 4)
            $display("[tb_iop_to_sif_via_map] PASS");
        else
            $display("[tb_iop_to_sif_via_map] FAIL");

        $finish;
    end

    initial begin
        #200000;
        $error("[tb_iop_to_sif_via_map] timeout");
        $finish;
    end

endmodule : tb_iop_to_sif_via_map
