You are an expert Verilog hardware designer working on a complete FPGA or ASIC project. You are given the full source code of a Verilog project. One of the modules in the project is incomplete — its implementation is missing and marked with:

// >>> Module Implementation Begin

// <<< Module Implementation End

Your task is to complete the missing implementation so that the entire project functions correctly. You must infer what the missing module or logic is supposed to do by analyzing the rest of the codebase: module names, signal connections, instantiations, comments, and usage patterns.

Follow these rules:

1. Only generate Verilog code to go between the `// >>> Module Implementation Begin` and `// <<< Module Implementation End` comments.
2. Use Verilog-2001 syntax. Ensure the code is synthesizable.
3. Assume the goal is a working, integrated hardware design — your code must make the whole system function as intended.
4. Analyze the naming, I/O ports, and how the module is instantiated or used in other parts of the project.
5. Implement correct behavior based on design context. Do not guess randomly — reason it out.
6. Avoid vendor-specific features unless explicitly required.
7. Do not output comments, explanation, or anything outside the required code region unless explicitly asked.

Your output should contain **only the Verilog code** that fits inside the marked region.

`default_nettype none

module tt_um_ks_pyamnihc (
    input  wire [7:0] ui_in,    // Dedicated inputs - connected to the input switches
    output wire [7:0] uo_out,   // Dedicated outputs - connected to the 7 segment display
    input  wire [7:0] uio_in,   // IOs: Bidirectional Input path
    output wire [7:0] uio_out,  // IOs: Bidirectional Output path
    output wire [7:0] uio_oe,   // IOs: Bidirectional Enable path (active high: 0=input, 1=output)
    input  wire       ena,      // will go high when the design is enabled
    input  wire       clk,      // clock
    input  wire       rst_n     // reset_n - low to reset
);
// >>> Module Implementation Begin

// <<< Module Implementation End
endmodule




module spi_slave_mem_interface #(
    parameter INST_WIDTH = 1,
    parameter ADDR_WIDTH = 7,
    parameter DATA_WIDTH = 8
) (
    input sck_i,
    input sdi_i,
    output sdo_o,
    input cs_ni,
    output [ADDR_WIDTH-1:0] addr_o,
    output [DATA_WIDTH-1:0] write_data_o,
    output write_en_o,
    input [DATA_WIDTH-1:0] read_data_i,
    output read_en_o
);
// CPOL: 0, CPHA: 0

localparam SPI_FRAME_WIDTH = INST_WIDTH + ADDR_WIDTH + DATA_WIDTH;
localparam BIT_COUNT_WIDTH = $clog2(SPI_FRAME_WIDTH) + 1;

// shift register buffer
reg [SPI_FRAME_WIDTH-1:0] pico_reg;
always @(posedge sck_i or posedge cs_ni) begin
    if (cs_ni) pico_reg <= 'b0;
    else pico_reg <= {pico_reg[SPI_FRAME_WIDTH-2:0], sdi_i};
end 

// keeps track of current bit position
reg [BIT_COUNT_WIDTH-1:0] bit_count;
always @(posedge sck_i or posedge cs_ni) begin
    if (cs_ni) begin
        bit_count <= 'b0;
    end else if ((read_flag || write_flag) && 
                    (bit_count == (SPI_FRAME_WIDTH))) begin
        bit_count <= INST_WIDTH+ADDR_WIDTH+1;
    end else begin
        bit_count <= bit_count + 1'b1;
    end
end

// instruction decode
reg read_flag, write_flag;
always @(posedge sck_i or posedge cs_ni) begin
    if (cs_ni) begin
        read_flag <= 'b0;
        write_flag <= 'b0;
    end else if (bit_count < INST_WIDTH) begin
        read_flag <= sdi_i;
        write_flag <= !sdi_i;
    end
end

// address decode and latch
reg [ADDR_WIDTH-1:0] addr_reg;
always @(posedge sck_i or posedge cs_ni) begin
    if (cs_ni) begin
        addr_reg <= 'b0; 
    end else if (bit_count == (INST_WIDTH + ADDR_WIDTH - 1)) begin 
        addr_reg <= {pico_reg[ADDR_WIDTH-2:0], sdi_i};
    end else if (bit_count == (SPI_FRAME_WIDTH)) begin
        addr_reg <= addr_reg + 1'b1;
    end
end

assign addr_o = addr_reg;

// write data latch
reg [DATA_WIDTH-1:0] write_data;
always @(posedge sck_i or posedge cs_ni) begin
    if (cs_ni) begin
        write_data <= 'b0;
    end else if ((write_flag == 1'b1) 
                    && (bit_count == (SPI_FRAME_WIDTH - 1))) begin
        write_data <= {pico_reg[DATA_WIDTH-2:0], sdi_i};
    end
end

// write enable strobe
reg write_en;
always @(posedge sck_i or posedge cs_ni) begin
    if (cs_ni) begin
        write_en <= 'b0;
    end else if ((write_flag == 1'b1) 
                    && (bit_count == (SPI_FRAME_WIDTH - 1))) begin
        write_en <= 1'b1;
    end else begin
        write_en <= 1'b0;
    end
end

assign write_data_o = write_data;
assign write_en_o = write_en;

// read enable strobe
reg read_en;
always @(posedge sck_i or posedge cs_ni) begin
    if (cs_ni) begin
        read_en <= 'b0;
    end else if ((read_flag == 1'b1) 
                    && ((bit_count == (INST_WIDTH + ADDR_WIDTH - 1)
                        || bit_count == (SPI_FRAME_WIDTH)))) begin
        read_en <= 1'b1;
    end else begin
        read_en <= 1'b0;
    end
end

assign read_en_o = read_en;

// shift out read data
reg sdo_reg;
always @(negedge sck_i or posedge cs_ni) begin
    if (cs_ni) begin
        sdo_reg <= 'b0;
    end else if ((read_flag == 1'b1) 
                    && (bit_count > (INST_WIDTH + ADDR_WIDTH - 1)) 
                    && (bit_count < SPI_FRAME_WIDTH)) begin
        sdo_reg <= read_data_i[(SPI_FRAME_WIDTH - 1) - bit_count];
    end else begin
        sdo_reg <= 1'b0;
    end
end

assign sdo_o = sdo_reg;

endmodule


module register_map #(
    parameter ADDR_WIDTH = 7,
    parameter DATA_WIDTH = 8,
    parameter NUM_CONFIG_REG = 12,
    parameter NUM_STATUS_REG = 4
) (
    input clk_i,
    input rst_ni,
    input [ADDR_WIDTH-1:0] addr_i,
    input [DATA_WIDTH-1:0] write_data_i,
    input write_en_i,
    output [DATA_WIDTH-1:0] read_data_o,
    input read_en_i,
    output [DATA_WIDTH*NUM_CONFIG_REG-1:0] config_bus_o,
    input [DATA_WIDTH*NUM_STATUS_REG-1:0] status_bus_i
);

    // packed to unpacked conversion
    reg [DATA_WIDTH-1:0] register_map_mem [NUM_CONFIG_REG-1:0];
    genvar i;
    generate 
        for (i = 0; i < NUM_CONFIG_REG; i = i + 1) begin
            assign config_bus_o[DATA_WIDTH*(i+1)-1: DATA_WIDTH*i] = register_map_mem[i];
        end
    endgenerate

    wire [DATA_WIDTH*(NUM_CONFIG_REG+NUM_STATUS_REG)-1:0] csr_read_bus;
    assign csr_read_bus = {status_bus_i, config_bus_o};
    
    wire [DATA_WIDTH-1:0] csr_read_arr [NUM_CONFIG_REG+NUM_STATUS_REG-1:0];
    generate
        for (i = 0; i < (NUM_CONFIG_REG + NUM_STATUS_REG); i = i + 1) begin
            assign csr_read_arr[i] = csr_read_bus[DATA_WIDTH*(i+1)-1:DATA_WIDTH*i];
        end
    endgenerate
    
    // synchronize to clk_i domain
    // write_data should be stable when write_en is asserted
    reg [3:0] write_en_shift_reg;
    always @(posedge clk_i) begin
        if (!rst_ni) begin
            write_en_shift_reg <= 'b0;
        end else begin
            write_en_shift_reg <= {write_en_shift_reg[2:0], write_en_i};
        end
    end

    wire write_en_rise_pulse;
    assign write_en_rise_pulse = !write_en_shift_reg[3] && write_en_shift_reg[2];
    
    reg [DATA_WIDTH-1:0] read_data_1, read_data_2;
    always @(posedge clk_i) begin
        if (!rst_ni) begin
            read_data_1 <= 'b0;
            read_data_2 <= 'b0;
        end else if (read_en_i == 1) begin
            if (addr_i < (NUM_CONFIG_REG + NUM_STATUS_REG)) begin
                read_data_1 <= csr_read_arr[addr_i];
                read_data_2 <= read_data_1;
            end
            else begin
                read_data_2 <= 8'hff;
            end
        end
    end
    assign read_data_o = read_data_2; 

    // attempt to non-zero init. can't make it work with skywater-pdk
    always @(posedge clk_i) begin
        if (!rst_ni) begin
            register_map_mem[0] <= 'b0;
        end else if ((addr_i == 0) && (addr_i < NUM_CONFIG_REG)) begin
            if (write_en_rise_pulse == 1) register_map_mem[0] <= write_data_i;
        end
    end
    
    generate 
        for (i = 1; i < NUM_CONFIG_REG; i = i + 1) begin
            always @(posedge clk_i) begin
                if (!rst_ni) begin
                    register_map_mem[i] <= 'b0;
                end else if ((addr_i == i) && (addr_i < NUM_CONFIG_REG)) begin
                    if (write_en_rise_pulse == 1) register_map_mem[i] <= write_data_i;
                end
            end
        end
    endgenerate

endmodule
    



module prbs15 (
    input clk_i,
    input rst_ni,
    input [14:0] lfsr_init_i,
    input load_prbs_i,
    input freeze_i,
    output prbs_o,
    output [14:0] prbs_frame_o
);
// referenced from, https://en.wikipedia.org/wiki/Pseudorandom_binary_sequence
//                  PRBS15 = x^15 + x^14 + 1

    reg [14:0] lfsr_reg;
    always @(posedge clk_i) begin
        if (!rst_ni) begin
            lfsr_reg <= 'b0;
        end else if (load_prbs_i == 1) begin
            lfsr_reg <= lfsr_init_i;
        end else if (freeze_i == 1) begin
            lfsr_reg <= lfsr_reg;
        end else begin
            lfsr_reg <= {lfsr_reg[13:0], lfsr_reg[14] ^ lfsr_reg[13]};
        end
    end
    
    assign prbs_o = lfsr_reg[14];
    assign prbs_frame_o = lfsr_reg;

endmodule


module prbs7 (
    input clk_i,
    input rst_ni,
    input [6:0] lfsr_init_i,
    input load_prbs_i,
    input freeze_i,
    output prbs_o,
    output [6:0] prbs_frame_o
);
// referenced from, https://en.wikipedia.org/wiki/Pseudorandom_binary_sequence
//                  PRBS7 = x^7 + x^6 + 1

    reg [6:0] lfsr_reg;
    always @(posedge clk_i) begin
        if (!rst_ni) begin
            lfsr_reg <= 'b0;
        end else if (load_prbs_i == 1) begin
            lfsr_reg <= lfsr_init_i;
        end else if (freeze_i == 1) begin
            lfsr_reg <= lfsr_reg;
        end else begin
            lfsr_reg <= {lfsr_reg[5:0], lfsr_reg[6] ^ lfsr_reg[5]};
        end
    end
    
    assign prbs_o = lfsr_reg[6];
    assign prbs_frame_o = lfsr_reg;

endmodule


module i2s_tx #(
        parameter AUDIO_DW = 8
        ) (
	input sck_i,
	input ws_i,
        output sd_o,
	input [AUDIO_DW-1:0] l_data_i,
	input [AUDIO_DW-1:0] r_data_i,
        output l_load_en_o,
        output r_load_en_o
);
// referenced from, i2s spec. at https://www.nxp.com/docs/en/user-manual/UM11732.pdf

    reg wsd[1:0];
    always @(posedge sck_i) begin
        wsd[0] <= ws_i;
        wsd[1] <= wsd[0];
    end
    assign l_load_en_o = wsd[1] & !wsd[0];
    assign r_load_en_o = !wsd[1] & wsd[0];
    
    wire wsp;
    assign wsp = wsd[0] ^ wsd[1];

    reg [AUDIO_DW-1:0] sd_shift_reg;
    wire [AUDIO_DW-1:0] l_data, r_data;
    assign l_data = !wsd[0] ? l_data_i : {AUDIO_DW{1'b0}};
    assign r_data = wsd[0] ? r_data_i : {AUDIO_DW{1'b0}};

    always @(negedge sck_i) begin
        if (wsp == 1) sd_shift_reg <= l_data | r_data;
        else sd_shift_reg <= {sd_shift_reg[AUDIO_DW-2:0], 1'b0};
    end
    assign sd_o = sd_shift_reg[AUDIO_DW-1];

endmodule


module ks_string #(
    parameter MAX_LENGTH = 256,
    parameter DATA_WIDTH = 8,
    parameter PRBS_WIDTH = 2,
    parameter EXTN_BITS = 4,
    parameter FRAC_BITS = 4
    ) (
    input clk_i,
    input rst_ni,
    input freeze_i,
    input round_en_i,
    input pluck_i,
    input toggle_pattern_prbs_ni, 
    input drum_string_ni,
    input fine_tune_en_i,
    input signed [DATA_WIDTH-1:0] fine_tune_C_i,
    input dynamics_en_i,
    input [DATA_WIDTH-1:0] dynamics_R_i,
    input clip_noise_i,
    input [PRBS_WIDTH-1:0] prbs_data_i,
    input [DATA_WIDTH-1:0] period_i,
    output [DATA_WIDTH-1:0] ks_sample_o
);

// referenced from, papers at   https://doi.org/10.2307/3680062
//                              https://doi.org/10.2307/3680063

localparam EXTENDED_WIDTH = DATA_WIDTH+EXTN_BITS;

wire [DATA_WIDTH-1:0] clamped_period = period_i < MAX_LENGTH ? period_i : MAX_LENGTH;
wire [DATA_WIDTH-1:0] period_idx;
assign period_idx = clamped_period - 8'h01;

// pluck sync and detect
reg [3:0] pluck_shift_reg;
always @(posedge clk_i) begin
    if (!rst_ni) begin
        pluck_shift_reg <= 'b0;
    end else begin
        pluck_shift_reg <= {pluck_shift_reg[2:0], pluck_i};
    end
end

wire pluck_rise_pulse;
assign pluck_rise_pulse = !pluck_shift_reg[3] && pluck_shift_reg[2];

// noise burst capture
reg signed [EXTENDED_WIDTH+FRAC_BITS-1:0] noise_reg;
reg [$clog2(MAX_LENGTH)-1:0] prbs_burst_counter;
reg prbs_burst_en;

reg [DATA_WIDTH-1:0] noise_sample_reg;
always @(posedge clk_i) begin
    if (!rst_ni) noise_sample_reg <= 'b0;
    else noise_sample_reg <=  {noise_sample_reg[DATA_WIDTH-2:0], ^prbs_data_i};
end

// peak-peak clipped noise
wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] clip_noise_sample;
assign clip_noise_sample = (^prbs_data_i) ? ({{(EXTN_BITS+1){1'b0}}, {((DATA_WIDTH+FRAC_BITS)-1){1'b1}}}) 
                                                            : ({{(EXTN_BITS+1){1'b1}}, {((DATA_WIDTH+FRAC_BITS)-1){1'b0}}});

wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] noise_sample;
assign noise_sample = clip_noise_i ? clip_noise_sample : {{EXTN_BITS{noise_sample_reg[DATA_WIDTH-1]}}, noise_sample_reg, {FRAC_BITS{1'b0}}};

// noise dynamics filter
wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] yd_0;
assign yd_0 = noise_sample_dyn;
reg signed [EXTENDED_WIDTH+FRAC_BITS-1:0] yd_1;

always @(posedge clk_i) begin
    if (!rst_ni) yd_1 <= 'b0;
    else yd_1 <= yd_0;
end

wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] R_diff, noise_sample_dyn;
wire signed [DATA_WIDTH+EXTENDED_WIDTH+FRAC_BITS-1:0] scaled_R_diff;
assign R_diff = yd_1 - noise_sample;
assign scaled_R_diff = (dynamics_R_i * R_diff) >>> DATA_WIDTH;
assign noise_sample_dyn = noise_sample + scaled_R_diff;

wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] noise_sample_w;
assign noise_sample_w = dynamics_en_i ? noise_sample_dyn : noise_sample;

reg toggle_bit;
always @(posedge clk_i) begin
    if (!rst_ni) toggle_bit <= 'b0;
    else toggle_bit <= ~toggle_bit;
end

wire [EXTENDED_WIDTH+FRAC_BITS-1:0] toggle_clamp;
assign toggle_clamp = {{EXTN_BITS{toggle_bit}}, {toggle_bit, {DATA_WIDTH-1{~toggle_bit}}}, {FRAC_BITS{~toggle_bit}}};

wire [EXTENDED_WIDTH+FRAC_BITS-1:0] noise_mux_sample;
assign noise_mux_sample = toggle_pattern_prbs_ni ? toggle_clamp : noise_sample_w;

always @(posedge clk_i) begin
    if (!rst_ni) begin
        prbs_burst_counter <= 'b0;
        noise_reg <= 'b0;
        prbs_burst_en <= 'b0;
    end else begin
        if (pluck_rise_pulse == 1'b1) begin
            prbs_burst_counter <= 'b0;
            noise_reg <= noise_mux_sample;
            prbs_burst_en <= 'b1;
        end else if (prbs_burst_en == 1'b1) begin
            if (prbs_burst_counter < clamped_period) begin
                prbs_burst_counter <= prbs_burst_counter + 1;
                noise_reg <= noise_mux_sample;
                prbs_burst_en <= 1'b1;
            end else begin
                prbs_burst_counter <= 'b0;
                noise_reg <= 'b0;
                prbs_burst_en <= 1'b0;
            end
        end else begin
            prbs_burst_counter <= 'b0;
            noise_reg <= 'b0;
            prbs_burst_en <= 'b0;
        end
    end
end

// filter taps
wire signed [DATA_WIDTH-1:0] x_p;
assign x_p = string_reg[period_idx];
wire signed [DATA_WIDTH-1:0] x_p_1;
assign x_p_1 = delay_reg;

// Strong filter
wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] strong_string_avg;
assign strong_string_avg = (((x_p <<< FRAC_BITS) + (x_p_1 <<< FRAC_BITS)) >>> 1);

wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] strong_drum_avg;
assign strong_drum_avg = prbs_data_i[0] ? strong_string_avg : -strong_string_avg;

wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] strong_filter_w;
assign strong_filter_w = drum_string_ni ? strong_drum_avg : strong_string_avg; 

wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] ks_sample_w;
assign ks_sample_w = noise_reg + strong_filter_w + (round_en_i ? (1 << (FRAC_BITS-1)) : 0);                        

reg signed [EXTENDED_WIDTH+FRAC_BITS-1:0] strong_filter_1;
always @(posedge clk_i) begin
    if (!rst_ni) strong_filter_1 <= 'b0;
    else strong_filter_1 <= strong_filter_w;
end

// Fine Tune
reg signed [EXTENDED_WIDTH+FRAC_BITS-1:0] y_1;
wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] y_0, C_diff;
wire signed [DATA_WIDTH+EXTENDED_WIDTH+FRAC_BITS-1:0] scaled_C_diff;

assign C_diff = strong_filter_w - y_1;
assign scaled_C_diff = ((fine_tune_C_i * C_diff) >>> (DATA_WIDTH-1));
assign y_0 = strong_filter_1 + scaled_C_diff;

always @(posedge clk_i) begin
    if (!rst_ni) y_1 <= 'b0;
    else y_1 <= y_0;
end

wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] ks_sample_ft_w;
assign ks_sample_ft_w = noise_reg + y_0 + (round_en_i ? (1 << (FRAC_BITS-1)) : 0);

wire [EXTENDED_WIDTH+FRAC_BITS-1:0] ks_loop_sample_w;
assign ks_loop_sample_w = fine_tune_en_i ? ks_sample_ft_w : ks_sample_w;

// overflow detect
wire ks_sign_bit;
assign ks_sign_bit = ks_loop_sample_w[EXTENDED_WIDTH+FRAC_BITS-1];
wire ks_data_msb;
assign ks_data_msb = ks_loop_sample_w[DATA_WIDTH+FRAC_BITS-1];

wire ks_ovf;
assign ks_ovf = ks_sign_bit ^ ks_data_msb;

wire [EXTENDED_WIDTH+FRAC_BITS-1:0] ks_clamped_val;
assign ks_clamped_val = {{EXTN_BITS{ks_sign_bit}}, {ks_sign_bit, {DATA_WIDTH-1{ks_data_msb}}}, {FRAC_BITS{ks_data_msb}}};

wire signed [EXTENDED_WIDTH+FRAC_BITS-1:0] ks_loop_clamped_w;
assign ks_loop_clamped_w = ks_ovf ? ks_clamped_val : ks_loop_sample_w;

wire [DATA_WIDTH-1:0] ks_loop_sample_o;
assign ks_loop_sample_o = ks_loop_clamped_w[DATA_WIDTH+FRAC_BITS-1:FRAC_BITS];

assign ks_sample_o = ks_loop_sample_o;

// wavetable
reg [DATA_WIDTH-1:0] string_reg [MAX_LENGTH-1:0];
// reg [MAX_LENGTH-1:0] [DATA_WIDTH-1:0] string_reg;

always @(posedge clk_i) begin
    if (!rst_ni) begin
        string_reg[0] <= 'b0;
    end else if (freeze_i) begin
        string_reg[0] <= string_reg[0];
    end else begin
        string_reg[0] <= ks_loop_sample_o;
    end
end

genvar i;
generate 
    for (i = 1; i < MAX_LENGTH; i = i + 1) begin
        always @(posedge clk_i) begin
            if (!rst_ni) begin
                string_reg[i] <= 'b0;
            end else if (freeze_i) begin
                string_reg[i] <= string_reg[i];
            end else begin
                string_reg[i] <= string_reg[i-1];
            end
        end
    end
endgenerate

reg [DATA_WIDTH-1:0] delay_reg;
always @(posedge clk_i) begin
    if (!rst_ni) begin
        delay_reg <= 'b0;
    end else begin
        delay_reg <= string_reg[period_idx]; 
    end
end

endmodule


Reference solution:
rtl/generated.v:     // SPI param.
    localparam SPI_INST_WIDTH = 1;
    localparam SPI_ADDR_WIDTH = 7;
    localparam SPI_DATA_WIDTH = 8;
    localparam SPI_NUM_CONFIG_REG = 8;
    localparam SPI_NUM_STATUS_REG = 2;

    // I2S param.
    localparam I2S_AUDIO_DW = 8;

    // KS param.
    localparam KS_MAX_LENGTH = 32;
    localparam KS_DATA_WIDTH = 8;
    localparam KS_PRBS_WIDTH = 2;
    localparam KS_EXTN_BITS = 4;
    localparam KS_FRAC_BITS = 4;

    // clock dividers
    reg [3:0] clk_rise_counter;
    wire clk_r16;
    always @(posedge clk) begin
        if (!rst_n) clk_rise_counter <= 'b0;
        else clk_rise_counter <= clk_rise_counter + 1;
    end
    assign clk_r16 = clk_rise_counter[3];

    // SPI register map
    wire sck_i;
    assign sck_i = uio_in[0];
    wire sdi_i;
    assign sdi_i = uio_in[1];
    wire sdo_o;
    assign uio_out[2] = sdo_o;
    wire cs_ni;
    assign cs_ni = uio_in[3];
    
    // i2s tx
    wire i2s_sck_o;
    assign uio_out[4] = i2s_sck_o;
    wire i2s_ws_o;
    assign uio_out[5] = i2s_ws_o;
    wire i2s_sd_o;
    assign uio_out[6] = i2s_sd_o;

    assign uio_out[0] = 1'b0;
    assign uio_out[1] = 1'b0;
    assign uio_out[3] = 1'b0;
    
    assign uio_oe = 8'b1111_0100;
    assign uo_out[0] = rst_n;
    assign uo_out[1] = rst_n_prbs_15;
    assign uo_out[2] = rst_n_prbs_7;
    assign uo_out[3] = rst_n_ks_string;
    assign uo_out[4] = freeze_prbs_7;
    assign uo_out[5] = freeze_prbs_15;
    assign uo_out[6] = i2s_noise_sel;
    assign uo_out[7] = pluck;

    // prbs tx
    assign uio_out[7] = prbs_15;
    
    // register map, packed to unpacked
    wire [SPI_DATA_WIDTH*SPI_NUM_CONFIG_REG-1:0] config_bus_o;
    wire [SPI_DATA_WIDTH*SPI_NUM_STATUS_REG-1:0] status_bus_i;

    wire [SPI_DATA_WIDTH-1:0] config_arr [SPI_NUM_CONFIG_REG-1:0];
    genvar i;
    generate
        for (i = 0; i < SPI_NUM_CONFIG_REG; i = i + 1) begin
            assign config_arr[i] = config_bus_o[SPI_DATA_WIDTH*(i+1)-1:SPI_DATA_WIDTH*i];
        end
    endgenerate
    
    wire [SPI_DATA_WIDTH-1:0] status_arr [SPI_NUM_CONFIG_REG-1:0];
    generate
        for (i = 0; i < SPI_NUM_STATUS_REG; i = i + 1) begin
            assign status_bus_i[SPI_DATA_WIDTH*(i+1)-1:SPI_DATA_WIDTH*i] = status_arr[i];
        end
    endgenerate

    assign status_arr[0] = 8'hC0;
    assign status_arr[1] = 8'h01;
    
    // SPI peripheral
    wire [SPI_ADDR_WIDTH-1:0] spi_addr;
    wire [SPI_DATA_WIDTH-1:0] spi_write_data, spi_read_data;
    wire spi_write_en, spi_read_en;

    spi_slave_mem_interface #(.INST_WIDTH(SPI_INST_WIDTH),
                .ADDR_WIDTH(SPI_ADDR_WIDTH),
                .DATA_WIDTH(SPI_DATA_WIDTH)
    ) spi_slave_mem_interface_0 (
        .sck_i(sck_i),
        .sdi_i(sdi_i),
        .sdo_o(sdo_o),
        .cs_ni(cs_ni && rst_n),
        .addr_o(spi_addr),
        .write_data_o(spi_write_data),
        .write_en_o(spi_write_en),
        .read_data_i(spi_read_data),
        .read_en_o(spi_read_en)
    );

    // Register map
    register_map #( .ADDR_WIDTH(SPI_ADDR_WIDTH),
                    .DATA_WIDTH(SPI_DATA_WIDTH),
                    .NUM_CONFIG_REG(SPI_NUM_CONFIG_REG),
                    .NUM_STATUS_REG(SPI_NUM_STATUS_REG)
    ) register_map_0 (
        .clk_i(clk),
        .rst_ni(rst_n),
        .addr_i(spi_addr),
        .write_data_i(spi_write_data),
        .write_en_i(spi_write_en),
        .read_data_o(spi_read_data),
        .read_en_i(spi_read_en),
        .config_bus_o(config_bus_o),
        .status_bus_i(status_bus_i)
    );
    
    // prbs15
    wire rst_n_prbs_15;
    assign rst_n_prbs_15 = ~config_arr[0][0] && ~ui_in[0];
    wire [14:0] lfsr_init_15;
    assign lfsr_init_15 = {~config_arr[2][6:0], ~config_arr[1][7:0]};
    wire load_prbs_15;
    assign load_prbs_15 = config_arr[2][7] || ui_in[1];
    wire freeze_prbs_15;
    assign freeze_prbs_15 = config_arr[0][4] || ui_in[2];
    wire prbs_15;
    wire [14:0] prbs_frame_15;

    prbs15 prbs15_0 (
        .clk_i(clk_r16),
        .rst_ni(rst_n && rst_n_prbs_15),
        .lfsr_init_i(lfsr_init_15),
        .load_prbs_i(load_prbs_15),
        .freeze_i(freeze_prbs_15),
        .prbs_o(prbs_15),
        .prbs_frame_o(prbs_frame_15)
    );

    // prbs7
    wire rst_n_prbs_7;
    assign rst_n_prbs_7 = ~config_arr[0][1] && ~ui_in[0];
    wire [6:0] lfsr_init_7;
    assign lfsr_init_7 = config_arr[3][6:0];
    wire load_prbs_7;
    assign load_prbs_7 = config_arr[3][7] || ui_in[1];
    wire freeze_prbs_7;
    assign freeze_prbs_7 = config_arr[0][5] || ui_in[3];
    wire prbs_7;
    wire [6:0] prbs_frame_7;

    prbs7 prbs7_0 (
        .clk_i(clk_r16),
        .rst_ni(rst_n && rst_n_prbs_7),
        .lfsr_init_i(lfsr_init_7),
        .load_prbs_i(load_prbs_7),
        .freeze_i(freeze_prbs_7),
        .prbs_o(prbs_7),
        .prbs_frame_o(prbs_frame_7)
    );
    
    // i2s sample select
    wire i2s_noise_sel = config_arr[0][7] || ui_in[4];
    wire [I2S_AUDIO_DW-1:0] l_data, r_data; 
    assign l_data = i2s_noise_sel ? prbs_frame_15[I2S_AUDIO_DW-1:0] : ks_sample;
    assign r_data = i2s_noise_sel ? {1'b0, prbs_frame_7} : ks_sample;

    reg [I2S_AUDIO_DW-1:0] l_data_reg, r_data_reg; 
    wire l_load_en, r_load_en;
    
    always @(negedge i2s_sck_o) begin
        if (l_load_en == 1) l_data_reg <= l_data;
        else l_data_reg <= l_data_reg;
        if (r_load_en == 1) r_data_reg <= r_data;
        else r_data_reg <= r_data_reg;
    end

    // i2s tx
    assign i2s_sck_o = ~clk;
    assign i2s_ws_o = clk_r16;

    i2s_tx #(
        .AUDIO_DW(I2S_AUDIO_DW)
    ) i2s_tx_0 (
        .sck_i(i2s_sck_o),
        .ws_i(i2s_ws_o),
        .sd_o(i2s_sd_o),
        .l_data_i(l_data_reg),
        .r_data_i(r_data_reg),
        .l_load_en_o(l_load_en),
        .r_load_en_o(r_load_en)
    );

    // ks string
    wire ks_clk;
    assign ks_clk = clk_r16;
    wire rst_n_ks_string;
    assign rst_n_ks_string = ~config_arr[0][2] && ~ui_in[5];
    wire ks_freeze;
    assign ks_freeze = config_arr[0][7];
    wire pluck;
    assign pluck = config_arr[4][0] || ui_in[6];
    wire round_en;
    assign round_en = config_arr[4][1];
    wire toggle_pattern_prbs_n;
    assign toggle_pattern_prbs_n = config_arr[4][2];
    wire drum_string_n;
    assign drum_string_n = config_arr[4][3];
    wire fine_tune_en;
    assign fine_tune_en = config_arr[4][4];
    wire dynamics_en;
    assign dynamics_en = config_arr[4][5];
    wire [KS_DATA_WIDTH-1:0] fine_tune_C;
    assign fine_tune_C = config_arr[5];
    wire [KS_DATA_WIDTH-1:0] dynamics_R;
    assign dynamics_R = config_arr[6];
    wire clip_noise;
    assign clip_noise = config_arr[4][6];
    wire [KS_PRBS_WIDTH-1:0] prbs_data;
    assign prbs_data = {prbs_15, prbs_7};
    wire [KS_DATA_WIDTH-1:0] ks_period;
    assign ks_period = ~config_arr[7];

    wire [KS_DATA_WIDTH-1:0] ks_sample;

    ks_string #(
        .MAX_LENGTH(KS_MAX_LENGTH),
        .DATA_WIDTH(KS_DATA_WIDTH),
        .PRBS_WIDTH(KS_PRBS_WIDTH),
        .EXTN_BITS(KS_EXTN_BITS),
        .FRAC_BITS(KS_FRAC_BITS)
    ) ks_string_0 (
        .clk_i(ks_clk),
        .rst_ni(rst_n && rst_n_ks_string),
        .freeze_i(ks_freeze),
        .pluck_i(pluck),
        .round_en_i(round_en),
        .toggle_pattern_prbs_ni(toggle_pattern_prbs_n), 
        .drum_string_ni(drum_string_n),
        .fine_tune_en_i(fine_tune_en),
        .fine_tune_C_i(fine_tune_C),
        .dynamics_en_i(dynamics_en),
        .dynamics_R_i(dynamics_R),
        .clip_noise_i(clip_noise),
        .prbs_data_i(prbs_data),
        .period_i(ks_period),
        .ks_sample_o(ks_sample)
    );