// retroDE_ps2 — tb_ee_core_memops
//
// Second EE-core chapter. The EE memory map now routes CPU-side
// 32-bit reads and writes into the 128-bit ee_ram_stub (lane-select
// on reads, lane-expand + byte-enable mask on writes). This TB runs
// a store→load round-trip through real RAM from the EE core and
// proves the lane machinery works end to end.
//
// The test is self-checking via control flow: the core does
//   SW  $3, 0($2)    ; write 0xDEAD_BEEF to RAM
//   LW  $4, 0($2)    ; read it back
//   BEQ $3, $4, pass ; branch only if the round-trip worked
// If anything in the map's write-lane expansion or read-lane
// selection is wrong, $4 != $3 and the core falls through to a FAIL
// syscall instead of the PASS syscall. The TB asserts the final pc
// parks at PASS, which is the load-bearing check.
//
// RAM layout (preloaded by TB into u_bios.mem via hierarchical
// reference):
//   0xBFC0_0000   j  0xBFC0_0100
//   0xBFC0_0004   nop
//   0xBFC0_0100   lui   $1, 0x0000
//   0xBFC0_0104   ori   $2, $0, 0x1000           ; $2 = RAM addr 0x1000
//   0xBFC0_0108   lui   $3, 0xDEAD
//   0xBFC0_010C   ori   $3, $3, 0xBEEF           ; $3 = 0xDEAD_BEEF
//   0xBFC0_0110   sw    $3, 0($2)                ; store to RAM
//   0xBFC0_0114   lw    $4, 0($2)                ; load back
//   0xBFC0_0118   beq   $3, $4, pass             ; branch if equal
//   0xBFC0_011C   nop                             ; delay slot
//   0xBFC0_0120   syscall                         ; FAIL (not reached)
//   0xBFC0_0124   syscall                         ; PASS (reached)
//
// Acceptance:
//   - halt_o asserts exactly once
//   - retire count matches a successful (BEQ-taken) trace:
//       reset J + delay NOP + 8 bootstrap ops
//       (LUI/ORI/LUI/ORI/SW/LW/BEQ/DELAY_NOP) + PASS SYSCALL = 11
//   - 2 taken branches (reset J, BEQ-to-pass), 2 delay slots (1:1)
//   - final pc parks at the PASS SYSCALL, not the FAIL one —
//     if the memory round-trip broke, pc would park at FAIL
//   - exactly 1 EE-map EV_WRITE event with region=EE_RAM (the SW)
//   - at least 2 EE-map EV_READ events with region=EE_RAM
//     (the LW plus the LW's payload return — actually just the
//     request trace; 1 is the minimum, 2+ if read pipeline noise)

