// retroDE_ps2 — tb_ee_core_ld
//
// Ch284 — Focused TB for MIPS-III LD (Load Doubleword), opcode 0x37.
// The read-side of SD (Ch275). Reuses sq_beat as a 2-beat counter
// (terminal beat 1). Beat 0 captures mem[ea+0] into gpr128[rt][31:0]
// and mirrors to regfile[rt]; beat 1 captures mem[ea+4] into
// gpr128[rt][63:32]. gpr128[rt][127:64] is preserved (LD only loads
// doubleword; upper 64 of $rt are architecturally unchanged on real
// R5900 — but in this stub they're left untouched by LD, which means
// they're whatever they were before).
//
// qbert.elf hits `ld $ra, 0($sp)` (0xDFBF0000) at PC 0x00113378 —
// the function-epilogue $ra restore from the stack.
//
// Test cases:
//   1. Exact qbert encoding asserted — `ld $ra, 0($sp)` (rs=$sp=29,
//      rt=$ra=31, op=0x37, imm=0) → 0xDFBF0000. The TB sets $sp to a
//      safe base, pre-pokes (word0=0xAABBCCDD, word1=0x11223344) at
//      ea/ea+4, executes the exact qbert instruction, and verifies
//      regfile[$ra] = word0 + gpr128[$ra][63:32] = word1.
//   2. SD → LD round-trip for $ra. SD $ra,0($v0) with $ra =
//      0xABCD1234. LD $t1, 0($v0). Verify regfile[$t1] == 0xABCD1234
//      and gpr128[$t1][63:32] == 0 (because SD beat 1 wrote 0).
//
// Strict mode ON. Pre-Ch284 this would trap on op=0x37.

