// retroDE_ps2 — tb_ee_core_multu_mflo
//
// Chapter 203 opcode growth. Adds the unsigned-multiply HI/LO op
// MULTU (SPECIAL funct=0x19). Triggered by the Ch202 BIOS-long run
// hitting strict-trap at PC=0xBFC4F7E0 on `multu $11, $13` — the
// first MULTU the real BIOS issues after the Ch202 MMIO-ready
// unblock.
//
// Proves:
//   1. MULTU writes {HI, LO} = unsigned(rs) * unsigned(rt) as 64-bit
//      product split into 32-bit halves; LO = product[31:0],
//      HI = product[63:32].
//   2. MFLO/MFHI move the halves out to rd correctly (mirror of the
//      Ch43 DIVU/MFHI/MFLO machinery).
//   3. A zero operand produces {HI=0, LO=0}.
//   4. The rd field of the MULTU encoding is architecturally ignored
//      — even if a buggy assembler emits MULTU with rd=$N, $N stays
//      unchanged.
//   5. No STRICT_UNSUPPORTED trap for any of the encodings here.
//
// Bootstrap layout (slot indices relative to BOOTSTRAP_SLOT):
//   Test 1 — low-only product (small operands):
//     +0   ori   $8,  $0, 0x1234
//     +1   ori   $9,  $0, 0x5678
//     +2   multu $8,  $9             ; LO=0x06260060 HI=0
//     +3   mflo  $10
//     +4   mfhi  $11
//     +5   lui   $12, 0x0626
//     +6   ori   $12, $12, 0x0060    ; expected_lo
//     +7   bne   $10, $12, FAIL      ; LO check
//     +8   nop
//     +9   bne   $11, $0,  FAIL      ; HI must be 0
//     +10  nop
//
//   Test 2 — full 64-bit product (high half nonzero):
//     +11  lui   $8, 0xFFFF
//     +12  ori   $8, $8, 0xFFFF      ; $8 = 0xFFFFFFFF
//     +13  ori   $9, $0, 0x0002      ; $9 = 2
//     +14  multu $8, $9              ; product=0x1_FFFFFFFE LO=...FE HI=1
//     +15  mflo  $13
//     +16  mfhi  $14
//     +17  lui   $12, 0xFFFF
//     +18  ori   $12, $12, 0xFFFE
//     +19  bne   $13, $12, FAIL      ; LO check
//     +20  nop
//     +21  ori   $15, $0, 0x0001
//     +22  bne   $14, $15, FAIL      ; HI check
//     +23  nop
//
//   Test 3 — zero operand:
//     +24  multu $0, $9              ; product=0
//     +25  mflo  $16
//     +26  bne   $16, $0, FAIL
//     +27  nop
//     +28  mfhi  $17
//     +29  bne   $17, $0, FAIL
//     +30  nop
//
//   Test 4 — rd field architecturally ignored:
//     +31  ori   $18, $0, 0xBEEF     ; sentinel
//     +32  word  MULTU $8,$9 with rd=$18 (raw encoding)
//     +33  ori   $19, $0, 0xBEEF
//     +34  bne   $18, $19, FAIL      ; $18 must still be 0xBEEF
//     +35  nop
//
//   +36  syscall PASS
//   +37  syscall FAIL

