// retroDE_ps2 — tb_iop_fetch_through_map
//
// First IOP-side execution-visible traffic. Wires:
//
//   iop_fetch_stub → iop_memory_map_stub → iop_ram_stub
//
// TB preloads RAM with a distinctive pattern through the map's write
// port, then enables the fetcher and verifies that the resulting PC
// stream and data payloads match what was loaded.
//
// Pass criteria:
//   - sequential PC progression from RESET_VECTOR, step +4 per fetch
//   - returned data matches the preloaded pattern
//   - trace tagging correct: SUBSYS_IOP, IFETCH event, IOP_RAM region
//     at the map layer, IOP CPU master_id (2) propagated through
//   - at least N fetches observed (16 in this test)

`timescale 1ns/1ps

module tb_iop_fetch_through_map;

    localparam int RAM_BYTES    = 4 * 1024;
    localparam int RAM_ADDR_W   = $clog2(RAM_BYTES);
    localparam logic [31:0] RESET_VECTOR = 32'h0000_0000;
    localparam int N_FETCHES   = 16;
    localparam int PRELOAD_CNT = 32;     // cover fetch window plus slack

    logic clk;
    logic rst_n;

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

    // ------------------------------------------------------------------
    // Fetcher
    // ------------------------------------------------------------------

    logic        fetch_enable;
    logic        fetch_rd_en;
    logic [31:0] fetch_rd_addr;
    logic [31:0] fetch_rd_data;
    logic        fetch_rd_valid;

    logic                fetch_ev_valid;
    trace_pkg::subsys_e  fetch_ev_subsys;
    trace_pkg::event_e   fetch_ev_event;
    logic [63:0]         fetch_ev_arg0, fetch_ev_arg1, fetch_ev_arg2, fetch_ev_arg3;
    logic [31:0]         fetch_ev_flags;

    iop_fetch_stub #(.RESET_VECTOR(RESET_VECTOR)) u_fetch (
        .clk(clk), .rst_n(rst_n), .enable(fetch_enable),
        .rd_en(fetch_rd_en), .rd_addr(fetch_rd_addr),
        .rd_data(fetch_rd_data), .rd_valid(fetch_rd_valid),
        .ev_valid(fetch_ev_valid), .ev_subsys(fetch_ev_subsys),
        .ev_event(fetch_ev_event),
        .ev_arg0(fetch_ev_arg0), .ev_arg1(fetch_ev_arg1),
        .ev_arg2(fetch_ev_arg2), .ev_arg3(fetch_ev_arg3),
        .ev_flags(fetch_ev_flags)
    );

    // ------------------------------------------------------------------
    // Map — shared by TB (preload writes) and fetcher (runtime reads)
    // ------------------------------------------------------------------

    // Preload-side write signals (driven by TB)
    logic        tb_wr_en;
    logic [31:0] tb_wr_addr;
    logic [31:0] tb_wr_data;
    logic [3:0]  tb_wr_be;

    // When the fetcher is enabled, master_id reflects IOP CPU (2). During
    // the preload window, the TB owns writes and tags them as TB_direct (0).
    logic [7:0] map_master_id;
    assign map_master_id = fetch_enable ? 8'd2 : 8'd0;

    logic        map_ram_rd_en;
    logic [20:0] map_ram_rd_addr;
    logic [31:0] ram_rd_data;
    logic        ram_rd_valid;
    logic        map_ram_wr_en;
    logic [20:0] map_ram_wr_addr;
    logic [31:0] map_ram_wr_data;
    logic [3:0]  map_ram_wr_be;
    logic [7:0]  map_ram_master_id;

    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_map (
        .clk(clk), .rst_n(rst_n),
        // Read: from fetcher
        .iop_rd_en(fetch_rd_en), .iop_rd_addr(fetch_rd_addr),
        .iop_rd_data(fetch_rd_data), .iop_rd_valid(fetch_rd_valid),
        // Write: from TB preload
        .iop_wr_en(tb_wr_en), .iop_wr_addr(tb_wr_addr),
        .iop_wr_data(tb_wr_data), .iop_wr_be(tb_wr_be),
        .master_id(map_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(),
        // 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),
        .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),
        .ram_wr_en(map_ram_wr_en), .ram_wr_addr(map_ram_wr_addr),
        .ram_wr_data(map_ram_wr_data), .ram_wr_be(map_ram_wr_be),
        .ram_master_id(map_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 backing
    // ------------------------------------------------------------------

    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_ram_stub #(.SIZE_BYTES(RAM_BYTES)) u_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(map_ram_rd_en), .rd_addr(map_ram_rd_addr[RAM_ADDR_W-1:0]),
        .rd_data(ram_rd_data), .rd_valid(ram_rd_valid),
        .wr_en(map_ram_wr_en), .wr_addr(map_ram_wr_addr[RAM_ADDR_W-1:0]),
        .wr_data(map_ram_wr_data), .wr_be(map_ram_wr_be),
        .master_id(map_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 sinks
    // ------------------------------------------------------------------

    trace_sink_stub #(.FILENAME("iop_fetch_fetcher.trace"), .SINK_LABEL("iop_fetch"))
        u_trace_fetch (.clk(clk), .rst_n(rst_n),
            .ev_valid(fetch_ev_valid), .ev_subsys(fetch_ev_subsys),
            .ev_event(fetch_ev_event), .ev_arg0(fetch_ev_arg0),
            .ev_arg1(fetch_ev_arg1), .ev_arg2(fetch_ev_arg2),
            .ev_arg3(fetch_ev_arg3), .ev_flags(fetch_ev_flags));

    trace_sink_stub #(.FILENAME("iop_fetch_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_fetch_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));

    // ------------------------------------------------------------------
    // Checkers
    // ------------------------------------------------------------------

    int   ifetch_count;
    int   map_ifetch_routed;          // map-level IOP reads with region=IOP_RAM
    int   map_master_iop_cpu_count;   // map events tagged master_id=2
    int   errors;

    wire [31:0] obs_addr     = fetch_ev_arg0[31:0];
    wire [31:0] obs_data     = fetch_ev_arg1[31:0];
    wire [31:0] obs_offset   = obs_addr - RESET_VECTOR;
    wire [31:0] obs_wordidx  = obs_offset >> 2;
    // Preloaded pattern: 0xABCD_0000 | word_idx
    wire [31:0] obs_expected = 32'hABCD_0000 | {16'd0, obs_wordidx[15:0]};

    initial begin
        ifetch_count              = 0;
        map_ifetch_routed         = 0;
        map_master_iop_cpu_count  = 0;
        errors                    = 0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && fetch_ev_valid && fetch_ev_event == trace_pkg::EV_IFETCH) begin
            ifetch_count <= ifetch_count + 1;
            if (obs_data !== obs_expected) begin
                $error("[tb_iop_fetch_through_map] IFETCH mismatch: addr=0x%08h data=0x%08h expected=0x%08h",
                       obs_addr, obs_data, obs_expected);
                errors <= errors + 1;
            end
        end

        if (rst_n && map_ev_valid &&
            map_ev_subsys == trace_pkg::SUBSYS_IOP) begin
            if (map_ev_event == trace_pkg::EV_READ &&
                map_ev_arg3[7:0] == 8'd2)                 // region=IOP_RAM
                map_ifetch_routed <= map_ifetch_routed + 1;
            if (map_ev_arg2[7:0] == 8'd2)                 // master=IOP_CPU
                map_master_iop_cpu_count <= map_master_iop_cpu_count + 1;
        end
    end

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

    task automatic preload_word(input logic [31:0] addr,
                                input logic [31:0] data);
        @(negedge clk);
        tb_wr_en   = 1'b1;
        tb_wr_addr = addr;
        tb_wr_data = data;
        tb_wr_be   = 4'b1111;
        @(negedge clk);
        tb_wr_en   = 1'b0;
        tb_wr_addr = 32'd0;
        tb_wr_data = 32'd0;
        tb_wr_be   = 4'd0;
    endtask

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

    initial begin
        rst_n        = 1'b0;
        fetch_enable = 1'b0;
        tb_wr_en     = 1'b0;
        tb_wr_addr   = 32'd0;
        tb_wr_data   = 32'd0;
        tb_wr_be     = 4'd0;

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

        // Preload a window wider than N_FETCHES so any overshoot in the
        // run loop still hits preloaded words. Pattern is consistent:
        //   mem[word_idx] = 0xABCD_0000 | word_idx
        for (int i = 0; i < PRELOAD_CNT; i++) begin
            preload_word(RESET_VECTOR + (i * 32'd4),
                         32'hABCD_0000 | {16'd0, 16'(i)});
        end

        // Enable the fetcher and let it run through the preloaded window.
        @(negedge clk);
        fetch_enable = 1'b1;
        repeat (N_FETCHES + 6) @(posedge clk);
        fetch_enable = 1'b0;
        repeat (4) @(posedge clk);

        // ------------------------------------------------------------------
        $display("[tb_iop_fetch_through_map] ifetches=%0d map_routed=%0d map_iop_cpu_tagged=%0d errors=%0d",
                 ifetch_count, map_ifetch_routed, map_master_iop_cpu_count, errors);

        if (ifetch_count < N_FETCHES)
            $error("expected >= %0d IFETCHes, got %0d", N_FETCHES, ifetch_count);
        if (map_ifetch_routed < N_FETCHES)
            $error("expected >= %0d map-routed IOP reads (region=IOP_RAM), got %0d",
                   N_FETCHES, map_ifetch_routed);
        if (map_master_iop_cpu_count < N_FETCHES)
            $error("expected >= %0d map events tagged master=IOP_CPU, got %0d",
                   N_FETCHES, map_master_iop_cpu_count);

        if (errors == 0 && ifetch_count >= N_FETCHES &&
            map_ifetch_routed >= N_FETCHES &&
            map_master_iop_cpu_count >= N_FETCHES)
            $display("[tb_iop_fetch_through_map] PASS");
        else
            $display("[tb_iop_fetch_through_map] FAIL");

        $finish;
    end

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

endmodule : tb_iop_fetch_through_map
