// retroDE_ps2 — tb_ee_core_sync
//
// Ch292 — Focused TB for MIPS SYNC (SPECIAL funct 0x0F). The
// canonical encoding is 0x0000000F (all fields zero, just opcode 0
// + funct 0x0F). Architecturally a memory-ordering barrier; in
// this in-order stub model with no cache coherency, store buffer,
// or out-of-order memory, SYNC has no visible side effect.
//
// qbert.elf hits 0x0000000F at PC 0x00112994 — the post-handler-
// registration memory barrier after the Ch290/Ch291 paired
// Add+Enable Dmac handler calls.
//
// Test sequence (mirrors Ch286's tb_ee_core_ei structure):
//   1. LUI/ORI sentinels into $v0, $t0  (SENTINEL_A, SENTINEL_B)
//   2. Execute SYNC (0x0000000F). Must retire, PC += 4, no GPR /
//      HI / LO / memory side effects.
//   3. LUI/ORI sentinel into $t1 (SENTINEL_C). Reaching this
//      proves SYNC did not halt.
//   4. Execute the neighboring SPECIAL funct 0x0E (instr
//      0x0000000E) — currently unallocated; MUST trap under strict
//      mode. Confirms we didn't widen the decode beyond exact SYNC.
//   5. Post-halt: sentinels intact, saw_trap=1, trap_pc/instr at
//      the 0x0E slot.
//
// Strict mode ON.