`timescale 1ns/1ps

module tb_ee_core_multu_mflo;

    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;

    // Test 1
    localparam int B_T1_ORI8    = BOOTSTRAP_SLOT + 0;
    localparam int B_T1_ORI9    = BOOTSTRAP_SLOT + 1;
    localparam int B_T1_MULTU   = BOOTSTRAP_SLOT + 2;
    localparam int B_T1_MFLO    = BOOTSTRAP_SLOT + 3;
    localparam int B_T1_MFHI    = BOOTSTRAP_SLOT + 4;
    localparam int B_T1_EXPLO_L = BOOTSTRAP_SLOT + 5;
    localparam int B_T1_EXPLO_O = BOOTSTRAP_SLOT + 6;
    localparam int B_T1_BNE_LO  = BOOTSTRAP_SLOT + 7;
    localparam int B_T1_DS_LO   = BOOTSTRAP_SLOT + 8;
    localparam int B_T1_BNE_HI  = BOOTSTRAP_SLOT + 9;
    localparam int B_T1_DS_HI   = BOOTSTRAP_SLOT + 10;
    // Test 2
    localparam int B_T2_LUI8    = BOOTSTRAP_SLOT + 11;
    localparam int B_T2_ORI8    = BOOTSTRAP_SLOT + 12;
    localparam int B_T2_ORI9    = BOOTSTRAP_SLOT + 13;
    localparam int B_T2_MULTU   = BOOTSTRAP_SLOT + 14;
    localparam int B_T2_MFLO    = BOOTSTRAP_SLOT + 15;
    localparam int B_T2_MFHI    = BOOTSTRAP_SLOT + 16;
    localparam int B_T2_EXPLO_L = BOOTSTRAP_SLOT + 17;
    localparam int B_T2_EXPLO_O = BOOTSTRAP_SLOT + 18;
    localparam int B_T2_BNE_LO  = BOOTSTRAP_SLOT + 19;
    localparam int B_T2_DS_LO   = BOOTSTRAP_SLOT + 20;
    localparam int B_T2_EXPHI_O = BOOTSTRAP_SLOT + 21;
    localparam int B_T2_BNE_HI  = BOOTSTRAP_SLOT + 22;
    localparam int B_T2_DS_HI   = BOOTSTRAP_SLOT + 23;
    // Test 3
    localparam int B_T3_MULTU_Z = BOOTSTRAP_SLOT + 24;
    localparam int B_T3_MFLO    = BOOTSTRAP_SLOT + 25;
    localparam int B_T3_BNE_LO  = BOOTSTRAP_SLOT + 26;
    localparam int B_T3_DS_LO   = BOOTSTRAP_SLOT + 27;
    localparam int B_T3_MFHI    = BOOTSTRAP_SLOT + 28;
    localparam int B_T3_BNE_HI  = BOOTSTRAP_SLOT + 29;
    localparam int B_T3_DS_HI   = BOOTSTRAP_SLOT + 30;
    // Test 4
    localparam int B_T4_ORI18   = BOOTSTRAP_SLOT + 31;
    localparam int B_T4_MULTU_R = BOOTSTRAP_SLOT + 32;
    localparam int B_T4_ORI19   = BOOTSTRAP_SLOT + 33;
    localparam int B_T4_BNE_RD  = BOOTSTRAP_SLOT + 34;
    localparam int B_T4_DS_RD   = BOOTSTRAP_SLOT + 35;
    // Exit
    localparam int B_PASS       = BOOTSTRAP_SLOT + 36;
    localparam int B_FAIL       = BOOTSTRAP_SLOT + 37;

    localparam logic [5:0] OP_SPECIAL = 6'h00;
    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] FUNC_MFHI  = 6'h10;
    localparam logic [5:0] FUNC_MFLO  = 6'h12;
    localparam logic [5:0] FUNC_MULTU = 6'h19;

    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;

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

    function automatic logic [31:0] enc_r(input int rs, input int rt,
                                          input int rd, input int sa,
                                          input logic [5:0] func);
        return {OP_SPECIAL, rs[4:0], rt[4:0], rd[4:0], sa[4:0], func};
    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_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_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_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 bios_multu(input int slot, input int rs, input int rt);
        bios_word(slot, enc_r(rs, rt, 0, 0, FUNC_MULTU));
    endtask
    task automatic bios_multu_rd(input int slot, input int rs,
                                 input int rt, input int rd);
        // Same MULTU funct but with rd field nonzero — verifies the
        // core ignores rd architecturally.
        bios_word(slot, enc_r(rs, rt, rd, 0, FUNC_MULTU));
    endtask
    task automatic bios_mfhi(input int slot, input int rd);
        bios_word(slot, enc_r(0, 0, rd, 0, FUNC_MFHI));
    endtask
    task automatic bios_mflo(input int slot, input int rd);
        bios_word(slot, enc_r(0, 0, rd, 0, FUNC_MFLO));
    endtask

    initial begin
        rst_n   = 1'b0;
        core_go = 1'b0;

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

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

        // ----- Test 1: low-only product (0x1234 * 0x5678 = 0x06260060) -----
        bios_ori (B_T1_ORI8,    8, 0, 16'h1234);
        bios_ori (B_T1_ORI9,    9, 0, 16'h5678);
        bios_multu(B_T1_MULTU,  8, 9);
        bios_mflo(B_T1_MFLO,   10);
        bios_mfhi(B_T1_MFHI,   11);
        bios_lui (B_T1_EXPLO_L, 12, 16'h0626);
        bios_ori (B_T1_EXPLO_O, 12, 12, 16'h0060);
        bios_bne (B_T1_BNE_LO, 10, 12, B_FAIL);
        bios_nop (B_T1_DS_LO);
        bios_bne (B_T1_BNE_HI, 11, 0,  B_FAIL);
        bios_nop (B_T1_DS_HI);

        // ----- Test 2: full-width product (0xFFFFFFFF * 2 = 0x1_FFFFFFFE) -----
        bios_lui (B_T2_LUI8,    8, 16'hFFFF);
        bios_ori (B_T2_ORI8,    8, 8, 16'hFFFF);
        bios_ori (B_T2_ORI9,    9, 0, 16'h0002);
        bios_multu(B_T2_MULTU,  8, 9);
        bios_mflo(B_T2_MFLO,   13);
        bios_mfhi(B_T2_MFHI,   14);
        bios_lui (B_T2_EXPLO_L, 12, 16'hFFFF);
        bios_ori (B_T2_EXPLO_O, 12, 12, 16'hFFFE);
        bios_bne (B_T2_BNE_LO, 13, 12, B_FAIL);
        bios_nop (B_T2_DS_LO);
        bios_ori (B_T2_EXPHI_O, 15, 0, 16'h0001);
        bios_bne (B_T2_BNE_HI, 14, 15, B_FAIL);
        bios_nop (B_T2_DS_HI);

        // ----- Test 3: zero operand (multu $0, $9) -----
        bios_multu(B_T3_MULTU_Z, 0, 9);
        bios_mflo(B_T3_MFLO,    16);
        bios_bne (B_T3_BNE_LO, 16, 0, B_FAIL);
        bios_nop (B_T3_DS_LO);
        bios_mfhi(B_T3_MFHI,    17);
        bios_bne (B_T3_BNE_HI, 17, 0, B_FAIL);
        bios_nop (B_T3_DS_HI);

        // ----- Test 4: rd field ignored even if nonzero -----
        bios_ori (B_T4_ORI18,  18, 0, 16'hBEEF);
        bios_multu_rd(B_T4_MULTU_R, 8, 9, 18);
        bios_ori (B_T4_ORI19,  19, 0, 16'hBEEF);
        bios_bne (B_T4_BNE_RD, 18, 19, B_FAIL);
        bios_nop (B_T4_DS_RD);

        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_multu_mflo] expected 1 halt, got %0d",
                   core_halt_events);
            errors = errors + 1;
        end
        if (core_trap_events != 0) begin
            $error("[tb_ee_core_multu_mflo] 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_multu_mflo] final pc mismatch: got 0x%08h expected PASS 0x%08h",
                   core_pc,
                   BOOTSTRAP_VA + ((B_PASS - BOOTSTRAP_SLOT) << 2));
            errors = errors + 1;
        end

        $display("[tb_ee_core_multu_mflo] retired=%0d halt=%0d trap=%0d pc=0x%08h errors=%0d",
                 core_retired_events, core_halt_events,
                 core_trap_events, core_pc, errors);

        if (errors == 0 &&
            core_halt_events == 1 &&
            core_trap_events == 0 &&
            core_pc == BOOTSTRAP_VA + ((B_PASS - BOOTSTRAP_SLOT) << 2))
            $display("[tb_ee_core_multu_mflo] PASS");
        else
            $display("[tb_ee_core_multu_mflo] FAIL");

        $finish;
    end

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

endmodule : tb_ee_core_multu_mflo
