// retroDE_ps2 — tb_ee_core_sh
//
// Twelfth evidence-driven opcode growth: SH (store halfword, opcode
// 0x29). Real-BIOS trapped on SH at 0xBFC5_28B4 (`sh $0, 0x186($2)`)
// right after the SLL/SRL/SRA batch unlocked 12k instructions. SH
// is the store-side mate to LH/LHU — same halfword lane selection
// via ea[1], symmetric to the existing SB byte-store path.
//
// Scope per Codex: SH only, two explicit lane probes (low + high
// halfword), one sign-ambiguous value to make byte-enable bugs
// obvious. Misalignment deferred.
//
// The critical test is **preservation of the other halfword**.
// Probes use rt values with distinctive upper halves — if the core
// accidentally writes all 32 bits of rt instead of just [15:0], or
// if the byte-enable mask is wrong, the "other" halfword leaks
// garbage from the register and the LW readback shows it.
//
// Probes:
//   Preload RAM[0] low word = 0xDEAD_BEEF
//   $2 = 0xCAFE_FACE
//   sh $2, 0($1)       ; low halfword: 0xFACE
//     RAM[0] should now be 0xDEAD_FACE
//     (high 0xDEAD preserved, low 0xBEEF overwritten with 0xFACE)
//   lw $3, 0($1)       ; $3 should == 0xDEAD_FACE
//   $4 = 0x1234_5678
//   sh $4, 2($1)       ; high halfword: 0x5678
//     RAM[0] should now be 0x5678_FACE
//     (high 0xDEAD overwritten with 0x5678, low 0xFACE preserved
//      from the previous SH — this chains both probes)
//   lw $5, 0($1)       ; $5 should == 0x5678_FACE
//
// Three classes of bug this TB catches:
//   - Wrong byte-enable (0b1111 instead of 0b0011/0b1100): the
//     non-addressed halfword gets written too; preservation fails.
//   - Wrong lane data placement (all 32 bits of rt broadcast to
//     both halfwords): addressed halfword may match expected value
//     but the other halfword gets corrupted.
//   - Wrong lane selection (ea[1] inverted): writes go to the
//     opposite halfword; preservation fails and addressed halfword
//     doesn't get the value.

