// retroDE_ps2 — tb_ee_core_pcpyh
//
// Ch300 — Focused TB for R5900 EE MMI3/PCPYH (Parallel Copy
// Halfword), opcode 0x1C, funct 0x29 (MMI3), sa 0x1B. Per Codex
// (confirmed against PCSX2/DobieStation references):
//
//   pcpyh $rd, $rt        ($rs ignored)
//   Let h0 = $rt[15:0],  h4 = $rt[79:64]
//   $rd low 64  = {h0, h0, h0, h0}
//   $rd high 64 = {h4, h4, h4, h4}
//
// qbert.elf hits `pcpyh $v1, $t0` (instr 0x70081EE9) at PC
// 0x00110BB4 immediately after Ch299's library-gate unblock.
//
// Test cases:
//   1. Exact qbert encoding asserted == 0x70081EE9. Seed gpr128[$t0]
//      via PCPYLD so that h0=0x1234 and h4=0xABCD, then PCPYH $v1,$t0:
//        gpr128[$v1] = 0xABCD_ABCD_ABCD_ABCD_1234_1234_1234_1234
//        regfile[$v1] = 0x12341234
//   2. $rs-ignored check: PCPYH with a different (non-zero) $rs but
//      same $rt yields the SAME result. Encoded with rs=$v1 (3) for
//      contrast; result must match Case 1.
//   3. Neighbor MMI3 encoding sa=0x1C (unallocated) still traps under
//      strict mode.
//
// Strict mode ON.

