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.

module button_debounce( 
  input wire clk,
  input wire rst_n,
  input wire countdown_en,
  input wire button_in,
  output wire button_out
);

  reg [3:0] counter;

  always @(posedge clk) begin
      if      (~rst_n)                       counter <= 0;
      else if (button_in)                    counter <= 15;
      else if (countdown_en && counter != 0) counter <= counter - 1;
  end

  assign button_out = (counter != 0);
    
endmodule


/*
 * Copyright (c) 2025 UW ASIC
 * SPDX-License-Identifier: Apache-2.0
 */

`default_nettype none 
 
module tt_um_uwasic_dinogame #(parameter CONV = 2) (
    input  wire [7:0] ui_in,    // Dedicated inputs
    output wire [7:0] uo_out,   // Dedicated outputs
    input  wire [7:0] uio_in,   // IOs: Input path
    output wire [7:0] uio_out,  // IOs: Output path
    output wire [7:0] uio_oe,   // IOs: Enable path (active high: 0=input, 1=output)
    input  wire       ena,      // always 1 when the design is powered, so you can ignore it
    input  wire       clk,      // clock
    input  wire       rst_n     // reset_n - low to reset
);

    wire       game_tick_60hz;
    wire [1:0] game_tick_20hz; // two consecutive pulses generated ([0] and then [1]), enabling pipelining

    // GAME STATE SIGNALS
    wire crash; // set to 1'b1 by rendering when collision occurs
    wire [5:0] player_position;
    wire game_start_pulse;
    wire game_over_pulse;
    wire game_frozen;
    wire jump_pulse;
    wire [2:0] game_state;

    wire [9:CONV] obstacle1_pos;
    wire [9:CONV] obstacle2_pos;
    wire [2:0] obstacle1_type;
    wire [2:0] obstacle2_type;

    wire [9:CONV] bg_object_pos;

    wire [7:0] rng;

    lfsr #(.NUM_BITS(15)) lfsr_inst (
        .clk(clk),
        .enable(ena),
        .lfsr_data(rng)
    );

    // Gamepad Pmod support
    wire gamepad_pmod_latch = ui_in[4];
    wire gamepad_pmod_clk = ui_in[5];
    wire gamepad_pmod_data = ui_in[6];
    wire gamepad_is_present;  // HIGH when gamepad is connected
    wire gamepad_up;
    wire gamepad_down;
    wire gamepad_start;   // Can leverage start, select from SNES
    wire gamepad_b;
    wire gamepad_y;
    wire gamepad_select;
    wire gamepad_left;
    wire gamepad_right;
    wire gamepad_a;
    wire gamepad_x;
    wire gamepad_l;
    wire gamepad_r;

    wire button_start;
    wire button_up;
    wire button_down;

    // Synchronizes pmod_data, pmod_clk, pmod_latch signals to system clock
    // domain.
    gamepad_pmod_single gamepad_pmod (
        // Inputs:
        .clk(clk),
        .rst_n(rst_n),
        .pmod_latch(gamepad_pmod_latch),
        .pmod_clk(gamepad_pmod_clk),
        .pmod_data(gamepad_pmod_data),

        // Outputs:
        .is_present(gamepad_is_present),
        .up(gamepad_up),
        .down(gamepad_down),
        .start(gamepad_start),
        .b(gamepad_b),
        .y(gamepad_y),
        .select(gamepad_select),
        .left(gamepad_left),
        .right(gamepad_right),
        .a(gamepad_a),
        .x(gamepad_x),
        .l(gamepad_l),
        .r(gamepad_r)
    );

    player_controller player_constroller_inst (
        .clk(clk),
        .rst_n(rst_n),
        .game_tick(game_tick_20hz),
        .button_start(button_start),
        .button_up(button_up),
        .button_down(button_down),
        .crash(crash),
        .player_position(player_position),
        .game_frozen(game_frozen),
        .game_start_pulse(game_start_pulse),
        .game_over_pulse(game_over_pulse),
        .jump_pulse(jump_pulse),
        .game_state(game_state)
    );

    obstacles #(.GEN_LINE(71), .CONV(CONV)) obstacles_inst (
        .clk(clk),
        .rst_n(rst_n),
        .game_frozen(game_frozen),
        .game_start(game_start_pulse),
        .game_tick(game_tick_60hz),
        .rng(rng),
        .obstacle1_pos(obstacle1_pos),
        .obstacle2_pos(obstacle2_pos),
        .obstacle1_type(obstacle1_type),
        .obstacle2_type(obstacle2_type)
    );

    bg_object #(.CONV(CONV)) bg_object_inst (
        .clk(clk),
        .rst_n(rst_n),
        .game_tick(game_tick_20hz[0]),
        .rng(rng),
        .bg_object_pos(bg_object_pos)
    );

    // VGA signals
    wire hsync;
    wire vsync;
    wire [1:0] R;
    wire [1:0] G;
    wire [1:0] B;
  
    // graphics/rendering signals
    wire [9:CONV] hpos;
    wire [9:CONV] vpos;
    wire color_dino;
    wire color_obs_1;
    wire color_obs_2;
    wire color_bg_object;
    wire color_bg_line;
    wire obs_color_1;
    wire obs_color_2;
    wire bg_object_color;
    wire dino_color;
    wire score_color_1;
    wire score_color_2;
    wire score_color_3;
    wire score_color_4;
    wire score_color_5;
    wire [5:0] dino_rom_counter;
    wire [7:0] obs_rom_counter_1;
    wire [7:0] obs_rom_counter_2;
    wire [5:0] bg_objects_rom_counter;
 
    dino_rom dino_rom_inst (.clk(clk), .rst(~rst_n), .i_rom_counter(dino_rom_counter), .i_player_state(game_state), .o_sprite_color(dino_color));
    obs_rom obs_rom_inst_1 (.clk(clk), .rst(~rst_n), .i_rom_counter(obs_rom_counter_1), .i_obs_type(obstacle1_type), .o_sprite_color(obs_color_1));
    obs_rom obs_rom_inst_2 (.clk(clk), .rst(~rst_n), .i_rom_counter(obs_rom_counter_2), .i_obs_type(obstacle2_type), .o_sprite_color(obs_color_2));
    bg_object_rom bg_object_rom_inst (.clk(clk), .rst(~rst_n), .i_rom_counter(bg_objects_rom_counter), .o_sprite_color(bg_object_color));
    wire [19:0] score;

    score_render #(.CONV(CONV), .OFFSET(140)) score_inst_1 (
        .clk(clk),
        .rst(~rst_n),
        .num(score[3:0]),
        .i_hpos(hpos),
        .i_vpos(vpos),
        .o_score_color(score_color_1)
    );

    score_render #(.CONV(CONV), .OFFSET(133)) score_inst_2 (
        .clk(clk),
        .rst(~rst_n),
        .num(score[7:4]),
        .i_hpos(hpos),
        .i_vpos(vpos),
        .o_score_color(score_color_2)
    );

    score_render #(.CONV(CONV), .OFFSET(126)) score_inst_3 (
        .clk(clk),
        .rst(~rst_n),
        .num(score[11:8]),
        .i_hpos(hpos),
        .i_vpos(vpos),
        .o_score_color(score_color_3)
    );

    score_render #(.CONV(CONV), .OFFSET(119)) score_inst_4 (
        .clk(clk),
        .rst(~rst_n),
        .num(score[15:12]),
        .i_hpos(hpos),
        .i_vpos(vpos),
        .o_score_color(score_color_4)
    );

    score_render #(.CONV(CONV), .OFFSET(112)) score_inst_5 (
        .clk(clk),
        .rst(~rst_n),
        .num(score[19:16]),
        .i_hpos(hpos),
        .i_vpos(vpos),
        .o_score_color(score_color_5)
    );

    dino_render #(.CONV(CONV)) dino_inst  (
        .clk(clk),
        .rst(~rst_n),
        .i_hpos(hpos),
        .i_vpos(vpos),
        .o_color_dino(color_dino),
        .o_rom_counter(dino_rom_counter),
        .i_sprite_color(dino_color),
        .i_ypos(player_position)
    );
    obs_render #(.CONV(CONV)) obs_inst_1  (
        .clk(clk),
        .rst(~rst_n),
        .i_hpos(hpos),
        .i_vpos(vpos),
        .o_color_obs(color_obs_1),
        .o_rom_counter(obs_rom_counter_1),
        .i_sprite_color(obs_color_1),
        .i_xpos(obstacle1_pos)
    );

    obs_render #(.CONV(CONV)) obs_inst_2  (
        .clk(clk),
        .rst(~rst_n),
        .i_hpos(hpos),
        .i_vpos(vpos),
        .o_color_obs(color_obs_2),
        .o_rom_counter(obs_rom_counter_2),
        .i_sprite_color(obs_color_2),
        .i_xpos(obstacle2_pos)
    );
    
    bg_render #(.CONV(CONV)) bg_render_inst  (
        .clk(clk),
        .rst(~rst_n),
        .i_hpos(hpos),
        .i_vpos(vpos),
        .o_color_bg(color_bg_object),
        .o_rom_counter(bg_objects_rom_counter),
        .i_sprite_color(bg_object_color),
        .i_xpos(bg_object_pos)
    );
    bg_line #(.CONV(CONV), .GND_LINE(59)) bg_line_inst (
        .i_vpos(vpos),
        .o_color_bg(color_bg_line)
    );
    graphics_top #(.CONV(CONV)) graphics_inst  (
        .clk(clk),
        .rst(~rst_n),
        .o_hsync(hsync),
        .o_vsync(vsync), 
        .o_blue(B),
        .o_green(G),
        .o_red(R), 
        .i_color_background(color_bg_object | color_bg_line),
        .i_color_obstacle(color_obs_1 | color_obs_2),
        .i_color_player(color_dino),
        .i_color_score(score_color_1 | score_color_2 | score_color_3 | score_color_4 | score_color_5),
        .i_game_start_pulse(game_start_pulse),
        .o_hpos(hpos),
        .o_vpos(vpos),
        .i_rgb_scheme(score[14]),
        .i_invert(score[10]),
        .o_game_tick_60hz(game_tick_60hz),
        .o_game_tick_20hz(game_tick_20hz[0]),
        .o_game_tick_20hz_r(game_tick_20hz[1]),
        .o_collision(crash)
    );

    ScoreModule score_module_inst (
        .game_start(game_start_pulse),     
        .game_frozen(game_frozen),      
        .game_tick(game_tick_20hz[0]),     
        .clk(clk),            // clock
        .rst_n(rst_n),          // reset_n - low to reset
        .score(score)    
    );

    audio_interface audio_inst (
        .clk(clk),
        .rst_n(rst_n),
        .game_is_over(game_over_pulse),
        .jump_pulse(jump_pulse),
        .sound(uio_out[7])
    );

    ai_controller #(.CONV(CONV)) ai_controller_inst(
        .clk(clk),
        .rst_n(rst_n),
        .game_tick(game_tick_60hz),
        .gamepad_is_present(gamepad_is_present),
        .gamepad_start(gamepad_start),
        .gamepad_up(gamepad_up | gamepad_a),
        .gamepad_down(gamepad_down | gamepad_b),
        .obstacle1_pos(obstacle1_pos),
        .obstacle2_pos(obstacle2_pos),
        .crash(crash),
        .game_frozen(game_frozen),
        .button_start(button_start),
        .button_up(button_up),
        .button_down(button_down)
    );
  
    // TinyVGA PMOD
    assign uo_out = {hsync, B[0], G[0], R[0], vsync, B[1], G[1], R[1]};

    // All output pins must be assigned. If not used, assign to 0.
    assign uio_out[6:0] = 0;
    assign uio_oe  = 8'b10000000;

    // List all unused inputs to prevent warnings
    wire _unused = &{ena, ui_in[7], ui_in[3:0], uio_in, gamepad_start, gamepad_b, gamepad_y, gamepad_select, gamepad_left, gamepad_right, gamepad_a, gamepad_x, gamepad_l, gamepad_r, gamepad_is_present, 1'b0};

endmodule


`default_nettype none