`timescale 1ns/1ps

module tb_ee_core_sh;

    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;

    // rt values with distinctive upper halves — if they leak into
    // the non-addressed halfword, the LW readback catches it.
    //   $2 = 0xCAFE_FACE → SH to low  → RAM low word lo16 = 0xFACE
    //                      (upper 0xCAFE must NOT reach RAM)
    //   $4 = 0x1234_5678 → SH to high → RAM low word hi16 = 0x5678
    //                      (upper 0x1234 must NOT reach RAM)
    localparam logic [31:0] RT_LO_VAL = 32'hCAFE_FACE;
    localparam logic [31:0] RT_HI_VAL = 32'h1234_5678;

    // After probe 1 (SH $2 to offset 0), expect RAM[0] low word =
    //   preloaded high (0xDEAD) | written low (0xFACE) = 0xDEAD_FACE
    localparam logic [31:0] EXPECT_AFTER_1 = 32'hDEAD_FACE;

    // After probe 2 (SH $4 to offset 2), expect RAM[0] low word =
    //   written high (0x5678) | preserved low (0xFACE) = 0x5678_FACE
    localparam logic [31:0] EXPECT_AFTER_2 = 32'h5678_FACE;

    localparam int B_ORI_1    = BOOTSTRAP_SLOT + 0;   // $1 = 0 (base)
    localparam int B_LUI_2    = BOOTSTRAP_SLOT + 1;
    localparam int B_ORI_2    = BOOTSTRAP_SLOT + 2;   // $2 = RT_LO_VAL
    localparam int B_SH_LO    = BOOTSTRAP_SLOT + 3;   // sh $2, 0($1)
    localparam int B_LW_1     = BOOTSTRAP_SLOT + 4;   // $3 = lw 0($1)
    localparam int B_LUI_E1   = BOOTSTRAP_SLOT + 5;
    localparam int B_ORI_E1   = BOOTSTRAP_SLOT + 6;   // $6 = EXPECT_AFTER_1
    localparam int B_BNE_1    = BOOTSTRAP_SLOT + 7;   // $3 != $6 → FAIL
    localparam int B_DS_1     = BOOTSTRAP_SLOT + 8;

    localparam int B_LUI_4    = BOOTSTRAP_SLOT + 9;
    localparam int B_ORI_4    = BOOTSTRAP_SLOT + 10;  // $4 = RT_HI_VAL
    localparam int B_SH_HI    = BOOTSTRAP_SLOT + 11;  // sh $4, 2($1)
    localparam int B_LW_2     = BOOTSTRAP_SLOT + 12;  // $5 = lw 0($1)
    localparam int B_LUI_E2   = BOOTSTRAP_SLOT + 13;
    localparam int B_ORI_E2   = BOOTSTRAP_SLOT + 14;  // $7 = EXPECT_AFTER_2
    localparam int B_BNE_2    = BOOTSTRAP_SLOT + 15;  // $5 != $7 → FAIL
    localparam int B_DS_2     = 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_LW  = 6'h23;
    localparam logic [5:0] OP_SH  = 6'h29;

    logic clk;
    logic rst_n;

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

    logic        core_go;
    logic        core_halt;
    logic [31:0] core_pc;
    logic        core_trap;
    logic [31:0] core_trap_pc;
    logic [31:0] core_trap_instr;

    logic        ee_rd_en;
    logic [31:0] ee_rd_addr;
    logic [31:0] ee_rd_data;
    logic        ee_rd_valid;

    logic        ee_wr_en;
    logic [31:0] ee_wr_addr;
    logic [31:0] ee_wr_data;
    logic [3:0]  ee_wr_be;

    logic        ee_cpu_irq;
    assign ee_cpu_irq = 1'b0;

    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(ee_cpu_irq),
        .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;
    logic [21:0]  bios_rd_addr;
    logic [31:0]  bios_rd_data;
    logic         bios_rd_valid;

    logic         ee_ram_rd_en;
    logic [24:0]  ee_ram_rd_addr;
    logic [127:0] ee_ram_rd_data;
    logic         ee_ram_rd_valid;
    logic         ee_ram_wr_en;
    logic [24:0]  ee_ram_wr_addr;
    logic [127:0] ee_ram_wr_data;
    logic [15:0]  ee_ram_wr_be;
    logic [7:0]   ee_ram_master_id;

    logic         tb_br_wr_en;
    logic [31:0]  tb_br_wr_addr;
    logic [127:0] tb_br_wr_data;
    logic [15:0]  tb_br_wr_be;

    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(tb_br_wr_en), .bridge_wr_addr(tb_br_wr_addr),
        .bridge_wr_data(tb_br_wr_data), .bridge_wr_be(tb_br_wr_be),
        .bridge_master_id(8'd9),
        .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_sh_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;
    int core_halt_events;
    int core_trap_events;
    int 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 bios_j(input int slot, input logic [31:0] tgt);
        bios_word(slot, enc_j(tgt));
    endtask
    task automatic bios_nop(input int slot);
        bios_word(slot, 32'd0);
    endtask
    task automatic bios_lui(input int slot, input int rt,
                            input logic [15:0] imm);
        bios_word(slot, enc_i(OP_LUI, 0, rt, imm));
    endtask
    task automatic bios_ori(input int slot, input int rt, input int rs,
                            input logic [15:0] imm);
        bios_word(slot, enc_i(OP_ORI, rs, rt, imm));
    endtask
    task automatic bios_lw(input int slot, input int rt, input int rs,
                           input logic [15:0] offs);
        bios_word(slot, enc_i(OP_LW, rs, rt, offs));
    endtask
    task automatic bios_sh(input int slot, input int rt, input int rs,
                           input logic [15:0] offs);
        bios_word(slot, enc_i(OP_SH, rs, rt, offs));
    endtask
    task automatic bios_bne(input int slot, input int rs, input int rt,
                            input int target_slot);
        bios_word(slot, enc_i(OP_BNE, rs, rt,
                              branch_imm(slot, target_slot)));
    endtask
    task automatic bios_syscall(input int slot);
        bios_word(slot, 32'h0000_000C);
    endtask

    task automatic bridge_qword(input logic [31:0] addr,
                                input logic [127:0] data);
        @(negedge clk);
        tb_br_wr_en   = 1'b1;
        tb_br_wr_addr = addr;
        tb_br_wr_data = data;
        tb_br_wr_be   = 16'hFFFF;
        @(negedge clk);
        tb_br_wr_en   = 1'b0;
        tb_br_wr_addr = 32'd0;
        tb_br_wr_data = 128'd0;
        tb_br_wr_be   = 16'd0;
    endtask

    initial begin
        rst_n   = 1'b0;
        core_go = 1'b0;
        tb_br_wr_en   = 1'b0;
        tb_br_wr_addr = 32'd0;
        tb_br_wr_data = 128'd0;
        tb_br_wr_be   = 16'd0;

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

        // Preload RAM[0] low word = 0xDEAD_BEEF. The non-addressed
        // halves of this word are the "canary" — if either SH
        // corrupts the wrong halfword, the LW will see preserved-
        // halfword != expected.
        bridge_qword(32'h0000_0000,
                     128'h0000_0000_0000_0000_0000_0000_DEAD_BEEF);

        bios_j  (RESET_SLOT,     BOOTSTRAP_VA);
        bios_nop(RESET_SLOT + 1);

        // Setup
        // Ch33: kseg0 base instead of useg for bridge-preloaded RAM.
        bios_lui (B_ORI_1,  1,    16'h8000);                // $1 = 0x80000000
        bios_lui (B_LUI_2,  2,    RT_LO_VAL[31:16]);
        bios_ori (B_ORI_2,  2, 2, RT_LO_VAL[15:0]);         // $2 = 0xCAFE_FACE

        // Probe 1: SH low halfword
        bios_sh  (B_SH_LO,  2, 1, 16'h0000);                // RAM[0] lo16 = 0xFACE
        bios_lw  (B_LW_1,   3, 1, 16'h0000);                // $3 = RAM[0] word
        bios_lui (B_LUI_E1, 6,    EXPECT_AFTER_1[31:16]);
        bios_ori (B_ORI_E1, 6, 6, EXPECT_AFTER_1[15:0]);    // $6 = 0xDEAD_FACE
        bios_bne (B_BNE_1,  3, 6, B_FAIL);                  // preserve + write check
        bios_nop (B_DS_1);

        // Probe 2: SH high halfword, chain onto the mutated RAM
        bios_lui (B_LUI_4,  4,    RT_HI_VAL[31:16]);
        bios_ori (B_ORI_4,  4, 4, RT_HI_VAL[15:0]);         // $4 = 0x1234_5678
        bios_sh  (B_SH_HI,  4, 1, 16'h0002);                // RAM[0] hi16 = 0x5678
        bios_lw  (B_LW_2,   5, 1, 16'h0000);                // $5 = RAM[0] word
        bios_lui (B_LUI_E2, 7,    EXPECT_AFTER_2[31:16]);
        bios_ori (B_ORI_E2, 7, 7, EXPECT_AFTER_2[15:0]);    // $7 = 0x5678_FACE
        bios_bne (B_BNE_2,  5, 7, B_FAIL);
        bios_nop (B_DS_2);

        bios_syscall(B_PASS);
        bios_syscall(B_FAIL);

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

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

        if (core_halt_events != 1) begin
            $error("[tb_ee_core_sh] expected 1 halt, got %0d",
                   core_halt_events);
            errors = errors + 1;
        end
        if (core_trap_events != 0) begin
            $error("[tb_ee_core_sh] unexpected strict trap: %0d",
                   core_trap_events);
            errors = errors + 1;
        end
        if (core_pc !== BOOTSTRAP_VA + ((B_PASS - BOOTSTRAP_SLOT) << 2)) begin
            $error("[tb_ee_core_sh] final pc mismatch: got 0x%08h expected PASS 0x%08h",
                   core_pc,
                   BOOTSTRAP_VA + ((B_PASS - BOOTSTRAP_SLOT) << 2));
            errors = errors + 1;
        end
        // Direct RAM check: after both SHs the low word should be
        // 0x5678_FACE. Other qword lanes should still be 0 (preloaded).
        if (u_ee_ram.mem[0][31:0] !== EXPECT_AFTER_2) begin
            $error("[tb_ee_core_sh] RAM[0][31:0] = 0x%08h; expected 0x%08h",
                   u_ee_ram.mem[0][31:0], EXPECT_AFTER_2);
            errors = errors + 1;
        end

        $display("[tb_ee_core_sh] retired=%0d halt=%0d trap=%0d pc=0x%08h ram_word=0x%08h errors=%0d",
                 core_retired_events, core_halt_events,
                 core_trap_events, core_pc,
                 u_ee_ram.mem[0][31:0], errors);

        if (errors == 0 &&
            core_halt_events == 1 &&
            core_trap_events == 0 &&
            core_pc == BOOTSTRAP_VA + ((B_PASS - BOOTSTRAP_SLOT) << 2) &&
            u_ee_ram.mem[0][31:0] == EXPECT_AFTER_2)
            $display("[tb_ee_core_sh] PASS");
        else
            $display("[tb_ee_core_sh] FAIL");

        $finish;
    end

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

endmodule : tb_ee_core_sh
