{#- MSI-X Table and PBA Implementation Template (Synthesizable) -#}
{{ header | safe }}

`default_nettype none

{% set pba_size = ((msix_config.num_vectors | default(16) + 31) // 32) %}

module msix_implementation #(
    parameter NUM_MSIX = {{ msix_config.num_vectors }},
    parameter MSIX_TABLE_BIR = {{ msix_config.table_bir }},
    parameter MSIX_TABLE_OFFSET = 32'h{{ "%X" | format(msix_config.table_offset) }},
    parameter MSIX_PBA_BIR = {{ msix_config.pba_bir }},
    parameter MSIX_PBA_OFFSET = 32'h{{ "%X" | format(msix_config.pba_offset) }},
    parameter PBA_SIZE = {{ pba_size }}  // Number of 32-bit words needed for PBA
)(
    input  wire        clk,
    input  wire        rst_n,

    // MSI-X control registers - dynamically connected to configuration space
    input  wire        msix_enabled,        // Connected to MSI-X Message Control Enable bit (bit 15)
    input  wire        msix_function_mask,  // Connected to MSI-X Message Control Function Mask bit (bit 14)
    input  wire [10:0] msix_table_size,     // Connected to MSI-X Message Control Table Size field (bits 10:0)

    // MSI-X capability register interface for dynamic control (placeholder wiring)
    input  wire        msix_cap_wr,
    input  wire [31:0] msix_cap_addr,
    input  wire [31:0] msix_cap_wdata,
    input  wire [3:0]  msix_cap_be,
    output logic [31:0] msix_cap_rdata,

    // MSI-X interrupt generation interface
    output logic        msix_interrupt,  // MSI-X interrupt request
    output logic [10:0] msix_vector,     // MSI-X vector number
    output logic [63:0] msix_msg_addr,   // MSI-X message address (from table when delivered)
    output logic [31:0] msix_msg_data,   // MSI-X message data (from table when delivered)

    // External interrupt trigger and ack
    input  wire         trigger_valid,
    input  wire [10:0]  trigger_vector,
    input  wire         msix_interrupt_ack,

    // BAR access interface
    input  wire [31:0]  bar_addr,
    input  wire [2:0]   bar_index,
    input  wire         bar_wr,
    input  wire         bar_rd,
    input  wire [31:0]  bar_wdata,
    input  wire [3:0]   bar_be,
    output logic [31:0] bar_rdata,
    output logic        bar_ack
);

    // Safe log2 width to avoid zero-width vectors when NUM_MSIX == 1
    localparam int NUM_MSIX_LG2 = (NUM_MSIX <= 1) ? 1 : $clog2(NUM_MSIX);

    // MSI-X Table storage (staging and active for atomic commits)
    (* ram_style="block" *) logic [31:0] msix_table_staging[0:NUM_MSIX*4-1];
    (* ram_style="block" *) logic [31:0] msix_table_active[0:NUM_MSIX*4-1];
    logic [3:0] msix_stage_valid[0:NUM_MSIX-1];

    // MSI-X PBA storage
    logic [31:0] msix_pba[0:PBA_SIZE-1];

    // Address decode
    wire is_table_access = (bar_index == MSIX_TABLE_BIR) &&
                           (bar_addr >= MSIX_TABLE_OFFSET) &&
                           (bar_addr < (MSIX_TABLE_OFFSET + NUM_MSIX * 16));

    wire is_pba_access = (bar_index == MSIX_PBA_BIR) &&
                         (bar_addr >= MSIX_PBA_OFFSET) &&
                         (bar_addr < (MSIX_PBA_OFFSET + PBA_SIZE * 4));

    // Calculated addresses (DWORD addressing)
    wire [31:0] table_addr = (bar_addr - MSIX_TABLE_OFFSET) >> 2;
    wire [31:0] pba_addr   = (bar_addr - MSIX_PBA_OFFSET) >> 2;
    // Select the entry index as table_addr >> 2 (4 DWORDs per entry)
    wire [NUM_MSIX_LG2-1:0] entry_index = table_addr >> 2;
    wire [1:0]                  dword_index = table_addr[1:0];

    // BAR read data register and ack
    logic [31:0] bar_rdata_reg;
    logic        bar_ack_reg;

    // Pending/interrupt control
    logic interrupt_pending;
    logic [10:0] current_vector;

    // Priority encoder for pending vectors
    logic [NUM_MSIX_LG2-1:0] next_pending_idx;
    logic                        pending_found;

    // Combinational view of pending vector table control DWORD and data
    logic [31:0] pending_ctrl_dword;
    logic [31:0] pending_msg_addr_lo;
    logic [31:0] pending_msg_addr_hi;
    logic [31:0] pending_msg_data;
    logic        pending_vector_masked;

    // BAR read muxing (synchronous read)
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            bar_rdata_reg <= 32'h0;
            bar_ack_reg   <= 1'b0;
        end else begin
            bar_ack_reg <= 1'b0;
            if (bar_rd) begin
                if (is_table_access) begin
                    bar_rdata_reg <= msix_table_staging[table_addr];
                    bar_ack_reg   <= 1'b1;
                end else if (is_pba_access) begin
                    bar_rdata_reg <= msix_pba[pba_addr];
                    bar_ack_reg   <= 1'b1;
                end
            end else if (bar_wr) begin
                if (is_table_access) begin
                    // Byte-enable writes to staging table
                    if (bar_be[0]) msix_table_staging[table_addr][7:0]   <= bar_wdata[7:0];
                    if (bar_be[1]) msix_table_staging[table_addr][15:8]  <= bar_wdata[15:8];
                    if (bar_be[2]) msix_table_staging[table_addr][23:16] <= bar_wdata[23:16];
                    if (bar_be[3]) msix_table_staging[table_addr][31:24] <= bar_wdata[31:24];
                    // Mark dword staged
                    msix_stage_valid[entry_index][dword_index] <= 1'b1;
                    // Commit atomically when all 4 staged
                    if (&msix_stage_valid[entry_index]) begin
                        for (int k = 0; k < 4; k++) begin
                            msix_table_active[(entry_index << 2) + k] <=
                                msix_table_staging[(entry_index << 2) + k];
                        end
                        msix_stage_valid[entry_index] <= 4'b0000;
                    end
                    bar_ack_reg <= 1'b1;
                end else if (is_pba_access) begin
                    // PBA is read-only in typical MSI-X usage; ignore writes
                    bar_ack_reg <= 1'b1;
                end
            end
        end
    end

    assign bar_rdata = bar_rdata_reg;
    assign bar_ack   = bar_ack_reg;

    // External trigger: set PBA bit; Ack: clear PBA bit
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            for (int i = 0; i < PBA_SIZE; i++) begin
                msix_pba[i] <= 32'h0;
            end
        end else begin
            if (trigger_valid && (trigger_vector < NUM_MSIX)) begin
                msix_pba[trigger_vector >> 5] <=
                    msix_pba[trigger_vector >> 5] | (32'h1 << trigger_vector[4:0]);
            end
            if (msix_interrupt_ack && interrupt_pending) begin
                msix_pba[current_vector >> 5] <=
                    msix_pba[current_vector >> 5] & ~(32'h1 << current_vector[4:0]);
            end
        end
    end

    // Priority encoder for first pending vector
    always_comb begin
        pending_found = 1'b0;
        next_pending_idx = '0;
        for (int i = 0; i < NUM_MSIX; i++) begin
            if (!pending_found && (msix_pba[i >> 5] & (32'h1 << i[4:0]))) begin
                pending_found = 1'b1;
                next_pending_idx = i[NUM_MSIX_LG2-1:0];
            end
        end
    end

    // Combinational fetch of message fields for the pending vector
    assign pending_msg_addr_lo  = msix_table_active[{next_pending_idx, 2'b00}];
    assign pending_msg_addr_hi  = msix_table_active[{next_pending_idx, 2'b01}];
    assign pending_msg_data     = msix_table_active[{next_pending_idx, 2'b10}];
    assign pending_ctrl_dword   = msix_table_active[{next_pending_idx, 2'b11}];
    assign pending_vector_masked = pending_ctrl_dword[0];

    // Interrupt state machine
    typedef enum logic [1:0] { IDLE, PENDING, WAITING_ACK } intr_state_t;
    intr_state_t intr_state;
    logic [10:0] pending_vector;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            intr_state       <= IDLE;
            msix_interrupt   <= 1'b0;
            msix_vector      <= 11'h0;
            msix_msg_addr    <= 64'h0;
            msix_msg_data    <= 32'h0;
            pending_vector   <= 11'h0;
            current_vector   <= 11'h0;
            interrupt_pending <= 1'b0;
            // Clear tables and stage marks
            for (int i = 0; i < NUM_MSIX * 4; i++) begin
                msix_table_staging[i] <= 32'h0;
                msix_table_active[i]  <= 32'h0;
            end
            for (int v = 0; v < NUM_MSIX; v++) begin
                msix_stage_valid[v] <= 4'b0000;
            end
            // Capability readback default
            msix_cap_rdata <= 32'h0;
        end else begin
            msix_interrupt <= 1'b0; // pulse-style interrupt

            // Simple placeholder for capability readback (not implemented)
            if (msix_cap_wr) begin
                // No-op; integrate with real cap space externally
                msix_cap_rdata <= 32'h0;
            end

            case (intr_state)
                IDLE: begin
                    interrupt_pending <= 1'b0;
                    if (pending_found) begin
                        // Zero-extend next_pending_idx to 11 bits
{% raw %}                        pending_vector <= {{(11-NUM_MSIX_LG2){1'b0}}, next_pending_idx};{% endraw %}
                        intr_state <= PENDING;
                    end
                end

                PENDING: begin
                    if (msix_enabled && !msix_function_mask && !pending_vector_masked) begin
                        // Deliver this vector
                        msix_vector    <= pending_vector;
                        current_vector <= pending_vector;
                        msix_msg_addr  <= {pending_msg_addr_hi, pending_msg_addr_lo};
                        msix_msg_data  <= pending_msg_data;
                        msix_interrupt <= 1'b1;
                        interrupt_pending <= 1'b1;
                        intr_state <= WAITING_ACK;
                    end else begin
                        // Masked or disabled; remain pending and look again
                        intr_state <= IDLE;
                    end
                end

                WAITING_ACK: begin
                    if (msix_interrupt_ack) begin
                        msix_interrupt <= 1'b0;
                        intr_state <= IDLE;
                    end
                end

                default: intr_state <= IDLE;
            endcase
        end
    end

endmodule

`default_nettype wire