module dino_render #(parameter CONV = 0) (
  input  wire       clk,      // clock
  input  wire       rst, 

  // Graphics
  input wire [9:CONV] i_hpos,
  input wire [9:CONV] i_vpos,
  output reg o_color_dino,   // Dedicated outputs

  // ROM
  output reg [5:0] o_rom_counter,
  input wire  i_sprite_color,

  // Player
  input wire [5:0] i_ypos
);

  reg [9:CONV] y_offset;
  reg [9:CONV] x_offset;
  reg in_sprite;

  reg [9:CONV] y_offset_r;
  reg [9:CONV] x_offset_r;
  always @(posedge clk) begin
  if (rst) begin 
    y_offset_r <= 0;
    x_offset_r <= 0;
  end else begin
      y_offset_r <= y_offset;
      x_offset_r <= x_offset;
  end 
  end 

  always @(*) begin
    y_offset = i_vpos - ({{(2){i_ypos[5]}}, i_ypos}) - 50;
    x_offset = i_hpos - 6;
    in_sprite = (x_offset_r < 8) && (y_offset_r < 8);
  end 

  // ROM addressing
  reg [2:0] rom_x;
  reg [2:0] rom_y;
  always @(*) begin
    rom_x = x_offset_r[CONV+2:CONV];
    rom_y = y_offset_r[CONV+2:CONV];
  end
  always @(*) begin
    o_rom_counter = {rom_y, rom_x};
  end

  always @(*) begin
    o_color_dino = 1'b0;
    // optimize this heavily for ROM
    if (in_sprite) begin
      o_color_dino = i_sprite_color;
    end
  end
endmodule



`default_nettype none

module dino_rom (
  input  wire       clk,      // clock
  input  wire       rst, 
  input wire [5:0] i_rom_counter,
  input wire [2:0] i_player_state,
  output reg  o_sprite_color
);

// localparam RESTART   = 3'b000;
localparam JUMPING   = 3'b001;
localparam RUNNING_1 = 3'b010;
localparam RUNNING_2 = 3'b011;
localparam DUCKING   = 3'b100;
// localparam GAME_OVER = 3'b101;

reg [2:0] rom_x;
reg [2:0] rom_y;
always @(*) begin
  {rom_y, rom_x} = i_rom_counter;
end
reg [7:0] icon [7:0];
reg [7:0] icon_run_1 [7:0];
reg [7:0] icon_run_2 [7:0];
reg [7:0] icon_duck [7:0];

always @(posedge clk) begin
  if (rst) begin
    icon[0] <= 8'b01110000;
    icon[1] <= 8'b11110000;
    icon[2] <= 8'b00110000;
    icon[3] <= 8'b00111001;
    icon[4] <= 8'b00111111;
    icon[5] <= 8'b00011110;
    icon[6] <= 8'b00010100;
    icon[7] <= 8'b00010100;
  end
end

always @(posedge clk) begin
  if (rst) begin
    icon_run_1[0] <= 8'b01110000;
    icon_run_1[1] <= 8'b11110000;
    icon_run_1[2] <= 8'b00110000;
    icon_run_1[3] <= 8'b00111001;
    icon_run_1[4] <= 8'b00111111;
    icon_run_1[5] <= 8'b00011110;
    icon_run_1[6] <= 8'b00010100;
    icon_run_1[7] <= 8'b00000100;
  end
end

always @(posedge clk) begin
  if (rst) begin
    icon_run_2[0] <= 8'b01110000;
    icon_run_2[1] <= 8'b11110000;
    icon_run_2[2] <= 8'b00110000;
    icon_run_2[3] <= 8'b00111001;
    icon_run_2[4] <= 8'b00111111;
    icon_run_2[5] <= 8'b00011110;
    icon_run_2[6] <= 8'b00010100;
    icon_run_2[7] <= 8'b00010000;
  end
end

always @(posedge clk) begin
  if (rst) begin
    icon_duck[0] <= 8'b00000000;
    icon_duck[1] <= 8'b00000000;
    icon_duck[2] <= 8'b00000000;
    icon_duck[3] <= 8'b01110000;
    icon_duck[4] <= 8'b11111001;
    icon_duck[5] <= 8'b00111111;
    icon_duck[6] <= 8'b00011110;
    icon_duck[7] <= 8'b00010100;
    
  end
end

always @(*) begin
  case (i_player_state)
        JUMPING: begin
          o_sprite_color = icon[rom_y][rom_x];
        end
        RUNNING_1: begin
          o_sprite_color = icon_run_1[rom_y][rom_x];
        end
        RUNNING_2: begin
          o_sprite_color = icon_run_2[rom_y][rom_x];
        end
        DUCKING: begin
          o_sprite_color = icon_duck[rom_y][rom_x];
        end
        default: o_sprite_color = icon[rom_y][rom_x];
  endcase
end

endmodule



`default_nettype none

module graphics_top #(parameter CONV = 0)(
  input wire clk,
  input wire rst,
  output reg o_hsync,
  output reg o_vsync,
  output reg [1:0] o_blue,
  output reg [1:0] o_red,
  output reg [1:0] o_green,
  output reg [9:CONV] o_hpos,
  output reg [9:CONV] o_vpos,
  input wire i_color_obstacle,
  input wire i_color_player,
  input wire i_color_background,
  input wire i_color_score,
  input wire i_game_start_pulse,
  
  input wire i_rgb_scheme,
  input wire i_invert,

  output reg o_game_tick_60hz,
  output reg o_game_tick_20hz,
  output reg o_game_tick_20hz_r,
  output reg o_collision
);
    // ============== HVSYNC =============
    // TODO can change hpos to increment by 2 to reduce bits
    wire [9:0] hpos;
    wire [9:0] vpos;
    wire display_on;
    // TODO can remove this pipeline stage if we don't need it
    wire hsync;
    wire vsync;
    reg hsync_r;
    reg vsync_r;
    reg hsync_r_r;
    reg vsync_r_r;
    reg display_on_r;
    reg display_on_r_r;

    // TODO create custom hsync
    hvsync_generator hvsync_gen (
        .clk(clk),
        .reset(rst),
        .hsync(hsync),
        .vsync(vsync), 
        .vpos(vpos),
        .hpos(hpos),
        .display_on(display_on)
    ); 


    always @(posedge clk) begin
        if (rst) begin
            hsync_r <= 1'b0;
            vsync_r <= 1'b0;
            vsync_r_r <= 1'b0;
            hsync_r_r <= 1'b0;
            display_on_r <= 1'b0;
            display_on_r_r <= 1'b0;
        end else begin
            vsync_r <= vsync;
            hsync_r <= hsync;
            vsync_r_r <= vsync_r;
            hsync_r_r <= hsync_r;
            display_on_r <= display_on;
            display_on_r_r <= display_on_r;
        end
    end

    always @(*) begin
        o_hsync = hsync_r_r;
        o_vsync = vsync_r_r;
    end

    always @(*) begin
        o_hpos = hpos[9:CONV];
        o_vpos = vpos[9:CONV];
    end

    // ============== COMPARE =============
    wire is_colored;
    wire O0;
    wire O1;
    priority_encoder_4_2 pe_4_2_inst (
        .I0(i_color_background),
        .I1(i_color_obstacle),
        .I2(i_color_player),
        .I3(i_color_score),
        .O0(O0),
        .O1(O1),
        .V(is_colored)
    );
    wire [1:0] R;
    wire [1:0] G;
    wire [1:0] B;
    
    color_decoder_2_6 color_decoder_inst (
        .is_colored(is_colored),
        .layer({O1, O0}),
        .rgb_scheme(i_rgb_scheme),
        .invert(i_invert),
        .R(R),
        .G(G),
        .B(B)
    );

    reg [1:0] R_r;
    reg [1:0] G_r;
    reg [1:0] B_r;
    
    always @(posedge clk) begin
        if (rst) begin
            R_r <= 0;
            G_r <= 0;
            B_r <= 0;
        end else begin
            R_r <= R;
            G_r <= G;
            B_r <= B;
        end
    end

    // ============ GENERATE RGB / TRANSFORM ============
    // TODO stage can be merged with "CONVOLUTION" stage
    always @(*) begin
        o_red  = 2'b00;
        o_green = 2'b00;
        o_blue = 2'b00;

        // DEBUG remove after
        if (~display_on_r_r) begin
            o_red = 2'b00;
            o_green = 2'b00;
            o_blue = 2'b00;
        end else begin
            o_red = R_r;
            o_green = G_r;
            o_blue = B_r;
        end 
        
    end
    
    // ============ Other outputs ============
    // TODO probably can merge game_tick_r logic with frame_counter
    reg [1:0] frame_counter;
    reg game_tick_r;
    reg collision;

    always @(*) begin
        o_game_tick_60hz = (vpos == 0) && (hpos == 0);
        o_game_tick_20hz = frame_counter == 1 && o_game_tick_60hz;
        o_game_tick_20hz_r = game_tick_r;
        o_collision = collision;
    end

    always @(posedge clk) begin
        if (rst) begin
            frame_counter <= 0; 
            game_tick_r <= 0;
            collision <= 0;
        end else begin
            if (o_game_tick_60hz) begin
                frame_counter <= frame_counter + 1; 
                if (frame_counter == 2) begin
                    frame_counter <= 0;
                end
            end
            game_tick_r <= o_game_tick_20hz;
            if (i_game_start_pulse) begin
                collision <= 1'b0;
            end else if (i_color_obstacle && i_color_player && display_on_r) begin
                collision <= 1'b1;
            end
        end
    end

