// retroDE_ps2 — tb_ee_core_dsubu
//
// Ch305 — Focused TB for R5900 DSUBU (doubleword subtract unsigned).
// MIPS-III SPECIAL funct 0x2F. The 64-bit-subtract sibling of DADDU
// (Ch272). Our regfile is 32-bit, so DSUBU is modelled as SUBU on
// the low 32 bits; no overflow trap (DSUBU is unsigned).
//
// Three test cases:
//   1. Normal subtract: dsubu $t0, $a0, $a1 with $a0=8, $a1=3 → $t0=5
//   2. qbert exact encoding: dsubu $v0, $v1, $v0 (= 0x0062102F, the
//      instruction at PC 0x00110A60). $v1=10, $v0=4 → $v0 = 6.
//   3. Underflow: dsubu $t3, $a2, $a3 with $a2=0, $a3=1 → $t3 =
//      0xFFFFFFFF (low-32 wrap), no overflow trap.
//
// Strict mode ON — pre-Ch305 this would trap on SPECIAL funct 0x2F.

`timescale 1ns/1ps

module tb_ee_core_dsubu;

    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;

    // ----- program slot layout -----
    localparam int B_ORI_A0    = BOOTSTRAP_SLOT + 0;   // ori  $a0, $0, 8
    localparam int B_ORI_A1    = BOOTSTRAP_SLOT + 1;   // ori  $a1, $0, 3
    localparam int B_DSUBU_T0  = BOOTSTRAP_SLOT + 2;   // dsubu $t0, $a0, $a1
    localparam int B_ORI_T1    = BOOTSTRAP_SLOT + 3;   // ori  $t1, $0, 5
    localparam int B_BNE_1     = BOOTSTRAP_SLOT + 4;   // bne  $t0, $t1, FAIL
    localparam int B_DS_1      = BOOTSTRAP_SLOT + 5;
    // qbert's exact instruction encoding 0x0062102F
    localparam int B_ORI_V1    = BOOTSTRAP_SLOT + 6;   // ori  $v1, $0, 10
    localparam int B_ORI_V0    = BOOTSTRAP_SLOT + 7;   // ori  $v0, $0, 4
    localparam int B_DSUBU_V0  = BOOTSTRAP_SLOT + 8;   // dsubu $v0, $v1, $v0
    localparam int B_ORI_T2    = BOOTSTRAP_SLOT + 9;   // ori  $t2, $0, 6
    localparam int B_BNE_2     = BOOTSTRAP_SLOT + 10;  // bne  $v0, $t2, FAIL
    localparam int B_DS_2      = BOOTSTRAP_SLOT + 11;
    // underflow case
    localparam int B_ORI_A3    = BOOTSTRAP_SLOT + 12;  // ori  $a3, $0, 1
    localparam int B_DSUBU_T3  = BOOTSTRAP_SLOT + 13;  // dsubu $t3, $0, $a3
    localparam int B_LUI_T4    = BOOTSTRAP_SLOT + 14;  // lui  $t4, 0xFFFF
    localparam int B_ORI_T4    = BOOTSTRAP_SLOT + 15;  // ori  $t4, $t4, 0xFFFF
    localparam int B_BNE_3     = BOOTSTRAP_SLOT + 16;  // bne  $t3, $t4, FAIL
    localparam int B_DS_3      = BOOTSTRAP_SLOT + 17;
    localparam int B_PASS      = BOOTSTRAP_SLOT + 18;  // syscall PASS
    localparam int B_FAIL      = BOOTSTRAP_SLOT + 19;  // syscall FAIL

    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_SPCL  = 6'h00;
    localparam logic [5:0] FUNC_DSUBU = 6'h2F;

    // GPR indices
    localparam int RV0 = 2;    // $v0
    localparam int RV1 = 3;    // $v1
    localparam int RA0 = 4;    // $a0
    localparam int RA1 = 5;    // $a1
    localparam int RA3 = 7;    // $a3
    localparam int RT0 = 8;    // $t0
    localparam int RT1 = 9;    // $t1
    localparam int RT2 = 10;   // $t2
    localparam int RT3 = 11;   // $t3
    localparam int RT4 = 12;   // $t4

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

        // Test 1: normal subtract. $a0=8, $a1=3 → $t0=5.
        bios_word(B_ORI_A0,   enc_i(OP_ORI, 0, RA0, 16'h0008));
        bios_word(B_ORI_A1,   enc_i(OP_ORI, 0, RA1, 16'h0003));
        bios_word(B_DSUBU_T0, enc_rtype(OP_SPCL, RA0, RA1, RT0, 0, FUNC_DSUBU));
        bios_word(B_ORI_T1,   enc_i(OP_ORI, 0, RT1, 16'h0005));
        bios_word(B_BNE_1,    enc_i(OP_BNE, RT0, RT1, branch_imm(B_BNE_1, B_FAIL)));
        bios_word(B_DS_1,     32'd0);

        // Test 2: qbert exact encoding 0x0062102F. dsubu $v0, $v1, $v0.
        // $v1=10, $v0=4 → $v0 = 6. Sanity-assert the encoding.
        qbert_dsubu_raw = enc_rtype(OP_SPCL, RV1, RV0, RV0, 0, FUNC_DSUBU);
        if (qbert_dsubu_raw !== 32'h0062_102F) begin
            $error("[tb_ee_core_dsubu] enc_rtype mismatch: produced 0x%08h, expected 0x0062102F",
                   qbert_dsubu_raw);
            errors = errors + 1;
        end
        bios_word(B_ORI_V1,   enc_i(OP_ORI, 0, RV1, 16'h000A));   // $v1 = 10
        bios_word(B_ORI_V0,   enc_i(OP_ORI, 0, RV0, 16'h0004));   // $v0 = 4
        bios_word(B_DSUBU_V0, qbert_dsubu_raw);                   // $v0 = $v1 - $v0 = 6
        bios_word(B_ORI_T2,   enc_i(OP_ORI, 0, RT2, 16'h0006));
        bios_word(B_BNE_2,    enc_i(OP_BNE, RV0, RT2, branch_imm(B_BNE_2, B_FAIL)));
        bios_word(B_DS_2,     32'd0);

        // Test 3: underflow. $a3=1; $t3 = $0 - $a3 = 0xFFFFFFFF.
        // No overflow trap (DSUBU unsigned).
        bios_word(B_ORI_A3,   enc_i(OP_ORI, 0, RA3, 16'h0001));
        bios_word(B_DSUBU_T3, enc_rtype(OP_SPCL, 0, RA3, RT3, 0, FUNC_DSUBU));
        bios_word(B_LUI_T4,   enc_i(OP_LUI, 0, RT4, 16'hFFFF));
        bios_word(B_ORI_T4,   enc_i(OP_ORI, RT4, RT4, 16'hFFFF));
        bios_word(B_BNE_3,    enc_i(OP_BNE, RT3, RT4, branch_imm(B_BNE_3, B_FAIL)));
        bios_word(B_DS_3,     32'd0);

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

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

        if (u_core.regfile[RT0] !== 32'd5) begin
            $error("[tb_ee_core_dsubu] $t0 = 0x%08h (expected 0x00000005)",
                   u_core.regfile[RT0]);
            errors = errors + 1;
        end
        if (u_core.regfile[RV0] !== 32'd6) begin
            $error("[tb_ee_core_dsubu] $v0 = 0x%08h (expected 0x00000006 from qbert-case)",
                   u_core.regfile[RV0]);
            errors = errors + 1;
        end
        if (u_core.regfile[RT3] !== 32'hFFFF_FFFF) begin
            $error("[tb_ee_core_dsubu] $t3 = 0x%08h (expected 0xFFFFFFFF from underflow)",
                   u_core.regfile[RT3]);
            errors = errors + 1;
        end

        $display("[tb_ee_core_dsubu] 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_dsubu]   $t0=0x%08h $v0=0x%08h $t3=0x%08h",
                 u_core.regfile[RT0], u_core.regfile[RV0], u_core.regfile[RT3]);

        if (errors == 0)
            $display("[tb_ee_core_dsubu] PASS");
        else
            $display("[tb_ee_core_dsubu] FAIL");

        $finish;
    end

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

endmodule : tb_ee_core_dsubu