`timescale 1ns/1ps

module tb_ee_core_ld;

    localparam int BIOS_BYTES    = 4 * 1024 * 1024;
    localparam int EE_RAM_BYTES  = 64 * 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 logic [31:0] TARGET_KSEG0_A = 32'h8000_0400;
    localparam int          TARGET_PHYS_A  = 32'h400;
    localparam logic [31:0] TARGET_KSEG0_B = 32'h8000_0410;
    localparam int          TARGET_PHYS_B  = 32'h410;
    localparam logic [31:0] WORD0_A        = 32'hAABB_CCDD;
    localparam logic [31:0] WORD1_A        = 32'h1122_3344;
    localparam logic [31:0] RA_SENTINEL    = 32'hABCD_1234;

    // Order: Case 2 (SD→LD round-trip with $ra as the SD source)
    // runs FIRST; Case 1 (exact qbert encoding `ld $ra, 0($sp)`)
    // runs LAST so $ra holds the Case 1 LD result at end-of-sim for
    // post-halt gpr128/regfile peeks.

    // ===== Case 2: SD→LD round-trip =====
    localparam int B_LUI_BASEB  = BOOTSTRAP_SLOT + 0;  // lui $v0, 0x8000
    localparam int B_ORI_BASEB  = BOOTSTRAP_SLOT + 1;  // ori $v0, $v0, 0x0410
    localparam int B_LUI_RA_H   = BOOTSTRAP_SLOT + 2;  // lui $ra, 0xABCD
    localparam int B_ORI_RA_L   = BOOTSTRAP_SLOT + 3;  // ori $ra, $ra, 0x1234
    localparam int B_SD_B       = BOOTSTRAP_SLOT + 4;  // sd  $ra, 0($v0)
    localparam int B_LD_B       = BOOTSTRAP_SLOT + 5;  // ld  $t2, 0($v0)
    localparam int B_LUI_EXP1   = BOOTSTRAP_SLOT + 6;  // lui $t3, 0xABCD
    localparam int B_ORI_EXP1   = BOOTSTRAP_SLOT + 7;  // ori $t3, $t3, 0x1234
    localparam int B_BNE_RT_B   = BOOTSTRAP_SLOT + 8;  // bne $t2, $t3, FAIL
    localparam int B_DS_RT_B    = BOOTSTRAP_SLOT + 9;

    // ===== Case 1: exact qbert encoding — `ld $ra, 0($sp)` =====
    localparam int B_LUI_SP     = BOOTSTRAP_SLOT + 10; // lui $sp, 0x8000
    localparam int B_ORI_SP     = BOOTSTRAP_SLOT + 11; // ori $sp, $sp, 0x0400
    localparam int B_LD_A       = BOOTSTRAP_SLOT + 12; // ld  $ra, 0($sp)
    localparam int B_LUI_EXP0   = BOOTSTRAP_SLOT + 13; // lui $t1, 0xAABB
    localparam int B_ORI_EXP0   = BOOTSTRAP_SLOT + 14; // ori $t1, $t1, 0xCCDD
    localparam int B_BNE_LO_A   = BOOTSTRAP_SLOT + 15; // bne $ra, $t1, FAIL
    localparam int B_DS_LO_A    = BOOTSTRAP_SLOT + 16;

    localparam int B_PASS       = BOOTSTRAP_SLOT + 17;
    localparam int B_FAIL       = BOOTSTRAP_SLOT + 18;

    localparam logic [5:0] OP_J     = 6'h02;
    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_LD    = 6'h37;
    localparam logic [5:0] OP_SD    = 6'h3F;

    localparam int RV0 = 2;
    localparam int RT1 = 9;
    localparam int RT2 = 10;
    localparam int RT3 = 11;
    localparam int RSP = 29;
    localparam int RA  = 31;

    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_ld_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

    task automatic ram_word(input logic [31:0] phys_addr, input logic [31:0] word);
        int           qw_idx;
        logic [1:0]   lane;
        logic [127:0] cur;
        qw_idx = phys_addr >> 4;
        lane   = phys_addr[3:2];
        cur    = u_ee_ram.mem[qw_idx];
        case (lane)
            2'd0: cur[ 31:  0] = word;
            2'd1: cur[ 63: 32] = word;
            2'd2: cur[ 95: 64] = word;
            2'd3: cur[127: 96] = word;
        endcase
        u_ee_ram.mem[qw_idx] = cur;
    endtask

    initial begin
        logic [31:0] qbert_ld_raw;

        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);

        // Encoder-output assertion for the exact qbert encoding:
        // `ld $ra, 0($sp)` → rs=$sp=29, rt=$ra=31, op=0x37, imm=0 →
        // 0xDFBF0000. The Case 1 LD below uses this exact encoding.
        qbert_ld_raw = enc_i(OP_LD, RSP, RA, 16'h0000);
        if (qbert_ld_raw !== 32'hDFBF_0000) begin
            $error("[tb_ee_core_ld] enc_i mismatch: produced 0x%08h, expected 0xDFBF0000",
                   qbert_ld_raw);
            errors = errors + 1;
        end

        // ===== Case 1: exact qbert encoding — `ld $ra, 0($sp)` =====
        bios_word(B_LUI_SP,    enc_i(OP_LUI, 0, RSP, 16'h8000));
        bios_word(B_ORI_SP,    enc_i(OP_ORI, RSP, RSP, 16'h0400));
        bios_word(B_LD_A,      qbert_ld_raw);
        bios_word(B_LUI_EXP0,  enc_i(OP_LUI, 0, RT1, 16'hAABB));
        bios_word(B_ORI_EXP0,  enc_i(OP_ORI, RT1, RT1, 16'hCCDD));
        bios_word(B_BNE_LO_A,  enc_i(OP_BNE, RA, RT1, branch_imm(B_BNE_LO_A, B_FAIL)));
        bios_word(B_DS_LO_A,   32'd0);

        // ===== Case 2: SD→LD round-trip =====
        bios_word(B_LUI_BASEB, enc_i(OP_LUI, 0, RV0, 16'h8000));
        bios_word(B_ORI_BASEB, enc_i(OP_ORI, RV0, RV0, 16'h0410));
        bios_word(B_LUI_RA_H,  enc_i(OP_LUI, 0, RA, 16'hABCD));
        bios_word(B_ORI_RA_L,  enc_i(OP_ORI, RA, RA, 16'h1234));
        bios_word(B_SD_B,      enc_i(OP_SD,  RV0, RA, 16'h0000));
        bios_word(B_LD_B,      enc_i(OP_LD,  RV0, RT2, 16'h0000));
        bios_word(B_LUI_EXP1,  enc_i(OP_LUI, 0, RT3, 16'hABCD));
        bios_word(B_ORI_EXP1,  enc_i(OP_ORI, RT3, RT3, 16'h1234));
        bios_word(B_BNE_RT_B,  enc_i(OP_BNE, RT2, RT3, branch_imm(B_BNE_RT_B, B_FAIL)));
        bios_word(B_DS_RT_B,   32'd0);

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

        // Pre-poke Case 1 RAM: (word0, word1) at phys 0x400/0x404.
        ram_word(TARGET_PHYS_A + 32'd0, WORD0_A);
        ram_word(TARGET_PHYS_A + 32'd4, WORD1_A);

        // Pre-poke Case 2 RAM with junk so the SD has something to
        // overwrite.
        ram_word(TARGET_PHYS_B + 32'd0, 32'hDEAD_BEEF);
        ram_word(TARGET_PHYS_B + 32'd4, 32'hCAFE_F00D);

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

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

        if (core_trap_events != 0) begin
            $error("[tb_ee_core_ld] unexpected trap: %0d 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_ld] 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_ld] 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: regfile mirror and gpr128 upper-32 of low-D both
        // populated. $ra holds the Case 1 LD result since Case 1
        // runs last in the bootstrap.
        if (u_core.regfile[RA] !== WORD0_A) begin
            $error("[tb_ee_core_ld] CASE 1 regfile[$ra] = 0x%08h (expected 0x%08h)",
                   u_core.regfile[RA], WORD0_A);
            errors = errors + 1;
        end
        if (u_core.gpr128[RA][31:0] !== WORD0_A) begin
            $error("[tb_ee_core_ld] CASE 1 gpr128[$ra][31:0] = 0x%08h (expected 0x%08h)",
                   u_core.gpr128[RA][31:0], WORD0_A);
            errors = errors + 1;
        end
        if (u_core.gpr128[RA][63:32] !== WORD1_A) begin
            $error("[tb_ee_core_ld] CASE 1 gpr128[$ra][63:32] = 0x%08h (expected 0x%08h)",
                   u_core.gpr128[RA][63:32], WORD1_A);
            errors = errors + 1;
        end

        // Case 2: SD stored {0, $ra} into the qword. LD reads it
        // back; low 32 = $ra sentinel, gpr128[63:32] = 0.
        if (u_core.regfile[RT2] !== RA_SENTINEL) begin
            $error("[tb_ee_core_ld] CASE 2 regfile[$t2] = 0x%08h (expected 0x%08h)",
                   u_core.regfile[RT2], RA_SENTINEL);
            errors = errors + 1;
        end
        if (u_core.gpr128[RT2][63:32] !== 32'd0) begin
            $error("[tb_ee_core_ld] CASE 2 gpr128[$t2][63:32] = 0x%08h (expected 0)",
                   u_core.gpr128[RT2][63:32]);
            errors = errors + 1;
        end

        $display("[tb_ee_core_ld] 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_ld]   CASE1 $ra=0x%08h gpr128[$ra][63:32]=0x%08h",
                 u_core.regfile[RA], u_core.gpr128[RA][63:32]);
        $display("[tb_ee_core_ld]   CASE2 $t2=0x%08h gpr128[$t2][63:32]=0x%08h",
                 u_core.regfile[RT2], u_core.gpr128[RT2][63:32]);

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

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

endmodule : tb_ee_core_ld