`timescale 1ns/1ps

module tb_ee_core_pcpyh;

    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;

    // Case 1 — exact qbert encoding. Seed $t1 and $t2 then PCPYLD
    // into $t0; then PCPYH $v1, $t0 (rs=$0 = ignored).
    //   $t1 = 0xWWWW_ABCD   (low half → h4 after PCPYLD)
    //   $t2 = 0xYYYY_1234   (low half → h0 after PCPYLD)
    // PCPYLD $t0, $t1, $t2: gpr128[$t0][127:64] = {32'd0, $t1};
    //                       gpr128[$t0][63:0]   = {32'd0, $t2}.
    // → bits 79:64 = 0xABCD = h4; bits 15:0 = 0x1234 = h0.
    localparam int B_LUI_T1     = BOOTSTRAP_SLOT + 0;
    localparam int B_ORI_T1     = BOOTSTRAP_SLOT + 1;
    localparam int B_LUI_T2     = BOOTSTRAP_SLOT + 2;
    localparam int B_ORI_T2     = BOOTSTRAP_SLOT + 3;
    localparam int B_PCPYLD_A   = BOOTSTRAP_SLOT + 4;
    localparam int B_PCPYH_A    = BOOTSTRAP_SLOT + 5;   // 0x70081EE9
    localparam int B_LUI_EXPA   = BOOTSTRAP_SLOT + 6;
    localparam int B_ORI_EXPA   = BOOTSTRAP_SLOT + 7;
    localparam int B_BNE_A      = BOOTSTRAP_SLOT + 8;
    localparam int B_DS_A       = BOOTSTRAP_SLOT + 9;

    // Case 2 — same $rt input but non-zero $rs. Result must match.
    // PCPYH with rs=$v1 (3); the dispatcher must IGNORE rs.
    localparam int B_PCPYH_B    = BOOTSTRAP_SLOT + 10;
    localparam int B_BNE_B      = BOOTSTRAP_SLOT + 11;  // bne $t3, $t4, FAIL
    localparam int B_DS_B       = BOOTSTRAP_SLOT + 12;

    // Case 3 — neighbor MMI3 sa=0x1C (unallocated) must trap.
    localparam int B_NEIGHBOR   = BOOTSTRAP_SLOT + 13;

    localparam int B_PASS       = BOOTSTRAP_SLOT + 14;
    localparam int B_FAIL       = BOOTSTRAP_SLOT + 15;

    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_MMI    = 6'h1C;
    localparam logic [5:0] FUNC_MMI2 = 6'h09;
    localparam logic [5:0] FUNC_MMI3 = 6'h29;
    localparam logic [4:0] SA_PCPYLD = 5'h0E;
    localparam logic [4:0] SA_PCPYH  = 5'h1B;
    localparam logic [4:0] SA_NEIGHBOR = 5'h1C;

    localparam int RV1 = 3;
    localparam int RT0 = 8;
    localparam int RT1 = 9;
    localparam int RT2 = 10;
    localparam int RT3 = 11;
    localparam int RT4 = 12;
    localparam int RTMP = 24;

    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_pcpyh_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 [31:0] enc_rtype(input logic [5:0] op,
                                              input int rs, input int rt,
                                              input int rd, input int sa,
                                              input logic [5:0] funct);
        return {op, rs[4:0], rt[4:0], rd[4:0], sa[4:0], funct};
    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
        logic [31:0] qbert_pcpyh_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);

        // ===== Case 1 — exact qbert encoding =====
        // $t1 = 0x5555_ABCD (h4 source = low 16 = 0xABCD)
        bios_word(B_LUI_T1, enc_i(OP_LUI, 0, RT1, 16'h5555));
        bios_word(B_ORI_T1, enc_i(OP_ORI, RT1, RT1, 16'hABCD));
        // $t2 = 0xAAAA_1234 (h0 source = low 16 = 0x1234)
        bios_word(B_LUI_T2, enc_i(OP_LUI, 0, RT2, 16'hAAAA));
        bios_word(B_ORI_T2, enc_i(OP_ORI, RT2, RT2, 16'h1234));
        // PCPYLD $t0, $t1, $t2 → gpr128[$t0][127:64] = {32'd0, $t1};
        //                         gpr128[$t0][63:0]   = {32'd0, $t2}.
        // Net: bits 79:64 = 0xABCD (= h4); bits 15:0 = 0x1234 (= h0).
        bios_word(B_PCPYLD_A, enc_rtype(OP_MMI, RT1, RT2, RT0, SA_PCPYLD, FUNC_MMI2));
        // PCPYH $v1, $t0 — exact qbert encoding 0x70081EE9.
        // rs=$0 (ignored), rt=$t0=8, rd=$v1=3, sa=0x1B, funct=0x29.
        qbert_pcpyh_raw = enc_rtype(OP_MMI, 0, RT0, RV1, SA_PCPYH, FUNC_MMI3);
        if (qbert_pcpyh_raw !== 32'h7008_1EE9) begin
            $error("[tb_ee_core_pcpyh] enc_rtype mismatch: produced 0x%08h, expected 0x70081EE9",
                   qbert_pcpyh_raw);
            errors = errors + 1;
        end
        bios_word(B_PCPYH_A, qbert_pcpyh_raw);
        // Expected $v1 (low 32) = {0x1234, 0x1234} = 0x1234_1234
        bios_word(B_LUI_EXPA, enc_i(OP_LUI, 0, RTMP, 16'h1234));
        bios_word(B_ORI_EXPA, enc_i(OP_ORI, RTMP, RTMP, 16'h1234));
        bios_word(B_BNE_A,    enc_i(OP_BNE, RV1, RTMP, branch_imm(B_BNE_A, B_FAIL)));
        bios_word(B_DS_A,     32'd0);

        // ===== Case 2 — $rs ignored =====
        // PCPYH $t3, $t0 with rs=$v1 (3). Must yield same low-32
        // result as Case 1. Encoding differs in the rs field.
        bios_word(B_PCPYH_B, enc_rtype(OP_MMI, RV1, RT0, RT3, SA_PCPYH, FUNC_MMI3));
        // Compare $t3 to the same 0x12341234 sentinel.
        bios_word(B_BNE_B,   enc_i(OP_BNE, RT3, RTMP, branch_imm(B_BNE_B, B_FAIL)));
        bios_word(B_DS_B,    32'd0);

        // ===== Case 3 — neighbor MMI3 sa=0x1C should trap =====
        bios_word(B_NEIGHBOR, enc_rtype(OP_MMI, 0, RT0, RT4, SA_NEIGHBOR, FUNC_MMI3));

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

        // Case 3 SHOULD trap. Case 1 + 2 should not. Final PC should
        // be at the neighbor's trap slot.
        if (core_trap_events != 1) begin
            $error("[tb_ee_core_pcpyh] expected 1 trap (on neighbor sa=0x1C), 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_pcpyh] trap_pc = 0x%08h (expected neighbor slot 0x%08h)",
                   core_trap_pc, BOOTSTRAP_VA + ((B_NEIGHBOR - BOOTSTRAP_SLOT) << 2));
            errors = errors + 1;
        end

        // Case 1 — regfile + full-128 view of $v1.
        if (u_core.regfile[RV1] !== 32'h1234_1234) begin
            $error("[tb_ee_core_pcpyh] CASE 1 regfile[$v1] = 0x%08h (expected 0x12341234)",
                   u_core.regfile[RV1]);
            errors = errors + 1;
        end
        if (u_core.gpr128[RV1][63:0] !== 64'h1234_1234_1234_1234) begin
            $error("[tb_ee_core_pcpyh] CASE 1 gpr128[$v1][63:0] = 0x%016h (expected 0x1234123412341234)",
                   u_core.gpr128[RV1][63:0]);
            errors = errors + 1;
        end
        if (u_core.gpr128[RV1][127:64] !== 64'hABCD_ABCD_ABCD_ABCD) begin
            $error("[tb_ee_core_pcpyh] CASE 1 gpr128[$v1][127:64] = 0x%016h (expected 0xABCDABCDABCDABCD)",
                   u_core.gpr128[RV1][127:64]);
            errors = errors + 1;
        end

        // Case 2 — $rs ignored: $t3 must equal $v1's low 32.
        if (u_core.regfile[RT3] !== 32'h1234_1234) begin
            $error("[tb_ee_core_pcpyh] CASE 2 regfile[$t3] = 0x%08h (expected 0x12341234)",
                   u_core.regfile[RT3]);
            errors = errors + 1;
        end
        if (u_core.gpr128[RT3] !== u_core.gpr128[RV1]) begin
            $error("[tb_ee_core_pcpyh] CASE 2 gpr128[$t3] != gpr128[$v1] — $rs is NOT ignored");
            errors = errors + 1;
        end

        $display("[tb_ee_core_pcpyh] 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_pcpyh]   $v1=0x%08h $t3=0x%08h",
                 u_core.regfile[RV1], u_core.regfile[RT3]);
        $display("[tb_ee_core_pcpyh]   gpr128[$v1]=0x%032h",
                 u_core.gpr128[RV1]);
        $display("[tb_ee_core_pcpyh]   trap_pc=0x%08h trap_instr=0x%08h",
                 core_trap_pc, core_trap_instr);

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

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

endmodule : tb_ee_core_pcpyh
