// retroDE_ps2 — tb_ee_core_align_exc
//
// Chapter 49 coverage: synchronous AdES exception entry on a
// misaligned SW when the core is configured with
// TRAP_ALIGN_ERROR=0. Exercises the same fault as tb_ee_core_align
// but verifies the MIPS exception path instead of the debug halt:
//
//   1. pc vectors to EXC_VECTOR after the fault.
//   2. EPC captures the FAULTING pc (not next_pc as an interrupt
//      would use).
//   3. BadVAddr captures the offending EA.
//   4. Cause.ExcCode = 5 (AdES).
//   5. Status IE/KU stack shifted (iec → iep → ieo, iec=0; same
//      for KU).
//
// After taking the exception, we sit in a tiny handler at
// EXC_VECTOR that:
//   - Reads BadVAddr / EPC / Cause via MFC0 into $16 / $17 / $18.
//   - Performs a SYSCALL halt so the TB can check the snapshot.
//
// Layout (slots relative to BOOTSTRAP_SLOT):
//   +0    lui  $1, 0x8000                  ; $1 = kseg0 base
//   +1    lui  $3, 0xDEAD                  ; $3 witness value
//   +2    ori  $3, $3, 0xBEEF               ; $3 = 0xDEADBEEF
//   +3    sw   $3, 0x11($1)                 ; misaligned SW — AdES
//   +4    nop                                 ; delay slot (never retires)
//   +5    nop                                 ; (never reached)
//   ...
//
// Handler at EXC_VECTOR = 0x80000080:
//   +0    mfc0 $16, $8                     ; $16 = BadVAddr
//   +1    mfc0 $17, $14                    ; $17 = EPC
//   +2    mfc0 $18, $13                    ; $18 = Cause
//   +3    syscall                            ; halt for TB checks
//
// The handler lives in kseg0 at 0x80000080. Writing it there is
// done through the bridge port (tb_br_wr_*) before core_go.

