// retroDE_ps2 — tb_iop_core_bootstrap
//
// First BIOS-sourced IOP program. The core's reset vector is now the
// architectural MIPS R3000 value (0xBFC0_0000), which after kseg1 strip
// in iop_memory_map_stub lands in the shared BIOS window at physical
// 0x1FC0_0000. A tiny hand-assembled bootstrap lives in bios_rom_stub.
//
// Phase 1 scope (Codex's staging): no interrupts. The bootstrap just
// fetches from BIOS, jumps to a small routine, touches mapped DMAC
// registers, launches a ch9 transfer, polls DONE_COUNT, and halts via
// SYSCALL. The point is to validate the *reset source* moved into
// ROM — exception-driven control flow from BIOS is Phase 2.
//
// RAM layout (TB preloads via bridge_wr_*):
//   0x0000_0200..0x0000_020F   DMA payload
//
// BIOS layout (TB preloads via hierarchical reference):
//   0xBFC0_0000   j    0xBFC0_0100        ; reset entry; jump to main
//   0xBFC0_0004   nop                     ; delay slot
//   0xBFC0_0008..0xBFC0_00FF   NOP sled   (default bios_rom_stub fixture)
//   0xBFC0_0100+  bootstrap main:
//     lui   $1, 0x1F80
//     ori   $1, $1, 0x1520              ; DMAC_MADR
//     ori   $2, $0, 0x0200               ; payload base
//     ori   $3, $0, 4                    ; BCR
//     ori   $4, $0, 1
//     addiu $6, $1, 0x000C              ; DONE_COUNT
//     ori   $7, $0, 1                    ; target
//     sw    $2, 0($1)
//     sw    $3, 4($1)
//     sw    $4, 8($1)                    ; start DMA
//   wait:
//     lw    $10, 0($6)
//     bne   $10, $7, wait
//     nop
//     syscall
//
// Acceptance:
//   - halt_o asserts exactly once
//   - exactly 1 DMA_DONE event
//   - core retires the J at 0xBFC0_0000 with branch-taken flag
//   - at least two IOP map events with region=IOP_BIOS (the reset
//     J fetch and the delay-slot NOP fetch); in practice far more
//     as the core traverses the NOP sled until the bootstrap proper