endmodule



`ifndef HVSYNC_GENERATOR_H
`define HVSYNC_GENERATOR_H

/*
Video sync generator, used to drive a VGA monitor.
Timing from: https://en.wikipedia.org/wiki/Video_Graphics_Array
To use:
- Wire the hsync and vsync signals to top level outputs
- Add a 3-bit (or more) "rgb" output to the top level
*/

module hvsync_generator(
  input wire clk,
  input wire reset,
  output wire hsync, vsync,
  output wire display_on,
  output reg [9:0] hpos,
  output reg [9:0] vpos
);
  // declarations for TV-simulator sync parameters
  // horizontal constants
  parameter H_DISPLAY       = 640; // horizontal display width
  parameter H_BACK          =  48; // horizontal left border (back porch)
  parameter H_FRONT         =  16; // horizontal right border (front porch)
  parameter H_SYNC          =  96; // horizontal sync width
  // vertical constants
  parameter V_DISPLAY       = 480; // vertical display height
  parameter V_TOP           =  33; // vertical top border
  parameter V_BOTTOM        =  10; // vertical bottom border
  parameter V_SYNC          =   2; // vertical sync # lines
  // derived constants
  parameter H_SYNC_START    = H_DISPLAY + H_FRONT;
  parameter H_SYNC_END      = H_DISPLAY + H_FRONT + H_SYNC - 1;
  parameter H_MAX           = H_DISPLAY + H_BACK + H_FRONT + H_SYNC - 1;
  parameter V_SYNC_START    = V_DISPLAY + V_BOTTOM;
  parameter V_SYNC_END      = V_DISPLAY + V_BOTTOM + V_SYNC - 1;
  parameter V_MAX           = V_DISPLAY + V_TOP + V_BOTTOM + V_SYNC - 1;

  wire hmaxxed = (hpos == H_MAX) || reset;	// set when hpos is maximum
  wire vmaxxed = (vpos == V_MAX) || reset;	// set when vpos is maximum
  
  // horizontal position counter
  always @(posedge clk)
  begin
    if(hmaxxed)
      hpos <= 0;
    else
      hpos <= hpos + 1;
  end

  // vertical position counter
  always @(posedge clk)
  begin
    if(hmaxxed)
      if (vmaxxed)
        vpos <= 0;
      else
        vpos <= vpos + 1;
  end
  
  // display_on is set when beam is in "safe" visible frame
  assign display_on = (hpos<H_DISPLAY) && (vpos<V_DISPLAY);
  assign vsync = (vpos>=V_SYNC_START && vpos<=V_SYNC_END);
  assign hsync = (hpos>=H_SYNC_START && hpos<=H_SYNC_END);

endmodule

`endif


// See https://nandland.com/lfsr-linear-feedback-shift-register/
`default_nettype none

module lfsr
  #(    parameter NUM_BITS = 8)