`timescale 1ns/1ps

module tb_ee_core_sync;

    localparam int BIOS_BYTES    = 4 * 1024 * 1024;
    localparam int EE_RAM_BYTES  = 4 * 1024;
    localparam int EE_RAM_ADDR_W = $clog2(EE_RAM_BYTES);

    localparam logic [31:0] RESET_VECTOR   = 32'hBFC0_0000;
    localparam logic [31:0] BOOTSTRAP_VA   = 32'hBFC0_0100;
    localparam int          RESET_SLOT     = 0;
    localparam int          BOOTSTRAP_SLOT = 32'h100 >> 2;

    localparam int B_LUI_V0    = BOOTSTRAP_SLOT + 0;
    localparam int B_ORI_V0    = BOOTSTRAP_SLOT + 1;
    localparam int B_LUI_T0    = BOOTSTRAP_SLOT + 2;
    localparam int B_ORI_T0    = BOOTSTRAP_SLOT + 3;
    localparam int B_SYNC      = BOOTSTRAP_SLOT + 4;  // 0x0000000F
    localparam int B_LUI_T1    = BOOTSTRAP_SLOT + 5;
    localparam int B_ORI_T1    = BOOTSTRAP_SLOT + 6;
    localparam int B_NEIGHBOR  = BOOTSTRAP_SLOT + 7;  // 0x0000000E

    localparam logic [5:0] OP_J     = 6'h02;
    localparam logic [5:0] OP_ORI   = 6'h0D;
    localparam logic [5:0] OP_LUI   = 6'h0F;

    localparam int RV0 = 2;
    localparam int RT0 = 8;
    localparam int RT1 = 9;

    localparam logic [31:0] SENTINEL_A = 32'hAAAA_5555;
    localparam logic [31:0] SENTINEL_B = 32'hBBBB_3333;
    localparam logic [31:0] SENTINEL_C = 32'hCCCC_1111;

    logic clk, rst_n;
    initial clk = 1'b0;
    always #5 clk = ~clk;

    logic        core_go, core_halt, core_trap;
    logic [31:0] core_pc, core_trap_pc, core_trap_instr;
    logic        ee_rd_en, ee_rd_valid, ee_wr_en;
    logic [31:0] ee_rd_addr, ee_rd_data, ee_wr_addr, ee_wr_data;
    logic [3:0]  ee_wr_be;

    logic                core_ev_valid;
    trace_pkg::subsys_e  core_ev_subsys;
    trace_pkg::event_e   core_ev_event;
    logic [63:0]         core_ev_arg0, core_ev_arg1, core_ev_arg2, core_ev_arg3;
    logic [31:0]         core_ev_flags;

    ee_core_stub #(.STRICT_UNSUPPORTED(1'b1)) u_core (
        .clk(clk), .rst_n(rst_n), .go_i(core_go),
        .map_rd_en(ee_rd_en), .map_rd_addr(ee_rd_addr),
        .map_rd_data(ee_rd_data), .map_rd_valid(ee_rd_valid),
        .map_wr_en(ee_wr_en), .map_wr_addr(ee_wr_addr),
        .map_wr_data(ee_wr_data), .map_wr_be(ee_wr_be),
        .cpu_irq(1'b0),
        .halt_o(core_halt), .pc_o(core_pc),
        .trap_o(core_trap), .trap_pc_o(core_trap_pc),
        .trap_instr_o(core_trap_instr),
        .ev_valid(core_ev_valid), .ev_subsys(core_ev_subsys),
        .ev_event(core_ev_event),
        .ev_arg0(core_ev_arg0), .ev_arg1(core_ev_arg1),
        .ev_arg2(core_ev_arg2), .ev_arg3(core_ev_arg3),
        .ev_flags(core_ev_flags)
    );

    logic         bios_rd_en, bios_rd_valid;
    logic [21:0]  bios_rd_addr;
    logic [31:0]  bios_rd_data;
    logic         ee_ram_rd_en, ee_ram_rd_valid, ee_ram_wr_en;
    logic [24:0]  ee_ram_rd_addr, ee_ram_wr_addr;
    logic [127:0] ee_ram_rd_data, ee_ram_wr_data;
    logic [15:0]  ee_ram_wr_be;
    logic [7:0]   ee_ram_master_id;

    ee_memory_map_stub u_ee_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(ee_wr_en), .ee_wr_addr(ee_wr_addr),
        .ee_wr_data(ee_wr_data), .ee_wr_be(ee_wr_be),
        .dmac_rd_en(1'b0), .dmac_rd_addr(32'd0),
        .dmac_rd_data(), .dmac_rd_valid(),
        .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),
        .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(ee_ram_rd_en), .ram_rd_addr(ee_ram_rd_addr),
        .ram_rd_data(ee_ram_rd_data), .ram_rd_valid(ee_ram_rd_valid),
        .ram_wr_en(ee_ram_wr_en), .ram_wr_addr(ee_ram_wr_addr),
        .ram_wr_data(ee_ram_wr_data), .ram_wr_be(ee_ram_wr_be),
        .ram_master_id(ee_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(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    ee_ram_stub #(.SIZE_BYTES(EE_RAM_BYTES)) u_ee_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(ee_ram_rd_en), .rd_addr(ee_ram_rd_addr[EE_RAM_ADDR_W-1:0]),
        .rd_data(ee_ram_rd_data), .rd_valid(ee_ram_rd_valid),
        .wr_en(ee_ram_wr_en), .wr_addr(ee_ram_wr_addr[EE_RAM_ADDR_W-1:0]),
        .wr_data(ee_ram_wr_data), .wr_be(ee_ram_wr_be),
        .master_id(ee_ram_master_id),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    bios_rom_stub #(.SIZE_BYTES(BIOS_BYTES)) 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(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    trace_sink_stub #(.FILENAME("ee_core_sync_core.trace"), .SINK_LABEL("ee_core"))
        u_trace_core (.clk(clk), .rst_n(rst_n),
            .ev_valid(core_ev_valid), .ev_subsys(core_ev_subsys),
            .ev_event(core_ev_event), .ev_arg0(core_ev_arg0),
            .ev_arg1(core_ev_arg1), .ev_arg2(core_ev_arg2),
            .ev_arg3(core_ev_arg3), .ev_flags(core_ev_flags));

    int core_retired_events, core_halt_events, core_trap_events, errors;
    // Snapshot $v0/$t0 the cycle the SYNC retires (retired_pc ==
    // SYNC slot's PC). Confirms (a) SYNC retired at all and (b) no
    // GPR changed at that exact moment.
    logic [31:0] v0_at_sync_retire, t0_at_sync_retire;
    logic        seen_sync_retire;
    // Snapshot HI/LO across the SYNC retire to prove no HI/LO side
    // effect either. HI/LO start at 0; SYNC must leave them 0.
    logic [31:0] hi_at_sync_retire, lo_at_sync_retire;
    initial begin
        core_retired_events = 0; core_halt_events = 0;
        core_trap_events = 0; errors = 0;
        v0_at_sync_retire = 32'hDEAD_BEEF;
        t0_at_sync_retire = 32'hDEAD_BEEF;
        hi_at_sync_retire = 32'hDEAD_BEEF;
        lo_at_sync_retire = 32'hDEAD_BEEF;
        seen_sync_retire  = 1'b0;
    end
    always_ff @(posedge clk) begin
        if (rst_n && core_ev_valid) begin
            core_retired_events <= core_retired_events + 1;
            if ((core_ev_flags & 32'h08) != 32'd0) core_halt_events <= core_halt_events + 1;
            if ((core_ev_flags & 32'h80) != 32'd0) core_trap_events <= core_trap_events + 1;
            if (u_core.retired_pc == BOOTSTRAP_VA + ((B_SYNC - BOOTSTRAP_SLOT) << 2)
                    && !seen_sync_retire) begin
                v0_at_sync_retire <= u_core.regfile[RV0];
                t0_at_sync_retire <= u_core.regfile[RT0];
                hi_at_sync_retire <= u_core.hi_reg;
                lo_at_sync_retire <= u_core.lo_reg;
                seen_sync_retire  <= 1'b1;
            end
        end
    end

    task automatic bios_word(input int slot, input logic [31:0] word);
        u_bios.mem[slot] = word;
    endtask
    function automatic logic [31:0] enc_i(input logic [5:0] op,
                                          input int rs, input int rt,
                                          input logic [15:0] imm);
        return {op, rs[4:0], rt[4:0], imm};
    endfunction
    function automatic logic [31:0] enc_j(input logic [31:0] target_addr);
        return {OP_J, target_addr[27:2]};
    endfunction

    initial begin
        rst_n = 1'b0; core_go = 1'b0;
        repeat (4) @(posedge clk);
        rst_n = 1'b1;
        repeat (2) @(posedge clk);

        bios_word(RESET_SLOT,     enc_j(BOOTSTRAP_VA));
        bios_word(RESET_SLOT + 1, 32'd0);

        // $v0 = SENTINEL_A
        bios_word(B_LUI_V0, enc_i(OP_LUI, 0, RV0, SENTINEL_A[31:16]));
        bios_word(B_ORI_V0, enc_i(OP_ORI, RV0, RV0, SENTINEL_A[15:0]));
        // $t0 = SENTINEL_B
        bios_word(B_LUI_T0, enc_i(OP_LUI, 0, RT0, SENTINEL_B[31:16]));
        bios_word(B_ORI_T0, enc_i(OP_ORI, RT0, RT0, SENTINEL_B[15:0]));
        // SYNC — must retire, no side effects, PC += 4.
        bios_word(B_SYNC,   32'h0000_000F);
        // $t1 = SENTINEL_C — reaching this proves we got past SYNC.
        bios_word(B_LUI_T1, enc_i(OP_LUI, 0, RT1, SENTINEL_C[31:16]));
        bios_word(B_ORI_T1, enc_i(OP_ORI, RT1, RT1, SENTINEL_C[15:0]));
        // Neighbor SPECIAL funct 0x0E — not allocated; must trap.
        bios_word(B_NEIGHBOR, 32'h0000_000E);

        @(negedge clk); core_go = 1'b1;
        @(negedge clk); core_go = 1'b0;

        while (!core_halt) @(posedge clk);
        repeat (4) @(posedge clk);

        // SYNC must have retired.
        if (!seen_sync_retire) begin
            $error("[tb_ee_core_sync] SYNC never retired — core trapped on SYNC itself?");
            errors = errors + 1;
        end
        // At the SYNC retire, $v0/$t0 still hold their sentinels and
        // HI/LO are still 0 — proves SYNC was side-effect-free at
        // that moment.
        if (seen_sync_retire && v0_at_sync_retire !== SENTINEL_A) begin
            $error("[tb_ee_core_sync] $v0 at SYNC retire = 0x%08h (expected 0x%08h)",
                   v0_at_sync_retire, SENTINEL_A);
            errors = errors + 1;
        end
        if (seen_sync_retire && t0_at_sync_retire !== SENTINEL_B) begin
            $error("[tb_ee_core_sync] $t0 at SYNC retire = 0x%08h (expected 0x%08h)",
                   t0_at_sync_retire, SENTINEL_B);
            errors = errors + 1;
        end
        if (seen_sync_retire && hi_at_sync_retire !== 32'd0) begin
            $error("[tb_ee_core_sync] HI at SYNC retire = 0x%08h (expected 0)",
                   hi_at_sync_retire);
            errors = errors + 1;
        end
        if (seen_sync_retire && lo_at_sync_retire !== 32'd0) begin
            $error("[tb_ee_core_sync] LO at SYNC retire = 0x%08h (expected 0)",
                   lo_at_sync_retire);
            errors = errors + 1;
        end

        // Neighbor 0x0E must trap.
        if (core_trap_events != 1) begin
            $error("[tb_ee_core_sync] expected 1 trap (on neighbor 0x0E), got %0d",
                   core_trap_events);
            errors = errors + 1;
        end
        if (core_trap_pc !== BOOTSTRAP_VA + ((B_NEIGHBOR - BOOTSTRAP_SLOT) << 2)) begin
            $error("[tb_ee_core_sync] trap_pc = 0x%08h (expected neighbor slot 0x%08h)",
                   core_trap_pc, BOOTSTRAP_VA + ((B_NEIGHBOR - BOOTSTRAP_SLOT) << 2));
            errors = errors + 1;
        end
        if (core_trap_instr !== 32'h0000_000E) begin
            $error("[tb_ee_core_sync] trap_instr = 0x%08h (expected 0x0000000E)",
                   core_trap_instr);
            errors = errors + 1;
        end

        // End-of-sim: $t1 = SENTINEL_C proves the post-SYNC LUI+ORI
        // ran. $v0/$t0 still hold sentinels.
        if (u_core.regfile[RT1] !== SENTINEL_C) begin
            $error("[tb_ee_core_sync] $t1 = 0x%08h (expected 0x%08h — post-SYNC LUI+ORI didn't run, so SYNC must have halted)",
                   u_core.regfile[RT1], SENTINEL_C);
            errors = errors + 1;
        end
        if (u_core.regfile[RV0] !== SENTINEL_A) begin
            $error("[tb_ee_core_sync] $v0 (end) = 0x%08h (expected 0x%08h)",
                   u_core.regfile[RV0], SENTINEL_A);
            errors = errors + 1;
        end
        if (u_core.regfile[RT0] !== SENTINEL_B) begin
            $error("[tb_ee_core_sync] $t0 (end) = 0x%08h (expected 0x%08h)",
                   u_core.regfile[RT0], SENTINEL_B);
            errors = errors + 1;
        end

        $display("[tb_ee_core_sync] retired=%0d halt=%0d trap=%0d pc=0x%08h errors=%0d",
                 core_retired_events, core_halt_events,
                 core_trap_events, core_pc, errors);
        $display("[tb_ee_core_sync]   seen_sync_retire=%0b v0=0x%08h t0=0x%08h hi=0x%08h lo=0x%08h",
                 seen_sync_retire, v0_at_sync_retire, t0_at_sync_retire,
                 hi_at_sync_retire, lo_at_sync_retire);
        $display("[tb_ee_core_sync]   trap_pc=0x%08h trap_instr=0x%08h",
                 core_trap_pc, core_trap_instr);
        $display("[tb_ee_core_sync]   $v0=0x%08h $t0=0x%08h $t1=0x%08h",
                 u_core.regfile[RV0], u_core.regfile[RT0], u_core.regfile[RT1]);

        if (errors == 0)
            $display("[tb_ee_core_sync] PASS");
        else
            $display("[tb_ee_core_sync] FAIL");
        $finish;
    end

    initial begin
        #2000000;
        $error("[tb_ee_core_sync] timeout");
        $finish;
    end

endmodule : tb_ee_core_sync
