// retroDE_ps2 — tb_ee_core_beql
//
// Ch274 — Focused TB for MIPS-II BEQL (Branch on Equal Likely),
// opcode 0x14. qbert.elf hits this at PC 0x001000C0 in a C++
// static-constructor walker; the delay slot at PC+4 clobbers $a0,
// so the "squash on not-taken" semantics are load-bearing.
//
// Test cases:
//   1. BEQL TAKEN: $t0 == $t1 → branch to TARGET; delay slot
//      DOES execute (writes a sentinel into $t5). End state: $t5
//      = sentinel, control reached TARGET.
//   2. BEQL NOT-TAKEN (the load-bearing case): $t0 != $t1 →
//      delay slot squashed. End state: $t5 still at its
//      pre-BEQL value (NOT the sentinel), control fell through
//      to PC+8.
//   3. Cross-check: BEQ NOT-TAKEN under the same setup. BEQ's
//      delay slot DOES execute, so $t5 IS clobbered. This
//      proves BEQL differs from BEQ specifically on the
//      not-taken delay-slot behavior.
//
// Strict mode ON. Final PC = PASS slot.

`timescale 1ns/1ps

module tb_ee_core_beql;

    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;

    // ----- Slot layout -----
    // Case 1: BEQL taken.
    //   $t0 = 5, $t1 = 5, $t5 = 0xDEAD0000 (pre-BEQL)
    //   beql $t0, $t1, +5*4 (target = case1_arrived)
    //   addiu $t5, $0, 0x0011 (delay slot — should execute)
    //   ori   $t5, $t5, 0x2200 (fall-through; SHOULDN'T execute if taken)
    //   ...skipped slots...
    //   case1_arrived: ori $t6, $0, 0xCAFE
    localparam int B_ORI_T0_5      = BOOTSTRAP_SLOT + 0;   // ori $t0,$0,5
    localparam int B_ORI_T1_5      = BOOTSTRAP_SLOT + 1;   // ori $t1,$0,5
    localparam int B_LUI_T5_DEAD   = BOOTSTRAP_SLOT + 2;   // lui $t5,0xDEAD
    localparam int B_BEQL_TAKEN    = BOOTSTRAP_SLOT + 3;   // beql $t0,$t1,case1_arrived
    localparam int B_DS_TAKEN      = BOOTSTRAP_SLOT + 4;   // ori $t5,$0,0x0011 (delay slot, fires)
    localparam int B_FALL_TAKEN_A  = BOOTSTRAP_SLOT + 5;   // ori $t5,$t5,0x2200 (fall-through; should be SKIPPED)
    localparam int B_FALL_TAKEN_B  = BOOTSTRAP_SLOT + 6;   // ori $t5,$t5,0x4400 (also skipped)
    localparam int B_FALL_TAKEN_C  = BOOTSTRAP_SLOT + 7;   // ori $t5,$t5,0x8800 (also skipped)
    localparam int B_CASE1_LAND    = BOOTSTRAP_SLOT + 8;   // ori $t6,$0,0xCAFE   <-- BEQL target

    // Case 2: BEQL NOT taken. $t5 currently 0xDEAD0011 from case 1
    // delay slot. Re-init $t5 to a clean sentinel value so the
    // squash test isn't confounded.
    localparam int B_LUI_T5_BEEF   = BOOTSTRAP_SLOT + 9;   // lui $t5,0xBEEF -> $t5 = 0xBEEF0000
    localparam int B_ORI_T2_3      = BOOTSTRAP_SLOT + 10;  // ori $t2,$0,3
    localparam int B_ORI_T3_7      = BOOTSTRAP_SLOT + 11;  // ori $t3,$0,7
    localparam int B_BEQL_NOTAKEN  = BOOTSTRAP_SLOT + 12;  // beql $t2,$t3,FAIL
    localparam int B_DS_NOTAKEN    = BOOTSTRAP_SLOT + 13;  // ori $t5,$t5,0xBAD (delay — MUST NOT FIRE)
    // post-BEQL falls through here:
    localparam int B_ORI_T7_CASE2  = BOOTSTRAP_SLOT + 14;  // ori $t7,$0,0x2222 (proof we landed at PC+8)
    // Verify $t5 BEFORE Case 3 LUI overwrites it — $t5 must still be
    // 0xBEEF0000 (delay was squashed, low bits NOT OR'd with 0xBAD).
    localparam int B_LUI_T4_BEEF   = BOOTSTRAP_SLOT + 15;  // lui $t4,0xBEEF (expected $t5 value)
    localparam int B_BNE_T5_CHECK  = BOOTSTRAP_SLOT + 16;  // bne $t5,$t4,FAIL
    localparam int B_DS_T5_CHECK   = BOOTSTRAP_SLOT + 17;  // nop

    // Case 3: BEQ (not BEQL) cross-check. Same operands as Case 2;
    // BEQ's delay slot WILL execute, so $t5 SHOULD be clobbered.
    localparam int B_LUI_T5_BABE   = BOOTSTRAP_SLOT + 18;  // lui $t5,0xBABE -> $t5 = 0xBABE0000
    localparam int B_BEQ_NOTAKEN   = BOOTSTRAP_SLOT + 19;  // beq  $t2,$t3,FAIL
    localparam int B_DS_BEQ_NOTK   = BOOTSTRAP_SLOT + 20;  // ori $t5,$t5,0xCAB (delay — MUST FIRE)
    localparam int B_ORI_T8_CASE3  = BOOTSTRAP_SLOT + 21;  // ori $t8,$0,0x3333

    localparam int B_PASS          = BOOTSTRAP_SLOT + 22;  // syscall (PASS)
    localparam int B_FAIL          = BOOTSTRAP_SLOT + 23;  // syscall (FAIL)

    localparam logic [5:0] OP_J     = 6'h02;
    localparam logic [5:0] OP_BEQ   = 6'h04;
    localparam logic [5:0] OP_BNE   = 6'h05;
    localparam logic [5:0] OP_ORI   = 6'h0D;
    localparam logic [5:0] OP_LUI   = 6'h0F;
    localparam logic [5:0] OP_BEQL  = 6'h14;
    localparam logic [5:0] OP_ADDIU = 6'h09;

    // GPRs
    localparam int RT0 = 8;
    localparam int RT1 = 9;
    localparam int RT2 = 10;
    localparam int RT3 = 11;
    localparam int RT4 = 12;
    localparam int RT5 = 13;
    localparam int RT6 = 14;
    localparam int RT7 = 15;
    localparam int RT8 = 24;

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

    // ----- EE core -----
    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)
    );

    // ----- Memory map + RAM + BIOS -----
    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_beql_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;
    initial begin
        core_retired_events = 0; core_halt_events = 0;
        core_trap_events = 0; errors = 0;
    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;
        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
    function automatic logic [15:0] branch_imm(input int branch_slot,
                                               input int target_slot);
        int diff;
        diff = target_slot - branch_slot - 1;
        return diff[15:0];
    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);

        // ===== Case 1: BEQL TAKEN =====
        bios_word(B_ORI_T0_5,    enc_i(OP_ORI, 0, RT0, 16'h0005));
        bios_word(B_ORI_T1_5,    enc_i(OP_ORI, 0, RT1, 16'h0005));
        bios_word(B_LUI_T5_DEAD, enc_i(OP_LUI, 0, RT5, 16'hDEAD));   // $t5 = 0xDEAD0000
        bios_word(B_BEQL_TAKEN,  enc_i(OP_BEQL, RT0, RT1,
                                       branch_imm(B_BEQL_TAKEN, B_CASE1_LAND)));
        bios_word(B_DS_TAKEN,    enc_i(OP_ORI, RT5, RT5, 16'h0011)); // $t5 |= 0x0011 (delay fires)
        // fall-through SLOTS — should be SKIPPED because BEQL was taken.
        // If any of them executes, $t5 gets extra bits set or $t6 changes.
        bios_word(B_FALL_TAKEN_A, enc_i(OP_ORI, RT5, RT5, 16'h2200));
        bios_word(B_FALL_TAKEN_B, enc_i(OP_ORI, RT5, RT5, 16'h4400));
        bios_word(B_FALL_TAKEN_C, enc_i(OP_ORI, RT5, RT5, 16'h8800));
        bios_word(B_CASE1_LAND,   enc_i(OP_ORI, 0,   RT6, 16'hCAFE)); // landing marker

        // ===== Case 2: BEQL NOT-TAKEN (the squash test) =====
        bios_word(B_LUI_T5_BEEF, enc_i(OP_LUI, 0, RT5, 16'hBEEF));   // $t5 = 0xBEEF0000
        bios_word(B_ORI_T2_3,    enc_i(OP_ORI, 0, RT2, 16'h0003));
        bios_word(B_ORI_T3_7,    enc_i(OP_ORI, 0, RT3, 16'h0007));
        bios_word(B_BEQL_NOTAKEN, enc_i(OP_BEQL, RT2, RT3,
                                        branch_imm(B_BEQL_NOTAKEN, B_FAIL)));
        // OR-INTO ($t5,$t5,...) not OR-FROM-ZERO: if the delay fired
        // (bug), $t5 = 0xBEEF0BAD; if squashed (correct), $t5 stays
        // 0xBEEF0000.
        bios_word(B_DS_NOTAKEN,  enc_i(OP_ORI, RT5, RT5, 16'h0BAD));
        bios_word(B_ORI_T7_CASE2, enc_i(OP_ORI, 0, RT7, 16'h2222));

        // Verify $t5 == 0xBEEF0000 before Case 3 LUI overwrites it.
        bios_word(B_LUI_T4_BEEF,  enc_i(OP_LUI, 0, RT4, 16'hBEEF));
        bios_word(B_BNE_T5_CHECK, enc_i(OP_BNE, RT5, RT4,
                                        branch_imm(B_BNE_T5_CHECK, B_FAIL)));
        bios_word(B_DS_T5_CHECK,  32'd0);

        // ===== Case 3: BEQ NOT-TAKEN (cross-check) =====
        bios_word(B_LUI_T5_BABE, enc_i(OP_LUI, 0, RT5, 16'hBABE));   // $t5 = 0xBABE0000
        bios_word(B_BEQ_NOTAKEN, enc_i(OP_BEQ, RT2, RT3,
                                       branch_imm(B_BEQ_NOTAKEN, B_FAIL)));
        // OR-INTO ($t5,$t5,...): if BEQ delay fires (it should),
        // $t5 = 0xBABE0CAB.
        bios_word(B_DS_BEQ_NOTK, enc_i(OP_ORI, RT5, RT5, 16'h0CAB));
        bios_word(B_ORI_T8_CASE3, enc_i(OP_ORI, 0, RT8, 16'h3333));

        bios_word(B_PASS, 32'h0000_000C);
        bios_word(B_FAIL, 32'h0000_000C);

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

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

        // ---- Checks ----
        if (core_trap_events != 0) begin
            $error("[tb_ee_core_beql] unexpected trap: %0d trap_pc=0x%08h instr=0x%08h",
                   core_trap_events, core_trap_pc, core_trap_instr);
            errors = errors + 1;
        end
        if (core_halt_events != 1) begin
            $error("[tb_ee_core_beql] expected 1 halt, got %0d", core_halt_events);
            errors = errors + 1;
        end
        if (core_pc !== BOOTSTRAP_VA + ((B_PASS - BOOTSTRAP_SLOT) << 2)) begin
            $error("[tb_ee_core_beql] final pc mismatch: got 0x%08h expected PASS 0x%08h",
                   core_pc, BOOTSTRAP_VA + ((B_PASS - BOOTSTRAP_SLOT) << 2));
            errors = errors + 1;
        end

        // Case 1 (BEQL taken):
        //   $t6 should be 0xCAFE (we reached the target).
        //   $t5 should be 0xDEAD0011 (delay slot fired ONCE, fall-through skipped).
        // After Case 2's LUI we overwrite $t5, so we can't check Case 1's
        // final $t5 value here — but reaching $t6 = 0xCAFE proves taken.
        if (u_core.regfile[RT6] !== 32'h0000_CAFE) begin
            $error("[tb_ee_core_beql] CASE 1 (BEQL taken): $t6 = 0x%08h (expected 0xCAFE)",
                   u_core.regfile[RT6]);
            errors = errors + 1;
        end

        // Case 2 (BEQL not-taken):
        //   $t5 should be 0xBABE0CAB (BEQ delay fired in CASE 3), because
        //   we re-LUI'd $t5 to 0xBABE0000 before Case 3, then the BEQ
        //   delay-slot ORI set the low 16 bits. If BEQL's delay had fired
        //   in Case 2, $t5 would have been clobbered to 0x00000BAD before
        //   the Case 3 LUI rewrote it — but that's only verifiable via
        //   the $t7 check below.
        //   $t7 should be 0x2222 (we landed at PC+8 after BEQL squash).
        if (u_core.regfile[RT7] !== 32'h0000_2222) begin
            $error("[tb_ee_core_beql] CASE 2 (BEQL not-taken): $t7 = 0x%08h (expected 0x2222 — BEQL didn't squash + advance correctly)",
                   u_core.regfile[RT7]);
            errors = errors + 1;
        end

        // Case 3 (BEQ not-taken cross-check):
        //   $t5 should be 0xBABE0CAB (BEQ DELAY FIRED, low 16 bits ORI'd).
        //   $t8 should be 0x3333 (we landed at PC+8 after BEQ fall-through).
        if (u_core.regfile[RT5] !== 32'hBABE_0CAB) begin
            $error("[tb_ee_core_beql] CASE 3 (BEQ not-taken): $t5 = 0x%08h (expected 0xBABE0CAB — BEQ delay slot should HAVE fired)",
                   u_core.regfile[RT5]);
            errors = errors + 1;
        end
        if (u_core.regfile[RT8] !== 32'h0000_3333) begin
            $error("[tb_ee_core_beql] CASE 3 (BEQ not-taken): $t8 = 0x%08h (expected 0x3333)",
                   u_core.regfile[RT8]);
            errors = errors + 1;
        end

        $display("[tb_ee_core_beql] 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_beql]   $t5=0x%08h $t6=0x%08h $t7=0x%08h $t8=0x%08h",
                 u_core.regfile[RT5], u_core.regfile[RT6],
                 u_core.regfile[RT7], u_core.regfile[RT8]);

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

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

endmodule : tb_ee_core_beql