`timescale 1ns/1ps

module tb_ee_core_memops;

    localparam int EE_RAM_BYTES   = 16 * 1024;
    localparam int EE_RAM_ADDR_W  = $clog2(EE_RAM_BYTES);
    localparam int BIOS_BYTES     = 4 * 1024 * 1024;

    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;

    // Bootstrap slot layout
    localparam int B_LUI_1    = BOOTSTRAP_SLOT + 0;    // lui $1 (ignored; placeholder)
    localparam int B_ORI_2    = BOOTSTRAP_SLOT + 1;    // $2 = 0x1000
    localparam int B_LUI_3    = BOOTSTRAP_SLOT + 2;    // $3 upper = 0xDEAD
    localparam int B_ORI_3    = BOOTSTRAP_SLOT + 3;    // $3 lower = 0xBEEF
    localparam int B_SW       = BOOTSTRAP_SLOT + 4;    // sw $3, 0($2)
    localparam int B_LW       = BOOTSTRAP_SLOT + 5;    // lw $4, 0($2)
    localparam int B_BEQ      = BOOTSTRAP_SLOT + 6;    // beq $3, $4, pass
    localparam int B_DSLOT    = BOOTSTRAP_SLOT + 7;    // delay NOP
    localparam int B_FAIL     = BOOTSTRAP_SLOT + 8;    // syscall FAIL
    localparam int B_PASS     = BOOTSTRAP_SLOT + 9;    // syscall PASS

    // Opcodes
    localparam logic [5:0] OP_J     = 6'h02;
    localparam logic [5:0] OP_BEQ   = 6'h04;
    localparam logic [5:0] OP_ADDIU = 6'h09;
    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_SW    = 6'h2B;

    logic clk;
    logic rst_n;

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

    // ------------------------------------------------------------------
    // EE core
    // ------------------------------------------------------------------

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

    // ------------------------------------------------------------------
    // EE memory map + EE RAM + shared BIOS ROM
    // ------------------------------------------------------------------

    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                ee_map_ev_valid;
    trace_pkg::subsys_e  ee_map_ev_subsys;
    trace_pkg::event_e   ee_map_ev_event;
    logic [63:0]         ee_map_ev_arg0, ee_map_ev_arg1, ee_map_ev_arg2, ee_map_ev_arg3;
    logic [31:0]         ee_map_ev_flags;

    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(ee_map_ev_valid), .ev_subsys(ee_map_ev_subsys),
        .ev_event(ee_map_ev_event),
        .ev_arg0(ee_map_ev_arg0), .ev_arg1(ee_map_ev_arg1),
        .ev_arg2(ee_map_ev_arg2), .ev_arg3(ee_map_ev_arg3),
        .ev_flags(ee_map_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 sinks
    // ------------------------------------------------------------------

    trace_sink_stub #(.FILENAME("ee_core_memops_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));

    trace_sink_stub #(.FILENAME("ee_core_memops_map.trace"),
                      .SINK_LABEL("ee_map"))
        u_trace_map (.clk(clk), .rst_n(rst_n),
            .ev_valid(ee_map_ev_valid), .ev_subsys(ee_map_ev_subsys),
            .ev_event(ee_map_ev_event), .ev_arg0(ee_map_ev_arg0),
            .ev_arg1(ee_map_ev_arg1), .ev_arg2(ee_map_ev_arg2),
            .ev_arg3(ee_map_ev_arg3), .ev_flags(ee_map_ev_flags));

    // ------------------------------------------------------------------
    // Observers
    // ------------------------------------------------------------------

    int core_retired_events;
    int core_branch_taken_events;
    int core_halt_events;
    int core_delay_slot_events;
    int core_except_events;
    int core_trap_events;
    int map_ram_reads;
    int map_ram_writes;
    int map_bios_reads;
    int errors;

    initial begin
        core_retired_events      = 0;
        core_branch_taken_events = 0;
        core_halt_events         = 0;
        core_delay_slot_events   = 0;
        core_except_events       = 0;
        core_trap_events         = 0;
        map_ram_reads            = 0;
        map_ram_writes           = 0;
        map_bios_reads           = 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'h04) != 32'd0)
                core_branch_taken_events <= core_branch_taken_events + 1;
            if ((core_ev_flags & 32'h08) != 32'd0)
                core_halt_events <= core_halt_events + 1;
            if ((core_ev_flags & 32'h10) != 32'd0)
                core_delay_slot_events <= core_delay_slot_events + 1;
            if ((core_ev_flags & 32'h20) != 32'd0)
                core_except_events <= core_except_events + 1;
            if ((core_ev_flags & 32'h80) != 32'd0)
                core_trap_events <= core_trap_events + 1;
        end

        if (rst_n && ee_map_ev_valid &&
            ee_map_ev_subsys == trace_pkg::SUBSYS_MEM) begin
            if (ee_map_ev_event == trace_pkg::EV_READ &&
                ee_map_ev_arg3[7:0] == 8'd0)
                map_bios_reads <= map_bios_reads + 1;
            if (ee_map_ev_event == trace_pkg::EV_READ &&
                ee_map_ev_arg3[7:0] == 8'd1)
                map_ram_reads <= map_ram_reads + 1;
            if (ee_map_ev_event == trace_pkg::EV_WRITE &&
                ee_map_ev_arg3[7:0] == 8'd1 &&
                (ee_map_ev_flags & 32'h1) != 32'd0)
                map_ram_writes <= map_ram_writes + 1;
        end
    end

    // ------------------------------------------------------------------
    // BIOS preload helpers
    // ------------------------------------------------------------------

    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_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
    task automatic bios_beq  (input int slot, input int rs, input int rt,
                              input int target_slot);
        bios_word(slot, enc_i(OP_BEQ, rs, rt,
                              branch_imm(slot, target_slot)));
    endtask
    task automatic bios_syscall(input int slot);
        bios_word(slot, 32'h0000_000C);
    endtask

    // ------------------------------------------------------------------
    // Stimulus
    // ------------------------------------------------------------------

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

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

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

        // Bootstrap
        // Ch33: base via kseg0 (0x8000_0000) so the SW/LW hit the
        // real ee_ram (post-run check + REGION_EE_RAM trace tag).
        // Useg now maps to a separate 4 MiB shadow with a distinct
        // REGION_USEG_SHADOW tag.
        bios_lui (B_LUI_1, 1, 16'h0000);                  // $1 unused, placeholder
        bios_lui (B_ORI_2, 2,    16'h8000);               // $2 = 0x80000000 (RAM base)
        bios_lui (B_LUI_3, 3, 16'hDEAD);                  // $3 upper
        bios_ori (B_ORI_3, 3, 3, 16'hBEEF);               // $3 = 0xDEAD_BEEF
        bios_sw  (B_SW,    3, 2, 16'h1000);               // MEM[$2+0x1000] = $3
        bios_lw  (B_LW,    4, 2, 16'h1000);               // $4 = MEM[$2+0x1000]
        bios_beq (B_BEQ,   3, 4, B_PASS);                 // pass if $3 == $4
        bios_nop (B_DSLOT);                                // delay slot
        bios_syscall(B_FAIL);                              // FAIL path
        bios_syscall(B_PASS);                              // PASS path

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

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

        // --------------------------------------------------------------
        // Checks
        // --------------------------------------------------------------
        if (core_halt_events != 1) begin
            $error("[tb_ee_core_memops] expected 1 halt event, got %0d",
                   core_halt_events);
            errors = errors + 1;
        end
        if (core_retired_events != 11) begin
            $error("[tb_ee_core_memops] expected 11 retires (success path), got %0d",
                   core_retired_events);
            errors = errors + 1;
        end
        if (core_branch_taken_events != 2) begin
            $error("[tb_ee_core_memops] expected 2 taken branches, got %0d",
                   core_branch_taken_events);
            errors = errors + 1;
        end
        if (core_delay_slot_events != core_branch_taken_events) begin
            $error("[tb_ee_core_memops] delay-slot invariant: %0d delay vs %0d taken",
                   core_delay_slot_events, core_branch_taken_events);
            errors = errors + 1;
        end
        if (core_except_events != 0 || core_trap_events != 0) begin
            $error("[tb_ee_core_memops] unexpected exception/trap retires: except=%0d trap=%0d",
                   core_except_events, core_trap_events);
            errors = errors + 1;
        end
        // Load-bearing: final pc parks at PASS, not FAIL. This is the
        // real test of the round-trip: wrong memory plumbing sends the
        // core to FAIL instead.
        if (core_pc !== BOOTSTRAP_VA + ((B_PASS - BOOTSTRAP_SLOT) << 2)) begin
            $error("[tb_ee_core_memops] final pc mismatch: got 0x%08h expected PASS 0x%08h (or FAIL 0x%08h)",
                   core_pc,
                   BOOTSTRAP_VA + ((B_PASS - BOOTSTRAP_SLOT) << 2),
                   BOOTSTRAP_VA + ((B_FAIL - BOOTSTRAP_SLOT) << 2));
            errors = errors + 1;
        end
        if (map_ram_writes != 1) begin
            $error("[tb_ee_core_memops] expected exactly 1 CPU→RAM write in map trace, got %0d",
                   map_ram_writes);
            errors = errors + 1;
        end
        if (map_ram_reads < 1) begin
            $error("[tb_ee_core_memops] expected >=1 CPU→RAM read in map trace, got %0d",
                   map_ram_reads);
            errors = errors + 1;
        end

        $display("[tb_ee_core_memops] retired=%0d taken=%0d delay=%0d halt=%0d ram_wr=%0d ram_rd=%0d bios_rd=%0d errors=%0d",
                 core_retired_events, core_branch_taken_events,
                 core_delay_slot_events, core_halt_events,
                 map_ram_writes, map_ram_reads, map_bios_reads, errors);

        if (errors == 0 &&
            core_halt_events         == 1 &&
            core_retired_events      == 11 &&
            core_branch_taken_events == 2 &&
            core_delay_slot_events   == 2 &&
            map_ram_writes           == 1 &&
            core_pc == BOOTSTRAP_VA + ((B_PASS - BOOTSTRAP_SLOT) << 2))
            $display("[tb_ee_core_memops] PASS");
        else
            $display("[tb_ee_core_memops] FAIL");

        $finish;
    end

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

endmodule : tb_ee_core_memops