`timescale 1ns/1ps

module tb_iop_core_bootstrap;

    localparam int IOP_RAM_BYTES  = 4 * 1024;
    localparam int IOP_RAM_ADDR_W = $clog2(IOP_RAM_BYTES);
    localparam int BIOS_BYTES     = 4 * 1024 * 1024;

    // Architectural kseg1 addresses
    localparam logic [31:0] RESET_VECTOR  = 32'hBFC0_0000;
    localparam logic [31:0] BOOTSTRAP_VA  = 32'hBFC0_0100;
    // Physical equivalents (used for map-level trace filtering)
    localparam logic [31:0] RESET_VECTOR_PHYS = 32'h1FC0_0000;
    localparam logic [31:0] PAYLOAD_BASE      = 32'h0000_0200;

    // Bootstrap slot offsets inside BIOS (byte offset / 4)
    localparam int RESET_SLOT      = 0;                // 0xBFC0_0000 / 4 + 0
    localparam int BOOTSTRAP_SLOT0 = 32'h100 >> 2;     // 0x40
    // A BIOS "slot" here is an absolute word-index from start of the
    // BIOS ROM (which corresponds to byte offset = slot * 4). Since
    // the TB preloads via hierarchical reference into u_bios.mem[],
    // the slot is exactly the array index.

    // MIPS opcodes
    localparam logic [5:0] OP_SPECIAL = 6'h00;
    localparam logic [5:0] OP_J       = 6'h02;
    localparam logic [5:0] OP_BEQ     = 6'h04;
    localparam logic [5:0] OP_BNE     = 6'h05;
    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;

    // ------------------------------------------------------------------
    // IOP core — PC_RESET defaulted to architectural 0xBFC0_0000, so we
    // instantiate without overriding to validate the seam.
    // ------------------------------------------------------------------

    logic        core_go;
    logic        core_halt;
    logic [31:0] core_pc;

    logic        map_cpu_rd_en;
    logic [31:0] map_cpu_rd_addr;
    logic [31:0] map_cpu_rd_data;
    logic        map_cpu_rd_valid;

    logic        map_cpu_wr_en;
    logic [31:0] map_cpu_wr_addr;
    logic [31:0] map_cpu_wr_data;
    logic [3:0]  map_cpu_wr_be;

    logic        iop_cpu_irq;

    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;

    iop_core_stub u_core (
        .clk(clk), .rst_n(rst_n),
        .go_i(core_go),
        .map_rd_en(map_cpu_rd_en), .map_rd_addr(map_cpu_rd_addr),
        .map_rd_data(map_cpu_rd_data), .map_rd_valid(map_cpu_rd_valid),
        .map_wr_en(map_cpu_wr_en), .map_wr_addr(map_cpu_wr_addr),
        .map_wr_data(map_cpu_wr_data), .map_wr_be(map_cpu_wr_be),
        .cpu_irq(iop_cpu_irq),
        .halt_o(core_halt), .pc_o(core_pc),
        .trap_o(), .trap_pc_o(), .trap_instr_o(),
        .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)
    );

    // ------------------------------------------------------------------
    // IOP map + IOP RAM + BIOS ROM + DMAC + INTC
    // ------------------------------------------------------------------

    logic        iop_ram_rd_en;
    logic [20:0] iop_ram_rd_addr;
    logic [31:0] iop_ram_rd_data;
    logic        iop_ram_rd_valid;
    logic        iop_ram_wr_en;
    logic [20:0] iop_ram_wr_addr;
    logic [31:0] iop_ram_wr_data;
    logic [3:0]  iop_ram_wr_be;
    logic [7:0]  iop_ram_master_id;

    logic        map_dmac_rd_en;
    logic [3:0]  map_dmac_rd_addr;
    logic [31:0] map_dmac_rd_data;
    logic        map_dmac_rd_valid;
    logic        map_dmac_wr_en;
    logic [3:0]  map_dmac_wr_addr;
    logic [31:0] map_dmac_wr_data;

    logic        map_intc_rd_en;
    logic [7:0]  map_intc_rd_addr;
    logic [31:0] map_intc_rd_data;
    logic        map_intc_rd_valid;
    logic        map_intc_wr_en;
    logic [7:0]  map_intc_wr_addr;
    logic [31:0] map_intc_wr_data;

    logic        bios_rd_en;
    logic [21:0] bios_rd_addr;
    logic [31:0] bios_rd_data;
    logic        bios_rd_valid;

    logic        dma_rd_en;
    logic [31:0] dma_rd_addr;
    logic [7:0]  dma_master_id;
    logic [31:0] dma_rd_data;
    logic        dma_rd_valid;

    logic        tb_br_wr_en;
    logic [31:0] tb_br_wr_addr;
    logic [31:0] tb_br_wr_data;
    logic [3:0]  tb_br_wr_be;

    logic                iop_map_ev_valid;
    trace_pkg::subsys_e  iop_map_ev_subsys;
    trace_pkg::event_e   iop_map_ev_event;
    logic [63:0]         iop_map_ev_arg0, iop_map_ev_arg1, iop_map_ev_arg2, iop_map_ev_arg3;
    logic [31:0]         iop_map_ev_flags;

    iop_memory_map_stub u_iop_map (
        .clk(clk), .rst_n(rst_n),
        .iop_rd_en(map_cpu_rd_en), .iop_rd_addr(map_cpu_rd_addr),
        .iop_rd_data(map_cpu_rd_data), .iop_rd_valid(map_cpu_rd_valid),
        .iop_wr_en(map_cpu_wr_en), .iop_wr_addr(map_cpu_wr_addr),
        .iop_wr_data(map_cpu_wr_data), .iop_wr_be(map_cpu_wr_be),
        .master_id(8'd2),
        .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'd0),
        .dma_rd_en(dma_rd_en), .dma_rd_addr(dma_rd_addr),
        .dma_master_id(dma_master_id),
        .dma_rd_data(dma_rd_data), .dma_rd_valid(dma_rd_valid),
        .sif_rd_en(), .sif_rd_addr(),
        .sif_rd_data(32'd0), .sif_rd_valid(1'b0),
        .sif_wr_en(), .sif_wr_addr(), .sif_wr_data(),
        .iop_dmac_rd_en(map_dmac_rd_en), .iop_dmac_rd_addr(map_dmac_rd_addr),
        .iop_dmac_rd_data(map_dmac_rd_data), .iop_dmac_rd_valid(map_dmac_rd_valid),
        .iop_dmac_wr_en(map_dmac_wr_en), .iop_dmac_wr_addr(map_dmac_wr_addr),
        .iop_dmac_wr_data(map_dmac_wr_data),
        .iop_intc_rd_en(map_intc_rd_en), .iop_intc_rd_addr(map_intc_rd_addr),
        .iop_intc_rd_data(map_intc_rd_data), .iop_intc_rd_valid(map_intc_rd_valid),
        .iop_intc_wr_en(map_intc_wr_en), .iop_intc_wr_addr(map_intc_wr_addr),
        .iop_intc_wr_data(map_intc_wr_data),
        .input_p1(32'd0), .input_p2(32'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(iop_ram_rd_en), .ram_rd_addr(iop_ram_rd_addr),
        .ram_rd_data(iop_ram_rd_data), .ram_rd_valid(iop_ram_rd_valid),
        .ram_wr_en(iop_ram_wr_en), .ram_wr_addr(iop_ram_wr_addr),
        .ram_wr_data(iop_ram_wr_data), .ram_wr_be(iop_ram_wr_be),
        .ram_master_id(iop_ram_master_id),
        .ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys),
        .ev_event(iop_map_ev_event),
        .ev_arg0(iop_map_ev_arg0), .ev_arg1(iop_map_ev_arg1),
        .ev_arg2(iop_map_ev_arg2), .ev_arg3(iop_map_ev_arg3),
        .ev_flags(iop_map_ev_flags)
    );

    iop_ram_stub #(.SIZE_BYTES(IOP_RAM_BYTES)) u_iop_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(iop_ram_rd_en), .rd_addr(iop_ram_rd_addr[IOP_RAM_ADDR_W-1:0]),
        .rd_data(iop_ram_rd_data), .rd_valid(iop_ram_rd_valid),
        .wr_en(iop_ram_wr_en), .wr_addr(iop_ram_wr_addr[IOP_RAM_ADDR_W-1:0]),
        .wr_data(iop_ram_wr_data), .wr_be(iop_ram_wr_be),
        .master_id(iop_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()
    );

    logic        ep_valid;
    logic [31:0] ep_data;
    logic        ep_last;
    logic        ep_ready;
    assign ep_ready = 1'b1;

    logic        dmac_irq_completion;
    logic        dmac_busy;
    logic [31:0] dmac_done_count;

    logic                dmac_ev_valid;
    trace_pkg::subsys_e  dmac_ev_subsys;
    trace_pkg::event_e   dmac_ev_event;
    logic [63:0]         dmac_ev_arg0, dmac_ev_arg1, dmac_ev_arg2, dmac_ev_arg3;
    logic [31:0]         dmac_ev_flags;

    iop_dmac_reg_stub u_dmac (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(map_dmac_wr_en), .reg_rd_en(map_dmac_rd_en),
        .reg_offset(map_dmac_wr_en ? map_dmac_wr_addr : map_dmac_rd_addr),
        .reg_wr_data(map_dmac_wr_data),
        .reg_rd_data(map_dmac_rd_data), .reg_rd_valid(map_dmac_rd_valid),
        .mem_rd_en(dma_rd_en), .mem_rd_addr(dma_rd_addr),
        .mem_master_id(dma_master_id),
        .mem_rd_data(dma_rd_data), .mem_rd_valid(dma_rd_valid),
        .ep_valid(ep_valid), .ep_data(ep_data), .ep_last(ep_last),
        .ep_ready(ep_ready),
        .irq_completion_o(dmac_irq_completion),
        .busy_o(dmac_busy), .done_count_o(dmac_done_count),
        .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys),
        .ev_event(dmac_ev_event),
        .ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1),
        .ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3),
        .ev_flags(dmac_ev_flags)
    );

    logic [15:0] iop_irq_src;
    assign iop_irq_src = {15'd0, dmac_irq_completion};

    intc_stub #(
        .INTC_STAT_OFFSET(8'h70),
        .INTC_MASK_OFFSET(8'h74)
    ) u_intc (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(map_intc_wr_en), .reg_rd_en(map_intc_rd_en),
        .reg_addr(map_intc_wr_en ? map_intc_wr_addr : map_intc_rd_addr),
        .reg_wr_data(map_intc_wr_data),
        .reg_rd_data(map_intc_rd_data), .reg_rd_valid(map_intc_rd_valid),
        .irq_src(iop_irq_src), .cpu_irq(iop_cpu_irq),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    // ------------------------------------------------------------------
    // Trace sinks
    // ------------------------------------------------------------------

    trace_sink_stub #(.FILENAME("iop_boot_core.trace"), .SINK_LABEL("iop_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("iop_boot_map.trace"), .SINK_LABEL("iop_map"))
        u_trace_map (.clk(clk), .rst_n(rst_n),
            .ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys),
            .ev_event(iop_map_ev_event), .ev_arg0(iop_map_ev_arg0),
            .ev_arg1(iop_map_ev_arg1), .ev_arg2(iop_map_ev_arg2),
            .ev_arg3(iop_map_ev_arg3), .ev_flags(iop_map_ev_flags));

    trace_sink_stub #(.FILENAME("iop_boot_dmac.trace"), .SINK_LABEL("iop_dmac"))
        u_trace_dmac (.clk(clk), .rst_n(rst_n),
            .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys),
            .ev_event(dmac_ev_event), .ev_arg0(dmac_ev_arg0),
            .ev_arg1(dmac_ev_arg1), .ev_arg2(dmac_ev_arg2),
            .ev_arg3(dmac_ev_arg3), .ev_flags(dmac_ev_flags));

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

    int dmac_done_events;
    int core_retired_events;
    int core_branch_taken_events;
    int core_halt_events;
    int core_delay_slot_events;
    int map_bios_reads;
    int map_bios_reset_reads;       // reads at the reset-vector physical addr
    int errors;

    initial begin
        dmac_done_events         = 0;
        core_retired_events      = 0;
        core_branch_taken_events = 0;
        core_halt_events         = 0;
        core_delay_slot_events   = 0;
        map_bios_reads           = 0;
        map_bios_reset_reads     = 0;
        errors                   = 0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && dmac_ev_valid &&
            dmac_ev_subsys == trace_pkg::SUBSYS_DMAC &&
            dmac_ev_event  == trace_pkg::EV_DMA_DONE)
            dmac_done_events <= dmac_done_events + 1;

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

        if (rst_n && iop_map_ev_valid &&
            iop_map_ev_subsys == trace_pkg::SUBSYS_IOP &&
            iop_map_ev_event  == trace_pkg::EV_READ &&
            iop_map_ev_arg3[7:0] == 8'd6) begin          // REGION_IOP_BIOS
            map_bios_reads <= map_bios_reads + 1;
            // arg0 is the kseg1 virtual address emitted by the CPU;
            // reset-fetch is specifically 0xBFC0_0000.
            if (iop_map_ev_arg0[31:0] == RESET_VECTOR)
                map_bios_reset_reads <= map_bios_reset_reads + 1;
        end
    end

    // ------------------------------------------------------------------
    // Preload helpers
    // ------------------------------------------------------------------

    task automatic bridge_write(input logic [31:0] addr, input logic [31:0] data);
        @(negedge clk);
        tb_br_wr_en   = 1'b1;
        tb_br_wr_addr = addr;
        tb_br_wr_data = data;
        tb_br_wr_be   = 4'b1111;
        @(negedge clk);
        tb_br_wr_en   = 1'b0;
        tb_br_wr_addr = 32'd0;
        tb_br_wr_data = 32'd0;
        tb_br_wr_be   = 4'd0;
    endtask

    // BIOS preload via hierarchical reference. bios_rom_stub's initial
    // block fills mem with NOPs at time 0; this task runs after reset
    // is released (some clock cycles later), so it overwrites the NOPs
    // at the slots we care about. The remaining NOPs stay as the default
    // sled between the reset J and the bootstrap main entry.
    task automatic bios_word(input int slot, input logic [31:0] word);
        u_bios.mem[slot] = word;
    endtask

    // MIPS encoders
    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 [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

    // J encoder: target_addr is absolute; we emit the imm26 field as
    // target_addr[27:2]. MIPS hardware composes the final target as
    // {pc[31:28], imm26, 2'b00}.
    function automatic logic [31:0] enc_j(input logic [31:0] target_addr);
        return {OP_J, target_addr[27:2]};
    endfunction

    task automatic emit_j(input int bios_slot, input logic [31:0] target_va);
        bios_word(bios_slot, enc_j(target_va));
    endtask

    task automatic emit_lui(input int bios_slot, input int rt,
                            input logic [15:0] imm);
        bios_word(bios_slot, enc_i(OP_LUI, 0, rt, imm));
    endtask
    task automatic emit_ori(input int bios_slot, input int rt, input int rs,
                            input logic [15:0] imm);
        bios_word(bios_slot, enc_i(OP_ORI, rs, rt, imm));
    endtask
    task automatic emit_addiu(input int bios_slot, input int rt, input int rs,
                              input logic [15:0] imm);
        bios_word(bios_slot, enc_i(OP_ADDIU, rs, rt, imm));
    endtask
    task automatic emit_lw(input int bios_slot, input int rt, input int rs,
                           input logic [15:0] offs);
        bios_word(bios_slot, enc_i(OP_LW, rs, rt, offs));
    endtask
    task automatic emit_sw(input int bios_slot, input int rt, input int rs,
                           input logic [15:0] offs);
        bios_word(bios_slot, enc_i(OP_SW, rs, rt, offs));
    endtask
    task automatic emit_bne(input int bios_slot, input int rs, input int rt,
                            input int target_slot);
        bios_word(bios_slot,
                  enc_i(OP_BNE, rs, rt, branch_imm(bios_slot, target_slot)));
    endtask
    task automatic emit_nop(input int bios_slot);
        bios_word(bios_slot, 32'd0);
    endtask
    task automatic emit_syscall(input int bios_slot);
        bios_word(bios_slot, 32'h0000_000C);
    endtask

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

    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 = 32'd0;
        tb_br_wr_be   = 4'd0;

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

        // Preload DMA payload into IOP RAM
        bridge_write(PAYLOAD_BASE + 32'h00, 32'hB105_0000);
        bridge_write(PAYLOAD_BASE + 32'h04, 32'hB105_0001);
        bridge_write(PAYLOAD_BASE + 32'h08, 32'hB105_0002);
        bridge_write(PAYLOAD_BASE + 32'h0C, 32'hB105_0003);

        // BIOS reset entry: unconditional jump into the bootstrap proper.
        emit_j  (RESET_SLOT,     BOOTSTRAP_VA);
        emit_nop(RESET_SLOT + 1);                            // delay slot

        // Bootstrap main at word index 0x40 (byte 0x100 in BIOS).
        // Slots are numbered from BOOTSTRAP_SLOT0 (= 0x40).
        emit_lui  (BOOTSTRAP_SLOT0 +  0,  1, 16'h1F80);
        emit_ori  (BOOTSTRAP_SLOT0 +  1,  1,  1, 16'h1520);  // $1 = DMAC_MADR
        emit_ori  (BOOTSTRAP_SLOT0 +  2,  2,  0, 16'h0200);  // $2 = payload
        emit_ori  (BOOTSTRAP_SLOT0 +  3,  3,  0, 16'h0004);  // $3 = 4
        emit_ori  (BOOTSTRAP_SLOT0 +  4,  4,  0, 16'h0001);  // $4 = 1
        emit_addiu(BOOTSTRAP_SLOT0 +  5,  6,  1, 16'h000C);  // $6 = DONE_COUNT
        emit_ori  (BOOTSTRAP_SLOT0 +  6,  7,  0, 16'h0001);  // $7 = 1 (target)
        emit_sw   (BOOTSTRAP_SLOT0 +  7,  2,  1, 16'h0000);  // MADR = $2
        emit_sw   (BOOTSTRAP_SLOT0 +  8,  3,  1, 16'h0004);  // BCR  = $3
        emit_sw   (BOOTSTRAP_SLOT0 +  9,  4,  1, 16'h0008);  // CHCR = $4 (start)
        // wait (slot +10)
        emit_lw   (BOOTSTRAP_SLOT0 + 10, 10,  6, 16'h0000);  // $10 = DONE_COUNT
        emit_bne  (BOOTSTRAP_SLOT0 + 11, 10,  7, BOOTSTRAP_SLOT0 + 10);
        emit_nop  (BOOTSTRAP_SLOT0 + 12);                    // delay slot
        emit_syscall(BOOTSTRAP_SLOT0 + 13);                  // halt

        // Kick the core. No TB orchestration after this.
        @(negedge clk);
        core_go = 1'b1;
        @(negedge clk);
        core_go = 1'b0;

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

        // --------------------------------------------------------------
        // Post-halt checks
        // --------------------------------------------------------------
        if (dmac_done_events != 1) begin
            $error("[tb_iop_core_bootstrap] expected 1 DMA_DONE, got %0d",
                   dmac_done_events);
            errors = errors + 1;
        end
        if (core_halt_events != 1) begin
            $error("[tb_iop_core_bootstrap] expected 1 halt event, got %0d",
                   core_halt_events);
            errors = errors + 1;
        end
        // The core fetched *every* instruction from BIOS — both the
        // reset J and the entire bootstrap routine. The map emits one
        // IOP READ per fetch tagged region=IOP_BIOS. Load-bearing check:
        // at least one of those reads must be at the reset vector
        // itself (0xBFC0_0000), which proves the reset source changed.
        if (map_bios_reset_reads < 1) begin
            $error("[tb_iop_core_bootstrap] no BIOS fetch at reset vector 0x%08h",
                   RESET_VECTOR);
            errors = errors + 1;
        end
        if (map_bios_reads < 10) begin
            $error("[tb_iop_core_bootstrap] expected >=10 BIOS reads, got %0d",
                   map_bios_reads);
            errors = errors + 1;
        end
        // Delay-slot invariant still holds
        if (core_delay_slot_events != core_branch_taken_events) begin
            $error("[tb_iop_core_bootstrap] delay-slot mismatch: %0d vs %0d taken",
                   core_delay_slot_events, core_branch_taken_events);
            errors = errors + 1;
        end

        $display("[tb_iop_core_bootstrap] retired=%0d taken=%0d delay=%0d halt=%0d done=%0d bios_reads=%0d reset_reads=%0d errors=%0d",
                 core_retired_events, core_branch_taken_events,
                 core_delay_slot_events, core_halt_events,
                 dmac_done_events, map_bios_reads, map_bios_reset_reads,
                 errors);

        if (errors == 0 &&
            dmac_done_events     == 1 &&
            core_halt_events     == 1 &&
            map_bios_reset_reads >= 1 &&
            map_bios_reads       >= 10)
            $display("[tb_iop_core_bootstrap] PASS");
        else
            $display("[tb_iop_core_bootstrap] FAIL");

        $finish;
    end

    initial begin
        #10000000;
        $error("[tb_iop_core_bootstrap] timeout");
        $finish;
    end

endmodule : tb_iop_core_bootstrap
