// retroDE_ps2 — tb_iop_memory_map_stub
//
// Unit test for iop_memory_map_stub wired to iop_ram_stub. TB drives the
// map's CPU-side port and verifies that RAM-region accesses land in the
// backing store while non-RAM accesses deterministically fault.
//
// Scenarios:
//   1. Word write into IOP RAM region, read back — round-trip
//   2. Same write/read using a kseg1-aliased address (0xA0000000|offset)
//      — confirms kseg stripping routes to the same physical slot
//   3. Unmapped read — returns 0xDEADBEEF, emits IOP UNMAPPED trace
//   4. Unmapped write — no effect on RAM, emits IOP UNMAPPED trace
//   5. Trace tagging: region=IOP_RAM on routed accesses, region=0xFF on
//      unmapped, master_id preserved through the map

`timescale 1ns/1ps

module tb_iop_memory_map_stub;

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

    logic clk;
    logic rst_n;

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

    // ------------------------------------------------------------------
    // DUTs
    // ------------------------------------------------------------------

    // Map <-> IOP CPU (TB-driven)
    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 [7:0]  master_id;

    // Map <-> RAM
    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;

    // Map trace
    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;

    // RAM trace
    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;

    iop_memory_map_stub u_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(master_id),
        // Bridge port — unused by this TB
        .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(),
        .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),
        // SIF register-shell port — unused by this TB
        .sif_rd_en(), .sif_rd_addr(),
        .sif_rd_data(32'd0), .sif_rd_valid(1'b0),
        .sif_wr_en(), .sif_wr_addr(), .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),
        .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_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(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)
    );

    trace_sink_stub #(.FILENAME("iop_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_map_ram.trace"), .SINK_LABEL("iop_ram"))
        u_trace_ram (.clk(clk), .rst_n(rst_n),
            .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));

    // ------------------------------------------------------------------
    // Counters (map side)
    // ------------------------------------------------------------------

    int map_reads_ram;
    int map_writes_ram;
    int map_unmapped;
    int map_master_mismatches;
    int errors;

    initial begin
        map_reads_ram        = 0;
        map_writes_ram       = 0;
        map_unmapped         = 0;
        map_master_mismatches = 0;
        errors               = 0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && map_ev_valid && map_ev_subsys == trace_pkg::SUBSYS_IOP) begin
            // Master_id preserved through the map.
            if (map_ev_arg2[7:0] !== master_id)
                map_master_mismatches <= map_master_mismatches + 1;

            if (map_ev_event == trace_pkg::EV_READ &&
                map_ev_arg3[7:0] == 8'd2)
                map_reads_ram <= map_reads_ram + 1;
            if (map_ev_event == trace_pkg::EV_WRITE &&
                map_ev_arg3[7:0] == 8'd2)
                map_writes_ram <= map_writes_ram + 1;
            if (map_ev_event == trace_pkg::EV_UNMAPPED)
                map_unmapped <= map_unmapped + 1;
        end
    end

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

    task automatic do_write(input logic [31:0] addr,
                            input logic [31:0] data,
                            input logic [3:0] be);
        @(negedge clk);
        iop_wr_en   = 1'b1;
        iop_wr_addr = addr;
        iop_wr_data = data;
        iop_wr_be   = be;
        @(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 do_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_memory_map_stub] read %s: got 0x%08h valid=%0b expected 0x%08h",
                   label, iop_rd_data, iop_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;
        master_id   = 8'd2;                      // IOP_CPU

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

        // 1. RAM-region write + read-back (physical address).
        do_write(32'h0000_0100, 32'hCAFE_BABE, 4'b1111);
        do_read_expect(32'h0000_0100, 32'hCAFE_BABE, "RAM phys 0x100");

        // 2. kseg1-aliased access to the same physical slot.
        do_read_expect(32'hA000_0100, 32'hCAFE_BABE, "RAM kseg1 alias 0xA0000100");

        // 3. RAM write through a kseg0 alias; read-back via physical.
        do_write(32'h8000_0200, 32'h1234_5678, 4'b1111);
        do_read_expect(32'h0000_0200, 32'h1234_5678, "kseg0-aliased write landed at phys 0x200");

        // 4. Unmapped read — not in RAM window.
        do_read_expect(32'h1000_0000, 32'hDEAD_BEEF, "unmapped phys 0x10000000");

        // 5. Unmapped write — shouldn't affect any backing. No easy direct
        //    readback (since there's no backing), but verify trace below.
        do_write(32'h1F80_0000, 32'h0BAD_F00D, 4'b1111);

        // 6. RAM stays intact after unmapped write.
        do_read_expect(32'h0000_0100, 32'hCAFE_BABE, "RAM survives nearby unmapped write");

        repeat (4) @(posedge clk);

        // ------------------------------------------------------------------
        $display("[tb_iop_memory_map_stub] map_reads_ram=%0d map_writes_ram=%0d map_unmapped=%0d master_mismatches=%0d errors=%0d",
                 map_reads_ram, map_writes_ram, map_unmapped,
                 map_master_mismatches, errors);

        if (map_reads_ram  < 4) $error("expected >= 4 RAM-routed reads, got %0d", map_reads_ram);
        if (map_writes_ram < 2) $error("expected >= 2 RAM-routed writes, got %0d", map_writes_ram);
        if (map_unmapped   < 2) $error("expected >= 2 UNMAPPED events, got %0d", map_unmapped);
        if (map_master_mismatches != 0)
            $error("master_id mismatch at map trace: %0d occurrences", map_master_mismatches);

        if (errors == 0 &&
            map_reads_ram  >= 4 &&
            map_writes_ram >= 2 &&
            map_unmapped   >= 2 &&
            map_master_mismatches == 0)
            $display("[tb_iop_memory_map_stub] PASS");
        else
            $display("[tb_iop_memory_map_stub] FAIL");

        $finish;
    end

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

endmodule : tb_iop_memory_map_stub