(
  input wire clk,
  input wire enable,
  output wire [7:0] lfsr_data
);
  // reg [NUM_BITS:1] r_lfsr = {NUM_BITS/2{2'b01}}; // 010101 ...
  reg [NUM_BITS:1] r_lfsr = 15'b0101010101010101; // 010101 ...
  wire r_xnor;

  // Create Feedback Polynomials.  Based on Application Note:
  // http://www.xilinx.com/support/documentation/application_notes/xapp052.pdf
  generate
    case (NUM_BITS)
      3: begin : gen_poly assign r_xnor = r_lfsr[3] ^~ r_lfsr[2]; end
      4: begin : gen_poly assign r_xnor = r_lfsr[4] ^~ r_lfsr[3]; end
      5: begin : gen_poly assign r_xnor = r_lfsr[5] ^~ r_lfsr[3]; end
      6: begin : gen_poly assign r_xnor = r_lfsr[6] ^~ r_lfsr[5]; end
      7: begin : gen_poly assign r_xnor = r_lfsr[7] ^~ r_lfsr[6]; end
      8: begin : gen_poly assign r_xnor = ~(r_lfsr[8] ^ r_lfsr[6] ^ r_lfsr[5] ^ r_lfsr[4]); end
      9: begin : gen_poly assign r_xnor = r_lfsr[9] ^~ r_lfsr[5]; end
      10: begin : gen_poly assign r_xnor = r_lfsr[10] ^~ r_lfsr[7]; end
      11: begin : gen_poly assign r_xnor = r_lfsr[11] ^~ r_lfsr[9]; end
      12: begin : gen_poly assign r_xnor = ~(r_lfsr[12] ^ r_lfsr[6] ^ r_lfsr[4] ^ r_lfsr[1]); end
      13: begin : gen_poly assign r_xnor = ~(r_lfsr[13] ^ r_lfsr[4] ^ r_lfsr[3] ^ r_lfsr[1]); end
      14: begin : gen_poly assign r_xnor = ~(r_lfsr[14] ^ r_lfsr[5] ^ r_lfsr[3] ^ r_lfsr[1]); end
      15: begin : gen_poly assign r_xnor = r_lfsr[15] ^~ r_lfsr[14]; end
      16: begin : gen_poly assign r_xnor = ~(r_lfsr[16] ^ r_lfsr[15] ^ r_lfsr[13] ^ r_lfsr[4]); end
    endcase // case (NUM_BITS)
  endgenerate

  always @(posedge clk) begin
    if (enable == 1'b1)
      r_lfsr <= {r_lfsr[NUM_BITS-1:1], r_xnor};
    else
      r_lfsr <= 15'b0101010101010101;
  end

  assign lfsr_data = r_lfsr[8:1];
endmodule



`default_nettype none

module obs_render #(parameter CONV = 0) (
  input  wire       clk,      // clock
  input  wire       rst, 

  // Graphics
  input wire [9:CONV] i_hpos,
  input wire [9:CONV] i_vpos,
  output reg o_color_obs,   // Dedicated outputs

  // ROM
  output reg [7:0] o_rom_counter,
  input wire  i_sprite_color,

  // Obstacle
  input wire [9:CONV] i_xpos
);

  reg [9:CONV] y_offset;
  reg [9:CONV] x_offset;
  reg in_sprite;

  reg [9:CONV] y_offset_r;
  reg [9:CONV] x_offset_r;
  always @(posedge clk) begin
  if (rst) begin 
    y_offset_r <= 0;
    x_offset_r <= 0;
  end else begin
      y_offset_r <= y_offset;
      x_offset_r <= x_offset;
  end 
  end 

  always @(*) begin
    y_offset = i_vpos - 42;
    x_offset = i_hpos - i_xpos[9:CONV] + 16;
    in_sprite = (x_offset_r < 16) && (y_offset_r < 16);
  end 
  
  // ROM addressing
  reg [3:0] rom_x;
  reg [3:0] rom_y;
  always @(*) begin
    rom_x = x_offset_r[CONV+3:CONV];
    rom_y = y_offset_r[CONV+3:CONV];
    o_rom_counter = {rom_y, rom_x};
  end

  always @(*) begin
    o_color_obs = 1'b0;
    if (in_sprite) begin
      o_color_obs = i_sprite_color;
    end
  end
endmodule




`default_nettype none

module obs_rom (
  input  wire       clk,     
  input  wire       rst, 
  input wire [7:0] i_rom_counter,
  input wire [2:0] i_obs_type,
  output reg  o_sprite_color
);

localparam EMPTY        = 3'b000;
localparam CAC_3        = 3'b001;
localparam CAC_2        = 3'b010;
localparam CAC_THICK_1  = 3'b011; // actually the same as cac thick 2 but just repeating for probability purposes
localparam CAC_THICK_2  = 3'b100;
localparam CAC_THIN     = 3'b101;
localparam BIRD_LOW     = 3'b110;
localparam BIRD_HIGH    = 3'b111;

reg [3:0] rom_x;
reg [3:0] rom_y;


always @(*) begin
  {rom_y, rom_x} = i_rom_counter;
end

reg [15:0] icon_cac_3 [15:0];
reg [15:0] icon_cac_2 [15:0];
reg [15:0] icon_cac_thick_1 [15:0];
reg [15:0] icon_cac_thick_2 [15:0];
reg [15:0] icon_cac_thin [15:0];
reg [15:0] icon_bird_low [15:0];
reg [15:0] icon_bird_high [15:0];

always @(posedge clk) begin
  if (rst) begin
    icon_cac_3[0]  <= 16'b0000000000000000;
    icon_cac_3[1]  <= 16'b0000000000000000;
    icon_cac_3[2]  <= 16'b0000000000000000;
    icon_cac_3[3]  <= 16'b0000000000000000;
    icon_cac_3[4]  <= 16'b0000000000000000;
    icon_cac_3[5]  <= 16'b0000000000000000;
    icon_cac_3[6]  <= 16'b0000000000000000;
    icon_cac_3[7]  <= 16'b0000000110000000;
    icon_cac_3[8]  <= 16'b0001000110000100;
    icon_cac_3[9]  <= 16'b0001000110000100;
    icon_cac_3[10] <= 16'b0101010110010100;
    icon_cac_3[11] <= 16'b0101011110011101;
    icon_cac_3[12] <= 16'b0101000110100111;
    icon_cac_3[13] <= 16'b0011000111100100;
    icon_cac_3[14] <= 16'b0001000110000100;
    icon_cac_3[15] <= 16'b0001000110000100;
  end
end

always @(posedge clk) begin
  if (rst) begin
    icon_cac_2[0]  <= 16'b0000000000000000;
    icon_cac_2[1]  <= 16'b0000000000000000;
    icon_cac_2[2]  <= 16'b0000000000000000;
    icon_cac_2[3]  <= 16'b0000000000000000;
    icon_cac_2[4]  <= 16'b0000000000000000;
    icon_cac_2[5]  <= 16'b0000000000000000;
    icon_cac_2[6]  <= 16'b0000000000000000;
    icon_cac_2[7]  <= 16'b0000000000000000;
    icon_cac_2[8]  <= 16'b0000110000000000;
    icon_cac_2[9]  <= 16'b0000110000010000;
    icon_cac_2[10] <= 16'b0000110000010000;
    icon_cac_2[11] <= 16'b0010110000010100;
    icon_cac_2[12] <= 16'b0011110101011100;
    icon_cac_2[13] <= 16'b0000111101110000;
    icon_cac_2[14] <= 16'b0000110000010000;
    icon_cac_2[15] <= 16'b0000110000010000;
  end
end
always @(posedge clk) begin
  if (rst) begin
    icon_cac_thick_1[0]  <= 16'b0000000000000000;
    icon_cac_thick_1[1]  <= 16'b0000000000000000;
    icon_cac_thick_1[2]  <= 16'b0000000000000000;
    icon_cac_thick_1[3]  <= 16'b0000000000000000;
    icon_cac_thick_1[4]  <= 16'b0000000000000000;
    icon_cac_thick_1[5]  <= 16'b0000000000000000;
    icon_cac_thick_1[6]  <= 16'b0000000000000000;
    icon_cac_thick_1[7]  <= 16'b0000000110000000;
    icon_cac_thick_1[8]  <= 16'b0000001110000000;
    icon_cac_thick_1[9]  <= 16'b0000001110110000;
    icon_cac_thick_1[10] <= 16'b0000001110110000;
    icon_cac_thick_1[11] <= 16'b0001101111110000;
    icon_cac_thick_1[12] <= 16'b0001101110000000;
    icon_cac_thick_1[13] <= 16'b0001111110000000;
    icon_cac_thick_1[14] <= 16'b0000001110000000;
    icon_cac_thick_1[15] <= 16'b0000001110000000;
  end
end
always @(posedge clk) begin
  if (rst) begin
    icon_cac_thick_2[0]  <= 16'b0000000000000000;
    icon_cac_thick_2[1]  <= 16'b0000000000000000;
    icon_cac_thick_2[2]  <= 16'b0000000000000000;
    icon_cac_thick_2[3]  <= 16'b0000000000000000;
    icon_cac_thick_2[4]  <= 16'b0000000000000000;
    icon_cac_thick_2[5]  <= 16'b0000000000000000;
    icon_cac_thick_2[6]  <= 16'b0000000000000000;
    icon_cac_thick_2[7]  <= 16'b0000000000000000;
    icon_cac_thick_2[8]  <= 16'b0000000110000000;
    icon_cac_thick_2[9]  <= 16'b0000110110000000;
    icon_cac_thick_2[10] <= 16'b0000111110110000;
    icon_cac_thick_2[11] <= 16'b0000000110110000;
    icon_cac_thick_2[12] <= 16'b0000000111110000;
    icon_cac_thick_2[13] <= 16'b0000000110000000;
    icon_cac_thick_2[14] <= 16'b0000000110000000;
    icon_cac_thick_2[15] <= 16'b0000000110000000;
  end
end
always @(posedge clk) begin
  if (rst) begin
    icon_cac_thin[0]  <= 16'b0000000000000000;
    icon_cac_thin[1]  <= 16'b0000000000000000;
    icon_cac_thin[2]  <= 16'b0000000000000000;
    icon_cac_thin[3]  <= 16'b0000000000000000;
    icon_cac_thin[4]  <= 16'b0000000000000000;
    icon_cac_thin[5]  <= 16'b0000000000000000;
    icon_cac_thin[6]  <= 16'b0000000000000000;
    icon_cac_thin[7]  <= 16'b0000000000000000;
    icon_cac_thin[8]  <= 16'b0000000100000000;
    icon_cac_thin[9]  <= 16'b0000000100000000;
    icon_cac_thin[10] <= 16'b0000010100000000;
    icon_cac_thin[11] <= 16'b0000011100000000;
    icon_cac_thin[12] <= 16'b0000000101000000;
    icon_cac_thin[13] <= 16'b0000000111000000;
    icon_cac_thin[14] <= 16'b0000000100000000;
    icon_cac_thin[15] <= 16'b0000000100000000;
  end
end
always @(posedge clk) begin
  if (rst) begin
    icon_bird_low[0]  <= 16'b0000000000000000;
    icon_bird_low[1]  <= 16'b0000000000000000;
    icon_bird_low[2]  <= 16'b0000000000000000;
    icon_bird_low[3]  <= 16'b0000000000000000;
    icon_bird_low[4]  <= 16'b0000000000000000;
    icon_bird_low[5]  <= 16'b0010100011001100;
    icon_bird_low[6]  <= 16'b0001000100110010;
    icon_bird_low[7]  <= 16'b0000001000010001;
    icon_bird_low[8]  <= 16'b1010000000000000;
    icon_bird_low[9]  <= 16'b0100000000000000;
    icon_bird_low[10] <= 16'b0000000000000000;
    icon_bird_low[11] <= 16'b0000000000000000;
    icon_bird_low[12] <= 16'b0000000000000000;
    icon_bird_low[13] <= 16'b0000000000000000;
    icon_bird_low[14] <= 16'b0000000000000000;
    icon_bird_low[15] <= 16'b0000000000000000;
  end
end
always @(posedge clk) begin
  if (rst) begin
    icon_bird_high[0]  <= 16'b0000000000000000;
    icon_bird_high[1]  <= 16'b0000011001100000;
    icon_bird_high[2]  <= 16'b0000100110010000;
    icon_bird_high[3]  <= 16'b0001000010001000;
    icon_bird_high[4]  <= 16'b0000000000000000;
    icon_bird_high[5]  <= 16'b0000000000000000;
    icon_bird_high[6]  <= 16'b0000000000000000;
    icon_bird_high[7]  <= 16'b0000000000000000;
    icon_bird_high[8]  <= 16'b0000000000000000;
    icon_bird_high[9]  <= 16'b0000000000000000;
    icon_bird_high[10] <= 16'b0000000000000000;
    icon_bird_high[11] <= 16'b0000000000000000;
    icon_bird_high[12] <= 16'b0000000000000000;
    icon_bird_high[13] <= 16'b0000000000000000;
    icon_bird_high[14] <= 16'b0000000000000000;
    icon_bird_high[15] <= 16'b0000000000000000;
  end
end

always @(*) begin
  case (i_obs_type)
        EMPTY: begin
          o_sprite_color = 1'b0;
        end
        CAC_3: begin
          o_sprite_color = icon_cac_3[rom_y][rom_x];
        end
        CAC_2: begin
          o_sprite_color = icon_cac_2[rom_y][rom_x];
        end
        CAC_THICK_1: begin
          o_sprite_color = icon_cac_thick_1[rom_y][rom_x];
        end
        CAC_THICK_2: begin
          o_sprite_color = icon_cac_thick_2[rom_y][rom_x];
        end
        CAC_THIN: begin
          o_sprite_color = icon_cac_thin[rom_y][rom_x];
        end
        BIRD_LOW: begin
          o_sprite_color = icon_bird_low[rom_y][rom_x];
        end
        BIRD_HIGH: begin
          o_sprite_color = icon_bird_high[rom_y][rom_x];
        end
        default: o_sprite_color = 1'b0;
  endcase
end

endmodule



module obstacles
  #(    parameter CONV = 0, parameter GEN_LINE = 250)
(
    input wire clk,
    input wire rst_n,
    input wire game_frozen,
    input wire game_start,
    input wire game_tick,
    input wire [7:0] rng,
    output reg [9:CONV] obstacle1_pos,
    output reg [9:CONV] obstacle2_pos,
    output reg [2:0] obstacle1_type,
    output reg [2:0] obstacle2_type
);
    reg obstacle1_cross_gen_line_reg;
    reg obstacle2_cross_gen_line_reg;
    always @(posedge clk) begin
        if (!rst_n || game_start) begin
            obstacle1_pos <= 0;
            obstacle2_pos <= 0;
            obstacle1_type <= 0;
            obstacle2_type <= 0;
            obstacle1_cross_gen_line_reg <= 0;
            obstacle2_cross_gen_line_reg <= 1;
        end else begin
          if (!game_frozen && game_tick) begin
                if (obstacle1_pos != 0) obstacle1_pos <= obstacle1_pos - 1;
                if (obstacle2_pos != 0) obstacle2_pos <= obstacle2_pos - 1;
                
                if (obstacle1_pos == GEN_LINE) obstacle1_cross_gen_line_reg <= 1;
                if (obstacle2_pos == GEN_LINE) obstacle2_cross_gen_line_reg <= 1;
                
                if (obstacle1_pos == 0 && obstacle2_cross_gen_line_reg) begin
                    obstacle1_pos <= {2'b10, {(3-CONV){1'b1}}, rng[4:0]};
                    obstacle1_type <= rng[7:5];
                    obstacle2_cross_gen_line_reg <= 0;
                end
                if (obstacle2_pos== 0 && obstacle1_cross_gen_line_reg) begin
                    obstacle2_pos <= {2'b10, {(3-CONV){1'b1}}, rng[4:0]};
                    obstacle2_type <= rng[7:5];
                    obstacle1_cross_gen_line_reg <= 0;
                end
            end
        end
    end
