// retroDE_ps2 — tb_ee_fetch_stub
//
// Milestone B integration test: ee_fetch_stub → ee_memory_map_stub →
// bios_rom_stub. Verifies reset vector, BIOS-window fetches against the
// synthetic fixture, unmapped access handling, and trace emission on all
// three stubs.
//
// Plan ref: docs/stub_module_plan.md (Initial testbench plan).

`timescale 1ns/1ps

module tb_ee_fetch_stub;

    logic clk;
    logic rst_n;
    logic enable;

    // --- clock ---
    initial clk = 1'b0;
    always #5 clk = ~clk;    // 100 MHz sim clock

    // ------------------------------------------------------------------
    // DUTs: EE fetch → memory map → BIOS ROM
    // ------------------------------------------------------------------

    // EE <-> memory map
    logic        ee_rd_en;
    logic [31:0] ee_rd_addr;
    logic [31:0] ee_rd_data;
    logic        ee_rd_valid;

    // TB-driven write channel into the map, used to exercise the UNMAPPED
    // path without disturbing the ee_fetch_stub → bios chain. In Wave 1 no
    // writable region exists, so any write lands as UNMAPPED.
    logic        tb_wr_en;
    logic [31:0] tb_wr_addr;
    logic [31:0] tb_wr_data;
    logic [3:0]  tb_wr_be;

    // Memory map <-> BIOS ROM
    logic        bios_rd_en;
    logic [21:0] bios_rd_addr;
    logic [31:0] bios_rd_data;
    logic        bios_rd_valid;

    // Trace ports per stub
    logic                  ee_ev_valid;
    trace_pkg::subsys_e    ee_ev_subsys;
    trace_pkg::event_e     ee_ev_event;
    logic [63:0]           ee_ev_arg0, ee_ev_arg1, ee_ev_arg2, ee_ev_arg3;
    logic [31:0]           ee_ev_flags;

    logic                  mem_ev_valid;
    trace_pkg::subsys_e    mem_ev_subsys;
    trace_pkg::event_e     mem_ev_event;
    logic [63:0]           mem_ev_arg0, mem_ev_arg1, mem_ev_arg2, mem_ev_arg3;
    logic [31:0]           mem_ev_flags;

    logic                  bios_ev_valid;
    trace_pkg::subsys_e    bios_ev_subsys;
    trace_pkg::event_e     bios_ev_event;
    logic [63:0]           bios_ev_arg0, bios_ev_arg1, bios_ev_arg2, bios_ev_arg3;
    logic [31:0]           bios_ev_flags;

    ee_fetch_stub u_ee (
        .clk(clk), .rst_n(rst_n), .enable(enable),
        .rd_en(ee_rd_en), .rd_addr(ee_rd_addr),
        .rd_data(ee_rd_data), .rd_valid(ee_rd_valid),
        .ev_valid(ee_ev_valid), .ev_subsys(ee_ev_subsys), .ev_event(ee_ev_event),
        .ev_arg0(ee_ev_arg0), .ev_arg1(ee_ev_arg1),
        .ev_arg2(ee_ev_arg2), .ev_arg3(ee_ev_arg3),
        .ev_flags(ee_ev_flags)
    );

    // Wave 2.7 added DMAC + RAM ports on the map. Not exercised by this TB —
    // tie inputs to inactive values, leave outputs unconnected.
    logic [127:0] tb_dmac_rd_data_sink;
    logic         tb_dmac_rd_valid_sink;
    logic         tb_ram_rd_en_sink;
    logic [24:0]  tb_ram_rd_addr_sink;

    ee_memory_map_stub u_map (
        .clk(clk), .rst_n(rst_n),
        .ee_rd_en(ee_rd_en), .ee_rd_addr(ee_rd_addr),
        .ee_rd_data(ee_rd_data), .ee_rd_valid(ee_rd_valid),
        .ee_wr_en(tb_wr_en), .ee_wr_addr(tb_wr_addr),
        .ee_wr_data(tb_wr_data), .ee_wr_be(tb_wr_be),
        .dmac_rd_en(1'b0), .dmac_rd_addr(32'd0),
        .dmac_rd_data(tb_dmac_rd_data_sink),
        .dmac_rd_valid(tb_dmac_rd_valid_sink),
        .bios_rd_en(bios_rd_en), .bios_rd_addr(bios_rd_addr),
        .bios_rd_data(bios_rd_data), .bios_rd_valid(bios_rd_valid),
        .ram_rd_en(tb_ram_rd_en_sink), .ram_rd_addr(tb_ram_rd_addr_sink),
        .ram_rd_data(128'd0), .ram_rd_valid(1'b0),
        .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(mem_ev_valid), .ev_subsys(mem_ev_subsys), .ev_event(mem_ev_event),
        .ev_arg0(mem_ev_arg0), .ev_arg1(mem_ev_arg1),
        .ev_arg2(mem_ev_arg2), .ev_arg3(mem_ev_arg3),
        .ev_flags(mem_ev_flags)
    );

    bios_rom_stub u_bios (
        .clk(clk), .rst_n(rst_n),
        .rd_en(bios_rd_en), .rd_addr(bios_rd_addr),
        .rd_data(bios_rd_data), .rd_valid(bios_rd_valid),
        .ev_valid(bios_ev_valid), .ev_subsys(bios_ev_subsys), .ev_event(bios_ev_event),
        .ev_arg0(bios_ev_arg0), .ev_arg1(bios_ev_arg1),
        .ev_arg2(bios_ev_arg2), .ev_arg3(bios_ev_arg3),
        .ev_flags(bios_ev_flags)
    );

    // ------------------------------------------------------------------
    // Trace sinks (one per stub)
    // ------------------------------------------------------------------

    trace_sink_stub #(.FILENAME("ee_fetch.trace"),   .SINK_LABEL("ee"))
        u_trace_ee (
            .clk(clk), .rst_n(rst_n),
            .ev_valid(ee_ev_valid), .ev_subsys(ee_ev_subsys), .ev_event(ee_ev_event),
            .ev_arg0(ee_ev_arg0), .ev_arg1(ee_ev_arg1),
            .ev_arg2(ee_ev_arg2), .ev_arg3(ee_ev_arg3),
            .ev_flags(ee_ev_flags)
        );

    trace_sink_stub #(.FILENAME("ee_mem.trace"),     .SINK_LABEL("mem"))
        u_trace_mem (
            .clk(clk), .rst_n(rst_n),
            .ev_valid(mem_ev_valid), .ev_subsys(mem_ev_subsys), .ev_event(mem_ev_event),
            .ev_arg0(mem_ev_arg0), .ev_arg1(mem_ev_arg1),
            .ev_arg2(mem_ev_arg2), .ev_arg3(mem_ev_arg3),
            .ev_flags(mem_ev_flags)
        );

    trace_sink_stub #(.FILENAME("ee_bios.trace"),    .SINK_LABEL("bios"))
        u_trace_bios (
            .clk(clk), .rst_n(rst_n),
            .ev_valid(bios_ev_valid), .ev_subsys(bios_ev_subsys), .ev_event(bios_ev_event),
            .ev_arg0(bios_ev_arg0), .ev_arg1(bios_ev_arg1),
            .ev_arg2(bios_ev_arg2), .ev_arg3(bios_ev_arg3),
            .ev_flags(bios_ev_flags)
        );

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

    int ifetch_count;
    int unmapped_count;
    int errors;

    initial begin
        ifetch_count   = 0;
        unmapped_count = 0;
        errors         = 0;
    end

    // Count IFETCHes observed on the EE trace bus and verify each response
    // matches the synthetic NOP-sled fixture: data == 0x00000000 (MIPS NOP).
    // See sim/golden/trace_compare_spec.md for why the fixture is NOPs.
    wire [31:0] obs_addr        = ee_ev_arg0[31:0];
    wire [31:0] obs_data        = ee_ev_arg1[31:0];
    wire [31:0] obs_expected    = 32'h00000000;

    always_ff @(posedge clk) begin
        if (rst_n && ee_ev_valid && ee_ev_event == trace_pkg::EV_IFETCH) begin
            ifetch_count <= ifetch_count + 1;
            if (obs_data !== obs_expected) begin
                $error("[tb_ee_fetch_stub] 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 && mem_ev_valid && mem_ev_event == trace_pkg::EV_UNMAPPED) begin
            unmapped_count <= unmapped_count + 1;
        end
    end

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

    initial begin
        rst_n      = 1'b0;
        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;

        // Phase 1: let ee_fetch run for a window of cycles.
        enable = 1'b1;
        repeat (32) @(posedge clk);
        enable = 1'b0;
        repeat (4) @(posedge clk);

        // Phase 2: drive a write to an unmapped region. Wave 1 has no
        // writable backing, so this must trace as MEM UNMAPPED with
        // flags bit 0 (write) set.
        @(negedge clk);
        tb_wr_en   = 1'b1;
        tb_wr_addr = 32'h1000_0000;
        tb_wr_data = 32'hDEAD_BEEF;
        tb_wr_be   = 4'hF;
        @(negedge clk);
        tb_wr_en   = 1'b0;
        tb_wr_addr = 32'd0;
        tb_wr_data = 32'd0;
        tb_wr_be   = 4'd0;
        repeat (4) @(posedge clk);

        // ------------------------------------------------------------------
        // Summary
        // ------------------------------------------------------------------
        $display("[tb_ee_fetch_stub] ifetch_count=%0d unmapped_count=%0d errors=%0d",
                 ifetch_count, unmapped_count, errors);

        if (ifetch_count < 20)
            $error("[tb_ee_fetch_stub] expected at least 20 IFETCHes, got %0d", ifetch_count);
        if (unmapped_count < 1)
            $error("[tb_ee_fetch_stub] expected at least 1 UNMAPPED, got %0d", unmapped_count);

        if (errors == 0 && ifetch_count >= 20 && unmapped_count >= 1)
            $display("[tb_ee_fetch_stub] PASS");
        else
            $display("[tb_ee_fetch_stub] FAIL");

        $finish;
    end

    initial begin
        #100000;
        $error("[tb_ee_fetch_stub] timeout");
        $finish;
    end

endmodule : tb_ee_fetch_stub