`timescale 1ns/1ps

module tb_ee_core_align_exc;

    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;

    // The core's default EXC_VECTOR is 0x00000080 (useg). Override
    // to kseg0 0x80000080 so the handler lives in bridge-preloaded
    // RAM and the ifetch path reads back our installed handler
    // bytes reliably via kseg0.
    localparam logic [31:0] EXC_VECTOR_VA  = 32'h8000_0080;

    localparam int B_LUI_1   = BOOTSTRAP_SLOT + 0;
    localparam int B_LUI_3   = BOOTSTRAP_SLOT + 1;
    localparam int B_ORI_3   = BOOTSTRAP_SLOT + 2;
    localparam int B_SW_BAD  = BOOTSTRAP_SLOT + 3;
    localparam int B_DS_BAD  = BOOTSTRAP_SLOT + 4;
    localparam int B_NEVER   = BOOTSTRAP_SLOT + 5;

    localparam logic [5:0] OP_SPECIAL = 6'h00;
    localparam logic [5:0] OP_J       = 6'h02;
    localparam logic [5:0] OP_ORI     = 6'h0D;
    localparam logic [5:0] OP_LUI     = 6'h0F;
    localparam logic [5:0] OP_COP0    = 6'h10;
    localparam logic [5:0] OP_SW      = 6'h2B;
    localparam logic [4:0] COP0_RS_MF = 5'h00;

    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),
        .EXC_VECTOR        (EXC_VECTOR_VA),
        .TRAP_ALIGN_ERROR  (1'b0)  // Ch49 exception-entry mode
    ) 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_align_exc_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 core_except_events;
    int errors;

    initial begin
        core_retired_events = 0;
        core_halt_events    = 0;
        core_trap_events    = 0;
        core_except_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;
            if ((core_ev_flags & 32'h20) != 32'd0)
                core_except_events <= core_except_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_mfc0(input int rt, input int rd);
        return {OP_COP0, COP0_RS_MF, rt[4:0], rd[4:0], 11'd0};
    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_sw(input int slot, input int rt, input int rs,
                           input logic [15:0] offs);
        bios_word(slot, enc_i(OP_SW, rs, rt, offs));
    endtask

    // Preload one 32-bit word into ee_ram through the bridge.
    task automatic ram_write_word(input logic [31:0] addr,
                                  input logic [31:0] word);
        logic [127:0] data128;
        logic [15:0]  be16;
        data128 = 128'd0;
        be16    = 16'd0;
        case (addr[3:2])
            2'd0: begin data128[31:0]   = word; be16[3:0]   = 4'hF; end
            2'd1: begin data128[63:32]  = word; be16[7:4]   = 4'hF; end
            2'd2: begin data128[95:64]  = word; be16[11:8]  = 4'hF; end
            2'd3: begin data128[127:96] = word; be16[15:12] = 4'hF; end
        endcase
        @(negedge clk);
        tb_br_wr_en   = 1'b1;
        tb_br_wr_addr = addr;
        tb_br_wr_data = data128;
        tb_br_wr_be   = be16;
        @(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

    // EPC / BadVAddr / ExcCode snapshots captured via handler
    // MFC0 sequence (written post-exception into $16/$17/$18).
    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 handler at kseg0 0x80000080:
        //   0x80: mfc0 $16, $8    (BadVAddr)
        //   0x84: mfc0 $17, $14   (EPC)
        //   0x88: mfc0 $18, $13   (Cause)
        //   0x8C: syscall
        ram_write_word(32'h0000_0080, enc_mfc0(16, 8));   // BadVAddr=$8
        ram_write_word(32'h0000_0084, enc_mfc0(17, 14));  // EPC=$14
        ram_write_word(32'h0000_0088, enc_mfc0(18, 13));  // Cause=$13
        ram_write_word(32'h0000_008C, 32'h0000_000C);      // SYSCALL

        // Reset entry → bootstrap
        bios_j  (RESET_SLOT,     BOOTSTRAP_VA);
        bios_nop(RESET_SLOT + 1);

        bios_lui(B_LUI_1, 1, 16'h8000);
        bios_lui(B_LUI_3, 3, 16'hDEAD);
        bios_ori(B_ORI_3, 3, 3, 16'hBEEF);
        bios_sw (B_SW_BAD, 3, 1, 16'h0011);    // misaligned EA — AdES
        bios_nop(B_DS_BAD);
        bios_nop(B_NEVER);

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

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

        // Expectations:
        //   - no strict trap (align_trap branch disabled by parameter)
        //   - exactly 1 exception retire
        //   - exactly 1 SYSCALL halt retire (the handler's syscall)
        //   - handler ran: $16 = BadVAddr = 0x80000011 (offending EA)
        //                  $17 = EPC = bootstrap pc of the SW
        //                  $18 = Cause with ExcCode = AdES = 5
        if (core_trap_events != 0) begin
            $error("[tb_ee_core_align_exc] unexpected trap(s): %0d",
                   core_trap_events);
            errors = errors + 1;
        end
        if (core_except_events != 1) begin
            $error("[tb_ee_core_align_exc] expected 1 exception entry, got %0d",
                   core_except_events);
            errors = errors + 1;
        end
        if (core_halt_events != 1) begin
            $error("[tb_ee_core_align_exc] expected 1 handler SYSCALL halt, got %0d",
                   core_halt_events);
            errors = errors + 1;
        end
        if (u_core.regfile[16] !== 32'h8000_0011) begin
            $error("[tb_ee_core_align_exc] BadVAddr mismatch: got 0x%08h expected 0x80000011",
                   u_core.regfile[16]);
            errors = errors + 1;
        end
        if (u_core.regfile[17] !== BOOTSTRAP_VA + ((B_SW_BAD - BOOTSTRAP_SLOT) << 2)) begin
            $error("[tb_ee_core_align_exc] EPC mismatch: got 0x%08h expected 0x%08h",
                   u_core.regfile[17],
                   BOOTSTRAP_VA + ((B_SW_BAD - BOOTSTRAP_SLOT) << 2));
            errors = errors + 1;
        end
        // Cause word: ExcCode lives in bits [6:2] per MIPS layout;
        // AdES = 5'd5 so bits [6:2] = 5'd5 → cause[6:2] == 5.
        if (u_core.regfile[18][6:2] !== 5'd5) begin
            $error("[tb_ee_core_align_exc] Cause.ExcCode mismatch: got 0x%02h expected 5 (AdES)",
                   u_core.regfile[18][6:2]);
            errors = errors + 1;
        end

        $display("[tb_ee_core_align_exc] retired=%0d halt=%0d except=%0d trap=%0d BadVAddr=0x%08h EPC=0x%08h Cause=0x%08h errors=%0d",
                 core_retired_events, core_halt_events,
                 core_except_events, core_trap_events,
                 u_core.regfile[16], u_core.regfile[17],
                 u_core.regfile[18], errors);

        if (errors == 0 &&
            core_trap_events == 0 &&
            core_except_events == 1 &&
            core_halt_events == 1 &&
            u_core.regfile[16] == 32'h8000_0011 &&
            u_core.regfile[17] == BOOTSTRAP_VA + ((B_SW_BAD - BOOTSTRAP_SLOT) << 2) &&
            u_core.regfile[18][6:2] == 5'd5)
            $display("[tb_ee_core_align_exc] PASS");
        else
            $display("[tb_ee_core_align_exc] FAIL");

        $finish;
    end

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

endmodule : tb_ee_core_align_exc