endmodule


`default_nettype none

module player_controller (
  input wire clk,
  input wire rst_n,
  input wire [1:0] game_tick,
  input wire button_start,
  input wire button_up,
  input wire button_down,
  input wire crash,
  output wire [5:0] player_position,
  output wire game_frozen,
  output wire game_start_pulse,
  output wire game_over_pulse,
  output wire jump_pulse,
  output reg [2:0] game_state
);

  localparam RESTART   = 3'b000;
  localparam JUMPING   = 3'b001;
  localparam RUNNING1  = 3'b010;
  localparam RUNNING2  = 3'b011;
  localparam DUCKING   = 3'b100;
  localparam GAME_OVER = 3'b101;

  wire running;
  wire jump_done;
  wire physics_rst_n;
  wire game_over;

  always @(posedge(clk)) begin
    if (!rst_n) begin
      game_state <= RESTART;
    end else begin
      case (game_state)
        RESTART: begin
          if      (game_tick[0] &&  button_start) game_state <= JUMPING;
          else                                    game_state <= RESTART;
        end
        JUMPING: begin
          if      (crash                        ) game_state <= GAME_OVER;
          else if (game_tick[1] &&  jump_done   ) game_state <= RUNNING1;
          else                                    game_state <= JUMPING;
        end
        RUNNING1: begin
          if      (crash                        ) game_state <= GAME_OVER;
          else if (game_tick[0] &&  button_down ) game_state <= DUCKING;
          else if (game_tick[0] &&  button_up   ) game_state <= JUMPING;
          else if (game_tick[0]                 ) game_state <= RUNNING2;
          else                                    game_state <= RUNNING1;
        end
        RUNNING2: begin
          if      (crash                        ) game_state <= GAME_OVER;
          else if (game_tick[0] &&  button_down ) game_state <= DUCKING;
          else if (game_tick[0] &&  button_up   ) game_state <= JUMPING;
          else if (game_tick[0]                 ) game_state <= RUNNING1;
          else                                    game_state <= RUNNING2;
        end
        DUCKING: begin
          if      (crash                        ) game_state <= GAME_OVER;
          else if (game_tick[0] && !button_down ) game_state <= RUNNING1;
          else                                    game_state <= DUCKING;
        end
        GAME_OVER: begin
          if      (game_tick[0] &&  button_start) game_state <= RUNNING1;
          else                                    game_state <= GAME_OVER;
        end
        default: game_state <= RESTART;
      endcase
    end
  end

  assign physics_rst_n = rst_n && !(game_state == GAME_OVER && game_tick[0] && button_start);
  assign game_over = (game_state == GAME_OVER);

  player_physics u_player_physics (
    .clk(clk),
    .rst_n(physics_rst_n),
    .game_tick(game_tick),
    .game_over(game_over),
    .jump_pulse(jump_pulse),
    .button_down(button_down),
    .jump_done(jump_done),
    .position(player_position)
  );

  assign running = (game_state == RUNNING1 || game_state == RUNNING2);

  assign game_frozen      = (game_state == RESTART || game_state == GAME_OVER);
  assign game_start_pulse = (game_frozen             && game_tick[0] && button_start);
  assign game_over_pulse  = (game_state != GAME_OVER && game_tick[0] && crash    );
  assign jump_pulse       = (running                 && game_tick[0] && button_up);


endmodule


`default_nettype none

module player_physics #(
  // determined from pygame example
  parameter INITIAL_JUMP_VELOCITY = -7,
  parameter DOWNWARD_ACCELERATION =  1,
  parameter FASTDROP_VELOCITY     =  6
) (
  input wire clk,
  input wire rst_n,
  input wire [1:0] game_tick,     // enable for the FF that stores result of velocity [0] and position [1]
  input wire game_over,           // collision has occured -- freeze dino
  input wire jump_pulse,          // high for one clock cycle at start of jump (set initial velocity)
  input wire button_down,         // high if down button is pressed
  output reg [5:0] position, // -21..4
  output wire jump_done      // not(msb of adder) -- only sampled when game_tick[1] == 1
);

  reg  [3:0] velocity; // -7..6
  wire [3:0] active_vel;
  wire [3:0] adder_in1;
  wire [5:0] adder_in2, adder_res; // -21..4

  // If player presses down, override velocity calculated by gravity with FASTDROP_VELOCITY
  assign active_vel = (button_down) ? FASTDROP_VELOCITY : velocity;

  // game_tick[1] == 0 means calculating velocity, game_tick[1] == 1 means calculating position
  assign adder_in1 = (game_tick[1]) ? active_vel : DOWNWARD_ACCELERATION;
  assign adder_in2 = (game_tick[1]) ? position : { {2{velocity[3]}}, velocity };
  assign adder_res = { {2{velocity[3]}}, adder_in1 } + adder_in2;

  always @ (posedge clk) begin
    if (!rst_n) begin
      velocity <= 0;
      position <= 0; // Replace with ground position
    end else if (!game_over) begin
      if (game_tick[0]) begin
        if      (button_down) velocity <= 0;
        else if (jump_pulse)  velocity <= INITIAL_JUMP_VELOCITY;
        else if (position[5]) velocity <= adder_res[3:0];
      end else if (game_tick[1]) begin
        if (~adder_res[5]) begin
          velocity <= 0;
          position <= 0;
        end else begin
          position <= adder_res;
        end
      end
    end
  end

  // Only sampled when game_tick[1] == 1, so jump_done == 1 when calculated position overflows
  assign jump_done = ~adder_res[5];

endmodule


/*
 * Copyright (c) 2025 UW ASIC
 * SPDX-License-Identifier: Apache-2.0
 */

`default_nettype none

module ScoreModule (
    input  wire              game_start,     // pulse for starting the counter
    input  wire              game_frozen,    // If the game is frozen
    input  wire              game_tick,      // 60 Hz. end of frame pulse
    input  wire              clk,            // clock
    input  wire              rst_n,          // reset_n - low to reset
    output wire [19:0]       score
);

  // Internal registers to help with keeping track of the score in decimal
  reg [3:0] score_int [4:0];

  // determine if game_active
  always @(posedge clk) begin
    if (!rst_n || game_start) begin
      score_int[0] <= 0;
      score_int[1] <= 0;
      score_int[2] <= 0;
      score_int[3] <= 0;
      score_int[4] <= 0;
    end else begin
      
      if (!game_frozen && game_tick) begin
        if (score_int[0] == 9) begin
          score_int[0] <= 0;
          if (score_int[1] == 9) begin
            score_int[1] <= 0;
            if (score_int[2] == 9) begin
              score_int[2] <= 0;
              if (score_int[3] == 9) begin
                score_int[3] <= 0;
                if (score_int[4] == 9) begin
                  // Reset the game if the score gets to 99999
                  score_int[4] <= 0;
                end else begin
                  score_int[4] <= score_int[4] + 1;
                end
              end else begin
                score_int[3] <= score_int[3] + 1;
              end
            end else begin
              score_int[2] <= score_int[2] + 1;
            end
          end else begin
            score_int[1] <= score_int[1] + 1;
          end
        end else begin
          score_int[0] <= score_int[0] + 1;
        end
      end
      
    end
  end
  
  assign score = {score_int[4], score_int[3], score_int[2], score_int[1], score_int[0]};

endmodule

`default_nettype none

module score_render #(parameter CONV = 0, parameter OFFSET = 0, parameter OFFSET_Y = 7) (
  input  wire       clk,     
  input  wire       rst, 
  input wire [3:0] num,
  input wire [9:CONV] i_hpos,
  input wire [9:CONV] i_vpos,
  output reg  o_score_color
);

