// retroDE_ps2 — tb_iop_ram_stub
//
// Unit test for iop_ram_stub. Mirrors the style of other primitive tests
// (bios_rom_stub / ee_ram_stub) — drive reads and writes from the TB,
// verify round-trip data, check trace emission and region/master tagging.
//
// Scenarios:
//   1. Post-reset read returns zero (zero-init)
//   2. Byte-wide writes with partial byte enables
//   3. Word-wide writes
//   4. Read-back matches write pattern
//   5. Trace events tagged as SUBSYS_IOP with region=IOP_RAM and
//      caller-provided master_id

`timescale 1ns/1ps

module tb_iop_ram_stub;

    localparam int SIZE_BYTES = 4 * 1024;
    localparam int ADDR_W     = $clog2(SIZE_BYTES);

    logic clk;
    logic rst_n;

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

    logic              rd_en;
    logic [ADDR_W-1:0] rd_addr;
    logic [31:0]       rd_data;
    logic              rd_valid;

    logic              wr_en;
    logic [ADDR_W-1:0] wr_addr;
    logic [31:0]       wr_data;
    logic [3:0]        wr_be;

    logic [7:0]        master_id;

    logic                ev_valid;
    trace_pkg::subsys_e  ev_subsys;
    trace_pkg::event_e   ev_event;
    logic [63:0]         ev_arg0, ev_arg1, ev_arg2, ev_arg3;
    logic [31:0]         ev_flags;

    iop_ram_stub #(.SIZE_BYTES(SIZE_BYTES)) u_iop_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(rd_en), .rd_addr(rd_addr),
        .rd_data(rd_data), .rd_valid(rd_valid),
        .wr_en(wr_en), .wr_addr(wr_addr),
        .wr_data(wr_data), .wr_be(wr_be),
        .master_id(master_id),
        .ev_valid(ev_valid), .ev_subsys(ev_subsys),
        .ev_event(ev_event),
        .ev_arg0(ev_arg0), .ev_arg1(ev_arg1),
        .ev_arg2(ev_arg2), .ev_arg3(ev_arg3),
        .ev_flags(ev_flags)
    );

    trace_sink_stub #(.FILENAME("iop_ram.trace"), .SINK_LABEL("iop_ram"))
        u_trace (.clk(clk), .rst_n(rst_n),
            .ev_valid(ev_valid), .ev_subsys(ev_subsys),
            .ev_event(ev_event), .ev_arg0(ev_arg0),
            .ev_arg1(ev_arg1), .ev_arg2(ev_arg2),
            .ev_arg3(ev_arg3), .ev_flags(ev_flags));

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

    int iop_reads;
    int iop_writes;
    int iop_region_hits;
    int errors;

    initial begin
        iop_reads       = 0;
        iop_writes      = 0;
        iop_region_hits = 0;
        errors          = 0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && ev_valid && ev_subsys == trace_pkg::SUBSYS_IOP) begin
            if (ev_event == trace_pkg::EV_READ)  iop_reads  <= iop_reads  + 1;
            if (ev_event == trace_pkg::EV_WRITE) iop_writes <= iop_writes + 1;
            if (ev_arg3[7:0] == 8'd2) iop_region_hits <= iop_region_hits + 1;
        end
    end

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

    task automatic do_write(input logic [ADDR_W-1:0] addr,
                            input logic [31:0] data,
                            input logic [3:0] be);
        @(negedge clk);
        wr_en   = 1'b1;
        wr_addr = addr;
        wr_data = data;
        wr_be   = be;
        @(negedge clk);
        wr_en   = 1'b0;
        wr_addr = '0;
        wr_data = 32'd0;
        wr_be   = 4'd0;
    endtask

    task automatic do_read_expect(input logic [ADDR_W-1:0] addr,
                                  input logic [31:0] expected,
                                  input string label);
        @(negedge clk);
        rd_en   = 1'b1;
        rd_addr = addr;
        @(negedge clk);
        rd_en   = 1'b0;
        rd_addr = '0;
        if (rd_data !== expected || rd_valid !== 1'b1) begin
            $error("[tb_iop_ram_stub] read %s: got 0x%08h valid=%0b expected 0x%08h",
                   label, rd_data, rd_valid, expected);
            errors = errors + 1;
        end
    endtask

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

    logic [31:0] before_read;

    initial begin
        rst_n     = 1'b0;
        rd_en     = 1'b0;
        rd_addr   = '0;
        wr_en     = 1'b0;
        wr_addr   = '0;
        wr_data   = 32'd0;
        wr_be     = 4'd0;
        master_id = 8'd0;

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

        // 1. Zero-init check
        do_read_expect({ADDR_W{1'b0}}, 32'h0000_0000, "addr 0x0 after reset");

        // 2. Word-wide write + read-back
        master_id = 8'd0;                    // TB direct
        do_write({ADDR_W{1'b0}}, 32'hCAFE_BABE, 4'b1111);
        do_read_expect({ADDR_W{1'b0}}, 32'hCAFE_BABE, "word write round-trip");

        // 3. Partial byte-enable write: preserve upper half, overwrite lower
        //    (start from known word value, write only be=0x3 with new data)
        do_write({ADDR_W{1'b0}}, 32'h1234_0000, 4'b0011);
        do_read_expect({ADDR_W{1'b0}}, 32'hCAFE_0000, "partial byte-enable low");

        // 4. Non-zero master_id — IOP CPU hypothetical
        master_id = 8'd2;
        do_write(ADDR_W'(32'h0010), 32'hDEAD_BEEF, 4'b1111);
        do_read_expect(ADDR_W'(32'h0010), 32'hDEAD_BEEF, "IOP-tagged write");

        // 5. Address-distinct read — confirm storage independence
        do_read_expect(ADDR_W'(32'h0020), 32'h0000_0000, "untouched slot");

        repeat (4) @(posedge clk);

        // ------------------------------------------------------------------
        $display("[tb_iop_ram_stub] iop_reads=%0d iop_writes=%0d iop_region_hits=%0d errors=%0d",
                 iop_reads, iop_writes, iop_region_hits, errors);

        if (iop_reads  < 4) $error("expected >= 4 IOP read events, got %0d",  iop_reads);
        if (iop_writes < 3) $error("expected >= 3 IOP write events, got %0d", iop_writes);
        if (iop_region_hits != iop_reads + iop_writes)
            $error("region tag inconsistent: reads+writes=%0d region_hits=%0d",
                   iop_reads + iop_writes, iop_region_hits);

        if (errors == 0 && iop_reads >= 4 && iop_writes >= 3 &&
            iop_region_hits == iop_reads + iop_writes)
            $display("[tb_iop_ram_stub] PASS");
        else
            $display("[tb_iop_ram_stub] FAIL");

        $finish;
    end

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

endmodule : tb_iop_ram_stub