reg [9:CONV] y_offset;
reg [9:CONV] x_offset;
reg [9:CONV] y_offset_r;
reg [9:CONV] x_offset_r;
reg [3:0] num_r;
reg in_sprite;
reg [6:0] segment;
always @(posedge clk) begin
  if (rst) begin
    x_offset_r <= {(9-CONV+1){1'b0}};
    y_offset_r <= {(9-CONV+1){1'b0}};
    num_r <= 4'd0;
  end else begin
    num_r <= num;
    x_offset_r <= x_offset;
    y_offset_r <= y_offset;
  end
end

always @(*) begin
  y_offset = i_vpos - OFFSET_Y;
  x_offset = i_hpos - OFFSET;

  in_sprite = (x_offset_r < 4) && (y_offset_r < 7);
  
  segment[0] = y_offset_r == 0 && (num_r == 0 || num_r == 2 || num_r == 3 || num_r == 5 || num_r == 6 || num_r == 7 || num_r == 8 || num_r == 9);
  segment[1] = y_offset_r < 3 && x_offset_r == 0 && (num_r == 0 || num_r == 4 || num_r == 5 || num_r == 6 || num_r == 8 || num_r == 9);
  segment[2] = y_offset_r < 3 && x_offset_r == 3 && (num_r == 0 || num_r == 1 || num_r == 2 || num_r == 3 || num_r == 4 || num_r == 7|| num_r == 8 || num_r == 9);
  segment[3] = y_offset_r == 3 && (num_r == 2 || num_r == 3 || num_r == 4 || num_r == 5 || num_r == 6 || num_r == 8 || num_r == 9);
  segment[4] = y_offset_r > 3  && x_offset_r == 0 && (num_r == 0 || num_r == 2 || num_r == 6 || num_r == 8);
  segment[5] = y_offset_r > 3 && x_offset_r == 3 && (num_r == 0 || num_r == 1 || num_r == 3 || num_r == 4 || num_r == 5 || num_r == 6 || num_r == 7 || num_r == 8 || num_r == 9);
  segment[6] = y_offset_r == 6 && (num_r == 0 || num_r == 2 || num_r == 3 || num_r == 5 || num_r == 6 || num_r == 8);
end 

always @(*) begin
  o_score_color = |segment && in_sprite;
end

endmodule






module audio_interface(
    input wire clk,
    input wire rst_n,
    input wire game_is_over,
    input wire jump_pulse,
    output reg sound
);
    wire jump_sound;
    wire game_over_sound;

    always @(posedge clk) begin
        if (!rst_n) begin
            sound <= 0;
        end else begin
            sound <= jump_sound | game_over_sound;
        end
    end

    jump_sound_player i_jump(
        .clk(clk),
        .rst_n(rst_n),
        .sound_trigger(jump_pulse),
        .wave_out(jump_sound)
    );

    game_over_sound_player i_over(
        .clk(clk),
        .rst_n(rst_n),
        .is_over(game_is_over),
        .wave_out(game_over_sound)
    );

endmodule




module jump_sound_player (
    input wire clk,             // 50 MHz clock
    input wire rst_n,          // Enable this module
    input wire sound_trigger,   // One-shot pulse signal to generate sound
    output reg wave_out         // Square wave output (registered)
);

    localparam [18:0] PWM_ARR_PERIOD = 19'd333333;

    // Decay stages
    localparam [18:0] DECAY_0   = 19'd166666;
    localparam [18:0] DECAY_1  = 19'd140522;
    localparam [18:0] DECAY_2  = 19'd118300;
    localparam [18:0] DECAY_3  = 19'd99999;
    localparam [18:0] DECAY_4  = 19'd84313;
    localparam [18:0] DECAY_5  = 19'd71241;
    localparam [18:0] DECAY_6  = 19'd60130;
    localparam [18:0] DECAY_7  = 19'd50326;
    localparam [18:0] DECAY_8  = 19'd42483;
    localparam [18:0] DECAY_9  = 19'd35947;
    localparam [18:0] DECAY_10 = 19'd30065;
    localparam [18:0] DECAY_11 = 19'd25490;
    localparam [18:0] DECAY_12 = 19'd21568;
    localparam [18:0] DECAY_13 = 19'd18300;
    localparam [18:0] DECAY_14 = 19'd15032;
    localparam [18:0] DECAY_15 = 19'd13071;
    localparam [18:0] DECAY_16 = 19'd10457;
    localparam [18:0] DECAY_17 = 19'd9150;
    localparam [18:0] DECAY_18 = 19'd7843;
    localparam [18:0] DECAY_19 = 19'd6535;
    localparam [18:0] DECAY_20 = 19'd5228;
    localparam [18:0] DECAY_21 = 19'd4575;
    localparam [18:0] DECAY_22 = 19'd3921;
    localparam [18:0] DECAY_23 = 19'd3267;
    localparam [18:0] DECAY_24 = 19'd2614;
    localparam [18:0] DECAY_25 = 19'd1960;
    localparam [18:0] DECAY_26 = 19'd1960;
    localparam [18:0] DECAY_27 = 19'd1307;
    localparam [18:0] DECAY_28 = 19'd1307;
    localparam [18:0] DECAY_29 = 19'd25;
    localparam [18:0] DECAY_30 = 19'd25;

    // State machine states
    localparam [1:0] IDLE  = 2'b00,
                 PLAY   = 2'b01,
                 DONE   = 2'b10;
    reg [1:0] state = IDLE;
    reg [1:0] next_state = IDLE;
    
    reg [4:0]  stage_index = 0;  // Decay stage index
    reg [18:0] counter     = 0;  // PWM period counter
    reg        active      = 0;  // Active flag
    reg [18:0] decay_value;

    // State Register
    always @(posedge clk) begin
        if (!rst_n)
            state <= IDLE;
        else
            state <= next_state;
    end

    always@(*) begin
        case (state)
            IDLE: begin
                if (active)
                    next_state = PLAY;
                else
                    next_state = IDLE;
            end

            PLAY: begin
                if (stage_index== 30 )
                    next_state = DONE;
                else
                    next_state = PLAY;
            end

            DONE: begin
                next_state = IDLE;
            end

            default: next_state = IDLE;
        endcase
    end

    // State Machine
    always @(posedge clk) begin
        if (!rst_n) begin
            active      <= 0;
            stage_index <= 0;
            wave_out    <= 0;
            wave_out    <= 0;
        end else if (sound_trigger) begin
            active      <= 1;
            counter     <= 0;
            stage_index <= 0;
            wave_out    <= 0;
        end else begin
            case (state)
                IDLE: begin
                    stage_index <= 0;
                    counter     <= 0;
                    wave_out    <= 0;
                end
                
                PLAY: begin
                    if (counter >= decay_value) begin
                        wave_out <= 0;  // Toggle waveform
                    end

                    if (counter >= PWM_ARR_PERIOD) begin  // Once we complete a full cycle
                            wave_out    <= 1;
                            counter     <= 0;
                            stage_index <= stage_index + 1;
                    end
                    else begin
                        counter <= counter + 1;
                    end

                    case (stage_index)
                        5'd0:  decay_value <= DECAY_0;
                        5'd1:  decay_value <= DECAY_1;
                        5'd2:  decay_value <= DECAY_2;
                        5'd3:  decay_value <= DECAY_3;
                        5'd4:  decay_value <= DECAY_4;
                        5'd5:  decay_value <= DECAY_5;
                        5'd6:  decay_value <= DECAY_6;
                        5'd7:  decay_value <= DECAY_7;
                        5'd8:  decay_value <= DECAY_8;
                        5'd9:  decay_value <= DECAY_9;
                        5'd10: decay_value <= DECAY_10;
                        5'd11: decay_value <= DECAY_11;
                        5'd12: decay_value <= DECAY_12;
                        5'd13: decay_value <= DECAY_13;
                        5'd14: decay_value <= DECAY_14;
                        5'd15: decay_value <= DECAY_15;
                        5'd16: decay_value <= DECAY_16;
                        5'd17: decay_value <= DECAY_17;
                        5'd18: decay_value <= DECAY_18;
                        5'd19: decay_value <= DECAY_19;
                        5'd20: decay_value <= DECAY_20;
                        5'd21: decay_value <= DECAY_21;
                        5'd22: decay_value <= DECAY_22;
                        5'd23: decay_value <= DECAY_23;
                        5'd24: decay_value <= DECAY_24;
                        5'd25: decay_value <= DECAY_25;
                        5'd26: decay_value <= DECAY_26;
                        5'd27: decay_value <= DECAY_27;
                        5'd28: decay_value <= DECAY_28;
                        5'd29: decay_value <= DECAY_29;
                        5'd30: decay_value <= DECAY_30;
                        default: decay_value <= 19'd25;
                    endcase
                end
                
                DONE: begin
                    wave_out <= 0;
                    active   <= 0;
                end
                
                default: begin
                    active <= 0;
                    counter     <= 0;
                    stage_index <= 0;
                    wave_out <= 0;
                end
            endcase
        end
    end
endmodule


module game_over_sound_player (
    input wire clk,        // 50 MHz clock input
    input wire rst_n,      // reset on the negative edge
    input wire is_over,     // one period pulse to trigger the game over sound
    output reg wave_out    // Jump sound square wave output
);
// >>> Module Implementation Begin

// <<< Module Implementation End
endmodule




module bg_object
  #(    parameter CONV = 0)
(
    input wire clk,
    input wire rst_n,
    input  wire game_tick,      // 20 Hz
    input wire [7:0] rng,
    output reg [9:CONV] bg_object_pos
);
    always @(posedge clk) begin
        if (!rst_n) begin
            bg_object_pos <= 0;
        end else begin
            if (game_tick) begin
                if (bg_object_pos != 0) bg_object_pos <= bg_object_pos - 1;
                if (bg_object_pos == 0) begin
                    bg_object_pos <= {{(5-CONV){1'b1}}, rng[6:2]};
                end
            end
        end
    end
endmodule

`default_nettype none

module bg_object_rom (
  input  wire       clk,     
  input  wire       rst, 
  input wire [5:0] i_rom_counter,
  output reg  o_sprite_color
);

reg [2:0] rom_x;
reg [2:0] rom_y;


always @(*) begin
  {rom_y, rom_x} = i_rom_counter;
end

reg [7:0] icon_cloud [7:0];

always @(posedge clk) begin
  if (rst) begin
    icon_cloud[0]  <= 8'b00000000;
    icon_cloud[1]  <= 8'b00000000;
    icon_cloud[2]  <= 8'b00001100;
    icon_cloud[3]  <= 8'b00011110;
    icon_cloud[4]  <= 8'b01101011;
    icon_cloud[5]  <= 8'b11000001;
    icon_cloud[6]  <= 8'b11111111;
    icon_cloud[7]  <= 8'b01100110;
  end
end

always @(*) begin
  o_sprite_color = icon_cloud[rom_y][rom_x];
end

endmodule



`default_nettype none

module bg_render #(parameter CONV = 0) (
  input  wire       clk,      // clock
  input  wire       rst, 

  // Graphics
  input wire [9:CONV] i_hpos,
  input wire [9:CONV] i_vpos,
  output reg o_color_bg,   // Dedicated outputs

  // ROM
  output reg [5:0] o_rom_counter,
  input wire  i_sprite_color,

  // Bg
  input wire [9:CONV] i_xpos
);

  reg [9:CONV] y_offset;
  reg [9:CONV] x_offset;
  reg in_sprite;

  reg [9:CONV] y_offset_r;
  reg [9:CONV] x_offset_r;
  always @(posedge clk) begin
  if (rst) begin 
    y_offset_r <= 0;
    x_offset_r <= 0;
  end else begin
      y_offset_r <= y_offset;
      x_offset_r <= x_offset;
  end 
  end 

  always @(*) begin
    y_offset = i_vpos - 15;
    x_offset = i_hpos - i_xpos[9:CONV] + 8;
    in_sprite = (x_offset_r < 8) && (y_offset_r < 8);
  end 

  // ROM addressing
  reg [2:0] rom_x;
  reg [2:0] rom_y;
  always @(*) begin
    rom_x = x_offset_r[CONV+2:CONV];
    rom_y = y_offset_r[CONV+2:CONV];
  end
  always @(*) begin
    o_rom_counter = {rom_y, rom_x};
  end

  always @(*) begin
    o_color_bg = 1'b0;
    // optimize this heavily for ROM
    if (in_sprite) begin
      o_color_bg = i_sprite_color;
    end
  end
endmodule




module bg_line #(parameter CONV = 0, parameter GND_LINE = 0) (
  // Graphics
  input wire [9:CONV] i_vpos,
  output reg o_color_bg   // Dedicated outputs
);


  always @(*) begin
    o_color_bg = 1'b0;
    // optimize this heavily for ROM
    if (i_vpos == GND_LINE) begin
      o_color_bg = 1'b1;
    end
  end
endmodule




module priority_encoder_4_2 (
    input wire I0,
    input wire I1,
    input wire I2,
    input wire I3,
    output reg O0,
    output reg O1,
    output reg V
);
  always @(*) begin
    V = I0 || I1 || I2 || I3;
    O0 = ((~I2) && I1) || I3;
    O1 = I2 || I3;
  end
endmodule




module color_decoder_2_6 (
    input wire is_colored,
    input wire [1:0] layer,
    input wire rgb_scheme,
    input wire invert,
    output reg [1:0] R,
    output reg [1:0] G,
    output reg [1:0] B
);
  always @(*) begin
    case (layer)
      2'b00: begin
        R = 2'b01;
        G = 2'b10;
        B = 2'b10;
      end
      2'b01: begin
        R = 2'b00;
        G = 2'b11;
        B = 2'b00;
      end
      2'b10: begin
        R = 2'b10;
        G = 2'b10;
        B = 2'b00;
      end
      2'b11: begin
        R = 2'b00;
        G = 2'b00;
        B = 2'b00;
      end
      default: begin
        R = 2'b00;
        G = 2'b00;
        B = 2'b00;
      end
    endcase
    if (~rgb_scheme) begin
      R = 2'b00;
      G = 2'b00;
      B = 2'b00;
    end
    if (~is_colored) begin
      R = 2'b11;
      G = 2'b11;
      B = 2'b11;
    end
    if (invert) begin
      R = ~R;
      G = ~G;
      B = ~B;
    end
  end
endmodule




`default_nettype none

module ai_controller
    #(    parameter CONV = 0, parameter PLAYER_OFFSET = 6, parameter OBSTACLE_TRESHOLD = 40 )
(
  input wire clk,
  input wire rst_n,
  input wire game_tick,
  input wire gamepad_is_present,
  input wire gamepad_start,
  input wire gamepad_up,
  input wire gamepad_down,
  input wire [9:CONV] obstacle1_pos,
  input wire [9:CONV] obstacle2_pos,
  input wire crash,
  input wire game_frozen,
  output reg button_start,
  output reg button_up,
  output reg button_down
);

localparam RESTART_DELAY = 60; // Clock cycles to wait after crash to restart

// reg [9:CONV] obstacle_threshold; // When an obstacle reaches this xpos, set button_up signal
reg [7:0] restart_counter; 

always @(posedge clk) begin
  if (!rst_n) begin
    button_start <= 1'b0;
    button_up <= 1'b0;
    button_down <= 1'b0;
    restart_counter <= 8'b00000000;
  end else if (gamepad_is_present) begin
    button_start <= gamepad_start;
    button_up <= gamepad_up;
    button_down <= gamepad_down;
  end else if (game_tick) begin
      if (crash | game_frozen) begin
      restart_counter <= restart_counter + 1;
      if (restart_counter == RESTART_DELAY) begin
        button_start <= 1'b1;
        restart_counter <= 8'b00000000;
      end else begin
        button_start <= 1'b0;
      end
      button_up <= 1'b0;
      button_down <= 1'b0;
    end else if ((obstacle1_pos <= OBSTACLE_TRESHOLD && obstacle1_pos > PLAYER_OFFSET) || (obstacle2_pos <= OBSTACLE_TRESHOLD && obstacle2_pos > PLAYER_OFFSET)) begin
      button_up <= 1'b1;
    end else begin
      button_up <= 1'b0;
    end
  end
end



endmodule


// Vendored from: https://github.com/psychogenic/gamepad-pmod/blob/main/verilog/gamepad_pmod.v

/*
 * Copyright (c) 2025 Pat Deegan
 * https://psychogenic.com
 * SPDX-License-Identifier: Apache-2.0
 *
 * Interfacing code for the Gamepad Pmod from Psycogenic Technologies,
 * designed for Tiny Tapeout.
 *
 * There are two high-level modules that most users will be interested in:
 * - gamepad_pmod_single: for a single controller;
 * - gamepad_pmod_dual: for two controllers.
 * 
 * There are also two lower-level modules that you can use if you want to
 * handle the interfacing yourself:
 * - gamepad_pmod_driver: interfaces with the Pmod and provides the raw data;
 * - gamepad_pmod_decoder: decodes the raw data into button states.
 *
 * The docs, schematics, PCB files, and firmware code for the Gamepad Pmod
 * are available at https://github.com/psychogenic/gamepad-pmod.
 */

/**
 * gamepad_pmod_driver -- Serial interface for the Gamepad Pmod.
 *
 * This module reads raw data from the Gamepad Pmod *serially*
 * and stores it in a shift register. When the latch signal is received, 
 * the data is transferred into `data_reg` for further processing.
 *
 * Functionality:
 *   - Synchronizes the `pmod_data`, `pmod_clk`, and `pmod_latch` signals 
 *     to the system clock domain.
 *   - Captures serial data on each falling edge of `pmod_clk`.
 *   - Transfers the shifted data into `data_reg` when `pmod_latch` goes low.
 *
 * Parameters:
 *   - `BIT_WIDTH`: Defines the width of `data_reg` (default: 24 bits).
 *
 * Inputs:
 *   - `rst_n`: Active-low reset.
 *   - `clk`: System clock.
 *   - `pmod_data`: Serial data input from the Pmod.
 *   - `pmod_clk`: Serial clock from the Pmod.
 *   - `pmod_latch`: Latch signal indicating the end of data transmission.
 *
 * Outputs:
 *   - `data_reg`: Captured parallel data after shifting is complete.
 */
module gamepad_pmod_driver #(
    parameter BIT_WIDTH = 24
) (
    input wire rst_n,
    input wire clk,
    input wire pmod_data,
    input wire pmod_clk,
    input wire pmod_latch,
    output reg [BIT_WIDTH-1:0] data_reg
);

  reg pmod_clk_prev;
  reg pmod_latch_prev;
  reg [BIT_WIDTH-1:0] shift_reg;

  // Sync Pmod signals to the clk domain:
  reg [1:0] pmod_data_sync;
  reg [1:0] pmod_clk_sync;
  reg [1:0] pmod_latch_sync;

  always @(posedge clk) begin
    if (~rst_n) begin
      pmod_data_sync  <= 2'b0;
      pmod_clk_sync   <= 2'b0;
      pmod_latch_sync <= 2'b0;
    end else begin
      pmod_data_sync  <= {pmod_data_sync[0], pmod_data};
      pmod_clk_sync   <= {pmod_clk_sync[0], pmod_clk};
      pmod_latch_sync <= {pmod_latch_sync[0], pmod_latch};
    end
  end

  always @(posedge clk) begin
    if (~rst_n) begin
      /* set data and shift registers to all ones
       * such that it is detected as "not present" yet.
       */
      data_reg <= {BIT_WIDTH{1'b1}};
      shift_reg <= {BIT_WIDTH{1'b1}};
      pmod_clk_prev <= 1'b0;
      pmod_latch_prev <= 1'b0;
    end
    begin
      pmod_clk_prev   <= pmod_clk_sync[1];
      pmod_latch_prev <= pmod_latch_sync[1];

      // Capture data on rising edge of pmod_latch:
      if (pmod_latch_sync[1] & ~pmod_latch_prev) begin
        data_reg <= shift_reg;
      end

      // Sample data on rising edge of pmod_clk:
      if (pmod_clk_sync[1] & ~pmod_clk_prev) begin
        shift_reg <= {shift_reg[BIT_WIDTH-2:0], pmod_data_sync[1]};
      end
    end
  end

endmodule


/**
 * gamepad_pmod_decoder -- Decodes raw data from the Gamepad Pmod.
 *
 * This module takes a 12-bit parallel data register (`data_reg`) 
 * and decodes it into individual button states. It also determines
 * whether a controller is connected.
 *
 * Functionality:
 *   - If `data_reg` contains all `1's` (`0xFFF`), it indicates that no controller is connected.
 *   - Otherwise, it extracts individual button states from `data_reg`.
 *
 * Inputs:
 *   - `data_reg [11:0]`: Captured button state data from the gamepad.
 *
 * Outputs:
 *   - `b, y, select, start, up, down, left, right, a, x, l, r`: Individual button states (`1` = pressed, `0` = released).
 *   - `is_present`: Indicates whether a controller is connected (`1` = connected, `0` = not connected).
 */
module gamepad_pmod_decoder (
    input wire [11:0] data_reg,
    output wire b,
    output wire y,
    output wire select,
    output wire start,
    output wire up,
    output wire down,
    output wire left,
    output wire right,
    output wire a,
    output wire x,
    output wire l,
    output wire r,
    output wire is_present
);

  wire _unused = &{b, y, select, left, right, a, x, l, r};

  // When the controller is not connected, the data register will be all 1's
  wire reg_empty = (data_reg == 12'hfff);
  assign is_present = reg_empty ? 0 : 1'b1;
  assign {b, y, select, start, up, down, left, right, a, x, l, r} = reg_empty ? 0 : data_reg;

endmodule


/**
 * gamepad_pmod_single -- Main interface for a single Gamepad Pmod controller.
 * 
 * This module provides button states for a **single controller**, reducing 
 * resource usage (fewer flip-flops) compared to a dual-controller version.
 * 
 * Inputs:
 *   - `pmod_data`, `pmod_clk`, and `pmod_latch` are the signals from the PMOD interface.
 * 
 * Outputs:
 *   - Each button's state is provided as a single-bit wire (e.g., `start`, `up`, etc.).
 *   - `is_present` indicates whether the controller is connected (`1` = connected, `0` = not detected).
 */
module gamepad_pmod_single (
    input wire rst_n,
    input wire clk,
    input wire pmod_data,
    input wire pmod_clk,
    input wire pmod_latch,

    output wire b,
    output wire y,
    output wire select,
    output wire start,
    output wire up,
    output wire down,
    output wire left,
    output wire right,
    output wire a,
    output wire x,
    output wire l,
    output wire r,
    output wire is_present
);

  wire [11:0] gamepad_pmod_data;

  gamepad_pmod_driver #(
      .BIT_WIDTH(12)
  ) driver (
      .rst_n(rst_n),
      .clk(clk),
      .pmod_data(pmod_data),
      .pmod_clk(pmod_clk),
      .pmod_latch(pmod_latch),
      .data_reg(gamepad_pmod_data)
  );

  gamepad_pmod_decoder decoder (
      .data_reg(gamepad_pmod_data),
      .b(b),
      .y(y),
      .select(select),
      .start(start),
      .up(up),
      .down(down),
      .left(left),
      .right(right),
      .a(a),
      .x(x),
      .l(l),
      .r(r),
      .is_present(is_present)
  );

endmodule


/**
 * gamepad_pmod_dual -- Main interface for the Pmod gamepad.
 * This module provides button states for two controllers using
 * 2-bit vectors for each button (e.g., start[1:0], up[1:0], etc.).
 * 
 * Each button state is represented as a 2-bit vector:
 *   - Index 0 corresponds to the first controller (e.g., up[0], y[0], etc.).
 *   - Index 1 corresponds to the second controller (e.g., up[1], y[1], etc.).
 *
 * The `is_present` signal indicates whether a controller is connected:
 *   - `is_present[0] == 1` when the first controller is connected.
 *   - `is_present[1] == 1` when the second controller is connected.
 *
 * Inputs:
 *   - `pmod_data`, `pmod_clk`, and `pmod_latch` are the 3 wires coming from the Pmod interface.
 *
 * Outputs:
 *   - Button state vectors for each controller.
 *   - Presence detection via `is_present`.
 */
module gamepad_pmod_dual (
    input wire rst_n,
    input wire clk,
    input wire pmod_data,
    input wire pmod_clk,
    input wire pmod_latch,

    output wire [1:0] b,
    output wire [1:0] y,
    output wire [1:0] select,
    output wire [1:0] start,
    output wire [1:0] up,
    output wire [1:0] down,
    output wire [1:0] left,
    output wire [1:0] right,
    output wire [1:0] a,
    output wire [1:0] x,
    output wire [1:0] l,
    output wire [1:0] r,
    output wire [1:0] is_present
);

  wire [23:0] gamepad_pmod_data;

  gamepad_pmod_driver driver (
      .rst_n(rst_n),
      .clk(clk),
      .pmod_data(pmod_data),
      .pmod_clk(pmod_clk),
      .pmod_latch(pmod_latch),
      .data_reg(gamepad_pmod_data)
  );

  gamepad_pmod_decoder decoder1 (
      .data_reg(gamepad_pmod_data[11:0]),
      .b(b[0]),
      .y(y[0]),
      .select(select[0]),
      .start(start[0]),
      .up(up[0]),
      .down(down[0]),
      .left(left[0]),
      .right(right[0]),
      .a(a[0]),
      .x(x[0]),
      .l(l[0]),
      .r(r[0]),
      .is_present(is_present[0])
  );

  gamepad_pmod_decoder decoder2 (
      .data_reg(gamepad_pmod_data[23:12]),
      .b(b[1]),
      .y(y[1]),
      .select(select[1]),
      .start(start[1]),
      .up(up[1]),
      .down(down[1]),
      .left(left[1]),
      .right(right[1]),
      .a(a[1]),
      .x(x[1]),
      .l(l[1]),
      .r(r[1]),
      .is_present(is_present[1])
  );

endmodule


Reference solution:
rtl/generated.v: 
    localparam [18:0] PERIOD = 19'd200000;   // Clock speed / Frequency -> 50MHz / 660Hz 

    // Distinct localparams for decay values
    localparam [18:0] DECAY_0  = 37878;
    localparam [18:0] DECAY_1  = 33842;
    localparam [18:0] DECAY_2  = 30208;
    localparam [18:0] DECAY_3  = 26940;
    localparam [18:0] DECAY_4  = 24008;
    localparam [18:0] DECAY_5  = 21383;
    localparam [18:0] DECAY_6  = 19043;
    localparam [18:0] DECAY_7  = 16966;
    localparam [18:0] DECAY_8  = 15135;
    localparam [18:0] DECAY_9  = 13530;
    localparam [18:0] DECAY_10 = 12132;
    localparam [18:0] DECAY_11 = 10924;
    localparam [18:0] DECAY_12 = 9879;
    localparam [18:0] DECAY_13 = 8932;
    localparam [18:0] DECAY_14 = 8076;
    localparam [18:0] DECAY_15 = 7300;

    localparam [1:0] IDLE  = 2'b00,
                    PLAY1 = 2'b01,
                    PLAY2 = 2'b10,
                    DONE  = 2'b11;

    reg [1:0] state = IDLE;
    reg [1:0] next_state = IDLE;

    reg [4:0] stage_index = 0;   // 16 stages of decay values
    reg [18:0] counter = 0;   // 19-bit counter for a maximum period of 333333
    reg active;
    reg [18:0] decay_value;

    always@(posedge clk) begin
        if (!rst_n) begin
            state <= IDLE;
        end else begin
            state <= next_state;
        end
    end

    always@(*) begin
        case (state)
            IDLE: begin
                if (active)
                    next_state = PLAY1;
                else
                    next_state = IDLE;
            end

            PLAY1: begin
                if (stage_index == 15) begin
                    next_state = PLAY2;
                end else
                    next_state = PLAY1;
            end

            PLAY2: begin
                if (stage_index == 31)
                    next_state = DONE;
                else
                    next_state = PLAY2;
            end

            DONE: begin
                next_state = IDLE;
            end
            
            default: next_state = IDLE;
        endcase
    end

    always @(posedge clk ) begin
        if (!rst_n) begin // Detect negative level of rst_n
            stage_index <= 0;
            counter <= 0;
            active <= 0;
            wave_out <= 0;
        end else if (is_over) begin // Detect rising edge of is_over
            active <= 1;
            stage_index <= 0;
            counter <= 0;
            wave_out <= 0;
        end else begin  // running state
            case(state)
                IDLE: begin
                    stage_index <= 0;
                    counter     <= 0;
                    wave_out    <= 0;
                end

                PLAY1: begin
                    if ( counter >= decay_value) begin
                        wave_out <= 0;  // Toggle waveform
                    end 

                    if (counter >= PERIOD) begin  // Once we complete a full cycle
                            wave_out    <= 1;
                            counter     <= 0;
                            stage_index <= stage_index + 1;
                    end else begin
                        counter <= counter + 1;
                    end

                    // Use the distinct localparams instead of array lookup
                    case (stage_index)
                        5'd0:  decay_value<= DECAY_0;
                        5'd1:  decay_value<= DECAY_1;
                        5'd2:  decay_value<= DECAY_2;
                        5'd3:  decay_value<= DECAY_3;
                        5'd4:  decay_value<= DECAY_4;
                        5'd5:  decay_value<= DECAY_5;
                        5'd6:  decay_value<= DECAY_6;
                        5'd7:  decay_value<= DECAY_7;
                        5'd8:  decay_value<= DECAY_8;
                        5'd9:  decay_value<= DECAY_9;
                        5'd10: decay_value <= DECAY_10;
                        5'd11: decay_value <= DECAY_11;
                        5'd12: decay_value <= DECAY_12;
                        5'd13: decay_value <= DECAY_13;
                        5'd14: decay_value <= DECAY_14;
                        5'd15: decay_value <= DECAY_15;
                        default: decay_value<= DECAY_0;
                    endcase
                end

                PLAY2: begin
                    if ( counter >= decay_value) begin
                        wave_out <= 0;  // Toggle waveform
                    end 

                    if (counter >= PERIOD) begin  // Once we complete a full cycle
                            wave_out    <= 1;
                            counter     <= 0;
                            stage_index <= stage_index + 1;
                    end else begin
                        counter <= counter + 1;
                    end

                    // Use the distinct localparams instead of array lookup
                    case (stage_index - 15)
                        5'd0:  decay_value<= DECAY_0;
                        5'd1:  decay_value<= DECAY_1;
                        5'd2:  decay_value<= DECAY_2;
                        5'd3:  decay_value<= DECAY_3;
                        5'd4:  decay_value<= DECAY_4;
                        5'd5:  decay_value<= DECAY_5;
                        5'd6:  decay_value<= DECAY_6;
                        5'd7:  decay_value<= DECAY_7;
                        5'd8:  decay_value<= DECAY_8;
                        5'd9:  decay_value<= DECAY_9;
                        5'd10: decay_value <= DECAY_10;
                        5'd11: decay_value <= DECAY_11;
                        5'd12: decay_value <= DECAY_12;
                        5'd13: decay_value <= DECAY_13;
                        5'd14: decay_value <= DECAY_14;
                        5'd15: decay_value <= DECAY_15;
                    endcase
                end

                DONE: begin
                    active <= 0;
                    wave_out <= 0;
                end

                default: begin
                    stage_index <= 0;
                    counter <= 0;
                    active <= 0;
                    wave_out <= 0;
                end
            endcase
        end
    end