Verilog: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
Line 940: Line 940:
*store it in memory
*store it in memory
*play it back
*play it back
==Clock Divider==
This is the same as above with parameters for count width and max_count.
==Memory==
==Memory==
This is the same as before but we pass the depth and the width of the memory we are using. This did introduce System Verilog and the use of the function $clog2. I think that the system verilog can be transpiled to verilog. Anyway here it is.
This is the same as before but we pass the depth and the width of the memory we are using. This did introduce System Verilog and the use of the function $clog2. I think that the system verilog can be transpiled to verilog. Anyway here it is.

Revision as of 01:22, 21 December 2024

Introduction

Dipping my toe into this now I own an fpga

Hello World

This is the first program. There are two files to get it to work, a pcf file which defines things you use in the verilog file. Currently my understanding is it maps hardware to names which you can reference in the verilog file. Here is the project. I only use two buttons in the example but there are four shown for the second example
==Requirements For the requirements we have only light when both buttons are pressed.

Truth Table

We can express this as a circuit diagram and a truth table. We need to understand some boolean algebra to understand the truth table

PCF Physical Constraints File (and_gate.pcf)

Could not find a lexer but here we define names to io pins. You lookup the pin in the datasheet, in my case a ice40UP5K-B-EVN and you assign a name.

#LED 
set_io              led_0  41

#HEADER B I/O
set_io  -pullup yes pmod_0 23
set_io  -pullup yes pmod_1 25

Verilog File (and_gate.v)

Here is my first program, it defines two input buttons and 1 LED in a module called and_gate. The value of LED is true when pmod_0 and pmod_1 are true.

module and_gate (
    // inputs
    input   pmod_0,
    input   pmod_1,

    // Outputs
    output  led_0
);

    assign led_0 = ~pmod_0 & ~pmod_1;
    
endmodule

Hello World 2

Well now we have the basics we can add some more verilog stuff. We can reference the inputs and outputs as arrays. We are now going to build a circuit where if we press 1 button, 2 LEDs light up and if we press 2 buttons 2 LED light up. This is shown in the circuit diagram and truth table below

PCF Physical Constraints File (and_gate.pcf)

Not we can put the hardware in array to make the verilog file simpler

#LED 
set_io    led[0]  23
set_io    led[1]  25
set_io    led[2]  26
#set_io    led[3]  27
#set_io    led[4]  32

set_io  -pullup yes pmod[0] 36
set_io  -pullup yes pmod[1] 42

#set_io  -pullup yes pmod_2 12
#set_io  -pullup yes pmod_3 14

Verilog File (and_gate.v)

Here is my first program. We basically create a name for the wiring not_pod_0 and assign LED 0,1 to one value and LED 2 to another.

module and_gate (
    // inputs
    input   [1:0] pmod,

    // Outputs
    output  [2:0] led
);

    // Wire declaration
    wire not_pod_0;

    // Continuous Assignment replicate 1 wire to two outputs
    assign not_pmod_0 = ~pmod[0];
    assign led[1:0] = {2{not_pmod_0}};

    // Continuous Assignment: NOT and AND operators
    assign led[2] = not_pmod_0 & ~pmod[1];
    
endmodule

Inverting of Buttons

As I get more familar with this I realize that the assign can be used to control the fact that buttons work in reverse. We use a pullup resister to ensure the button is on by default. We can reverse this be using assign

module full_adder (
    // Inputs
    input   pmod,
);
    wire A;
    assign A = ~pmod;
...

True Adder

Truth Table

Next we make a truth adder using the following
The Cᵢₙ represents the carry value. So you add A + B and the carry to get column S. If there is a carry then Cₒᵤₜ is 1

Boolean Algebra representation

Doing my own attempt and it took a couple because it is hard to see the bars and we have

S    = (A̅.B̅.C ) +  (A̅.B.C̅) + (A.B̅.C̅) + (A.B.C)
Cₒᵤₜ = (A̅.B.C ) +  (A.B̅.C) + (A.B.C̅) + (A.B.C̅)

Now we need to simplify

Simplification of S

Here is the Simplification of S

Lets take C as common and pair up with 1st and 4th expression and 2 and 3

=> C ( A̅.B̅ + A.B )  + C̅ (A̅.B + A.B̅)
   
Found this a bit tricky to wrote up the XOR and NOR above. Basically A̅.B̅ + A.B = A̅ ⊕ B̅ - XOR and A̅.B + A.B̅ = A ⊕ B - NOR
=> C ( A̅ ⊕ B̅ ) + C̅ ( A ⊕ B)
 
Lets substitute A̅ ⊕ B̅ = x this gives
=> Cx̅ + C̅x Compliments result in XOR
=> C ⊕ x 
 
 Substituting x back in
=> C ⊕ A ⊕ B


Simplification of Cₒᵤₜ

Here is the Simplification of Cₒᵤₜ

 Lets take C as common for 1 and 2 and AB to 3 and 4
=> C ( A̅B + AB̅) + AB (C + C̅)

 Using OR law (Z + Z̅ = 1) we can apply this to the Right hand side

=> C ( A̅B + AB̅) + AB

 Using compliments

=> C (A̅ ⊕ B̅) + AB

PCF File

Perhaps the only part I found simple

#LEDS 
set_io    led[0]  23
set_io    led[1]  25
set_io    led[2]  26
set_io    led[3]  27
set_io    led[4]  32

#Push Buttons
set_io  -pullup yes pmod[0] 36
set_io  -pullup yes pmod[1] 42
set_io  -pullup yes pmod[2] 38
set_io  -pullup yes pmod[3] 28

Verilog File

Not that hard but do need to understand there result. Here is mine

module full_adder (

    // Inputs
    input   [2:0]   pmod,
    
    // Output
    output  [1:0]   led
);
    wire A;
    wire B;
    wire C;

    // Set A, B, and C  to buttons and off
    assign A = ~pmod[0];
    assign B = ~pmod[1];
    assign C = ~pmod[2];

    // Taken from above working
    // led[0] C (A̅ ⊕ B̅) + AB
    // led[1] C ⊕ A ⊕ B

    // Cout
    assign led[0] = (C & (!A ^ !B) ) | (A & B);

    // S
    assign led[1] = C ^ A ^ B;

    
endmodule

Digi-Keys Solution

Learning Verilog using the digi-key [tutorial]. Mine works but as is my want I need to understand the differences

  • leds are reversed
  • led[0] and my solution are identical we just need to define a_xor_b
  • led[1] I need to understand how ( (a ^ b) & c_in) in their solution = (C & (!A ^ !B) ) in my solution


So here is my explanation for why (A ^ B) = (!A ^ !B)

 We know A ^ B =  A!B + !AB because of the XOR above
 If we substitute A and B with there negatives
 We get A!B + !AB which is A^B
module full_adder (

    // Inputs
    input   [2:0]   pmod,
    
    // Output
    output  [1:0]   led
);

    // Wire (net) declarations (internal to module)
    wire a;
    wire b;
    wire c_in;
    wire a_xor_b;
    
    // A, B, and C_IN are inverted button logic
    assign a = ~pmod[0];
    assign b = ~pmod[1];
    assign c_in = ~pmod[2];
    
    // Create intermediate wire (net)
    assign a_xor_b = a ^ b;

    // Create output logic
    assign led[0] = a_xor_b ^ c_in;
    assign led[1] = (a_xor_b & c_in) | (a & b);
endmodule

Always and Registers

The next example is to demonstrate how these work. Hopefully comments in line make it obvious

module button_counter (

    // Inputs
    input   [2:0]   pmod,
    
    // Output
    output  reg [3:0]   led
);

    wire rst;
    wire clk;

    assign rst = ~pmod[0];
    assign clk = ~pmod[1];

    // Like a loop
    // while(clk or rst) {
    //   if(rst){
    //     led[0-3] = b0 
    //   } else {
    //     led[ led[current] +1] = b1
    //   }
    // }
     
    always @(posedge clk or posedge rst) begin
        if(rst == 1'b1) begin
            led <= 4'b0;
        end else begin
            led <= led + 1'b1;
        end
    end
endmodule

Clocks

Got this to work and understood it. Cannot seem to replace clock with crystal clearly more to learn.

module button_counter (

    // Inputs
    input               clk,
    input               rst_btn,   

    // Output
    output  reg [3:0]   led
);
    wire rst;

    // Create some registers
    reg div_clk;
    reg [31:0] count;

    // 10110111000110110000000 = 6000000 in Binary

    localparam [31:0] max_count = 6000000;
 
    // Reset is the inverse of the reset button
    assign rst = ~rst_btn;

    // Count up on (divided) clock rising edge or reset on button push
    // while (div_clk or reset) {
    //    if (reset == 1) {
    //       led[0-3] = 0
    //    }   
    //    else {
    //       led[ led[current] +1] = b1 
    //    }
    // }
    always @ (posedge div_clk or posedge rst) begin
        if (rst == 1'b1) begin
            led <= 4'b0;
        end else begin
            led <= led + 1'b1;
        end
    end

    // Count up on (divided) clock rising edge or reset on button push
    // while (clk or reset) {
    //    if (reset == 1) {
    //       count[0-31] = 0 
    //    }   
    //    else if (count = max_count) {
    //       count[0-31] = 0 
    //       div_clk = !div_clk;
    //    }
    //    else {
    //       count = count +1
    //    }
    // }
    always @ (posedge clk or posedge rst) begin
        if (rst == 1'b1) begin
            count <= 32'b0;
        end else if (count == max_count) begin
            count <= 32'b0;
            // Revisiting this, it was this line I struggled with but the answer is pretty easy
            // One invert the value if the max_count has been achieved. We invert because we don't know if it is one or zero
            div_clk <= ~div_clk;
        end else begin
            count <= count + 1;
        end
    end
    
endmodule

Mealy and Moore Finite State Machines (FSM)

Finite State Machines

Thought I knew about these. Turns out I need to match knowledge to theory. Here is how we show finite state machines. Either with a table or a diagram. The circles represent states and the double circles are what is called accepeting states.

Working through the logic we find it is just fancy language which is good because we can communicate and understand each other.

  • Q is a finite set of states.
  • ∑ is a finite set of symbols called the alphabet.
  • 8δ is the transition function where δ: Q × ∑ → Q
  • q0 is the initial state from where any input is processed (q0 ∈ Q).
  • F is a set of final state/states of Q (F ⊆ Q).

Looking at the video kinda explained it all. Note there is a arrow usually to show the start state.

Verilog State Machine Examples

Moore and Mearly State Machine

In the example we were shown both moore and mealy state machines for the same requirements
Moore

Mealy


A chapter that actually made sense. It was suggested the moore state machine was easier to read but moore states to handle.

Moore State Machine

module moore_fsm (

    // Inputs
    input               clk,
    input               rst_btn,
    input               go_btn,
    
    // Outputs
    output  reg [3:0]   led,
    output  reg         done_sig
);

    // States
    localparam  STATE_IDLE      = 2'd0;
    localparam  STATE_COUNTING  = 2'd1;
    localparam  STATE_DONE      = 2'd2;
    
    // Max counts for clock divider and counter
    localparam  MAX_CLK_COUNT   = 24'd1500000;
    localparam  MAX_LED_COUNT   = 4'hF;
    
    // Internal signals
    wire rst;
    wire go;
    
    // Internal storage elements
    reg         div_clk;
    reg [1:0]   state;
    reg [23:0]  clk_count;
    
    // Invert active-low buttons
    assign rst = ~rst_btn;
    assign go = ~go_btn;
    
    // Clock divider
    always @ (posedge clk or posedge rst) begin
        if (rst == 1'b1) begin
            clk_count <= 24'b0;
        end else if (clk_count == MAX_CLK_COUNT) begin
            clk_count <= 24'b0;
            div_clk <= ~div_clk;
        end else begin
            clk_count <= clk_count + 1;
        end
    end
    
    // State transition logic
    always @ (posedge div_clk or posedge rst) begin
    
        // On reset, return to idle state
        if (rst == 1'b1) begin
            state <= STATE_IDLE;
            
        // Define the state transitions
        end else begin
            case (state)
            
                // Wait for go button to be pressed
                STATE_IDLE: begin
                    if (go == 1'b1) begin
                        state <= STATE_COUNTING;
                    end
                end
                
                // Go from counting to done if counting reaches max
                STATE_COUNTING: begin
                    if (led == MAX_LED_COUNT) begin
                        state <= STATE_DONE;
                    end
                end
                
                // Spend one clock cycle in done state
                STATE_DONE: state <= STATE_IDLE;
                
                // Go to idle if in unknown state
                default: state <= STATE_IDLE;
            endcase
        end
    end
    
    // Handle the LED counter
    always @ (posedge div_clk or posedge rst) begin
        if (rst == 1'b1) begin
            led <= 4'd0;
        end else begin
            if (state == STATE_COUNTING) begin
                led <= led + 1;
            end else begin
                led <= 4'd0;
            end
        end
    end
    
    // Handle done LED output
    always @ ( * ) begin
        if (state == STATE_DONE) begin
            done_sig = 1'b1;
        end else begin
            done_sig = 1'b0;
        end
    end
    
endmodule

Mearly State Machine

module mealy_fsm (

    // Inputs
    input               clk,
    input               rst_btn,
    input               go_btn,
    
    // Outputs
    output  reg [3:0]   led,
    output  reg         done_sig
);

    // States
    localparam STATE_IDLE       = 2'd0;
    localparam STATE_COUNTING   = 2'd1;    
    localparam STATE_DONE       = 2'd2;
    
    // Not the letter before the value is the type
    // b = binary
    // d = decimal
    // h = hex
    // Max counts for clock divider and counter
    localparam MAX_CLK_COUNT    = 24'd1500000;
    localparam MAX_LED_COUNT    = 4'hf;
    
    // Internal signals
    wire rst;
    wire go;
    
    // Internal storage elements
    reg         div_clk;
    reg [1:0]   state;
    reg [23:0]  clk_count;
    
    // Invert active-low buttons
    assign rst = ~rst_btn;
    assign go = ~go_btn;

    // clock divider no change except for values    
    always @ (posedge clk or posedge rst) begin
        if (rst == 1'b1) begin
            clk_count <= 24'b0;
        end else if (clk_count == MAX_CLK_COUNT) begin
            clk_count <= 24'b0;
            div_clk <= ~div_clk;
        end else begin
            clk_count <= clk_count + 1;
        end
    end


    // State transition logic
    always @ (posedge div_clk or posedge rst) begin
    
        // On reset, return to idle state and restart counters
        if (rst == 1'b1) begin
            state <= STATE_IDLE;
            
        // Define the state transitions
        end else begin
            case (state)
            
                // Wait for go button to be pressed
                STATE_IDLE: begin
                    if (go == 1'b1) begin
                        state <= STATE_COUNTING;
                    end
                end
            
                // Go from counting to done if counting reaches max
                STATE_COUNTING: begin
                    if (led == MAX_LED_COUNT) begin
                        state <= STATE_DONE;
                    end
                end
                    
                // Default case: return to idle state
                default: state <= STATE_IDLE;
            endcase
        end
    end
    
    // Run counter if in wait state
    always @ (posedge div_clk or posedge rst) begin
        if (rst == 1'b1) begin
            led <= 4'd0;
        end else begin
            if (state == STATE_COUNTING) begin
                led <= led + 1;
            end else begin
                led <= 4'd0;
            end
        end
    end
    
    always @ ( * ) begin
        if (state == STATE_DONE) begin
            done_sig = 1'b1;
        end else begin
            done_sig = 1'b0;
        end
    end

endmodule

Verilog Modules 2024

Pretty pretty please to be doing new stuff. Been a journey for me. Anyway on with the show. This is the FPGA part 6 from digikey and introduces modules which are what they sound like. It show me the glue between modules had changes which will make this a tad longer than expected. First the module which is the clock divider we did before except we now use parameter for the values we want to be able to set differently.

Passing Parameters

There two ways to do this the old way and the new way. For modules you seem to be able to mix and match.

Old Way

module clock_divider (

    // Inputs
    input       clk,
    input       rst,
    
    // Outputs
    output  reg out    
);

    // Parameters
    parameter                   COUNT_WIDTH = 24;
    parameter [COUNT_WIDTH:0]   MAX_COUNT   = 6000000 - 1;

    // Internal signals
    wire rst;
    reg div_clk;
    reg [COUNT_WIDTH:0] count;
    
    // Clock divider
    always @ (posedge clk or posedge rst) begin
        if (rst == 1'b1) begin
            count <= 0;
            out <= 0;
        end else if (count == MAX_COUNT) begin
            count <= 0;
            out <= ~out;
        end else begin
            count <= count + 1;
        end
    end
endmodule

And now here is an example of using the module

module main (

    // Inputs
    input           clk,
    input           rst_btn,
    
    // Outputs
    output  [1:0]   led         // Not reg element!
);

    // Internal signals
    wire rst;
    
    // Invert active-low button
    assign rst = ~rst_btn;

    // Instantiate the first clock divider module
    clock_divider div_1 (
        .clk(clk), 
        .rst(rst), 
        .out(led[0])
    );
    defparam div_1.COUNT_WIDTH  = 32;
    defparam div_1.MAX_COUNT    = 1500000 - 1;
    
    // Instantiate the second clock divider module
    clock_divider div_2 (
        .clk(clk),
        .rst(rst),
        .out(led[1])
    );
endmodule

Ansi Way

module main (

    // Inputs
    input           clk,
    input           rst_btn,
    
    // Outputs
    output  [1:0]   led         // Not reg element!
);

    // Internal signals
    wire rst;
    
    // Invert active-low button
    assign rst = ~rst_btn;

    // Instantiate the first clock divider module
    clock_divider #(.COUNT_WIDTH(32), .MAX_COUNT(1500000 - 1)) div_1 (
        .clk(clk), 
        .rst(rst), 
        .out(led[0])
    );
    
    // Instantiate the second clock divider module
    clock_divider div_2 (
        .clk(clk),
        .rst(rst),
        .out(led[1])
    );
                    
endmodule

And now here is an example of using the module

module clock_divider #(
    parameter                   COUNT_WIDTH = 24,
    parameter [COUNT_WIDTH:0]   MAX_COUNT   = 6000000 - 1
) (

    // Inputs
    input       clk,
    input       rst,
    
    // Outputs
    output  reg out    
);

    // Internal signals
    // wire rst;
    reg div_clk;
    reg [COUNT_WIDTH:0] count;
    
    // Clock divider
    always @ (posedge clk or posedge rst) begin
        if (rst == 1'b1) begin
            count <= 0;
            out <= 0;
        end else if (count == MAX_COUNT) begin
            count <= 0;
            out <= ~out;
        end else begin
            count <= count + 1;
        end
    end
    
endmodule

Using the Test Bed

This was a bit full-on, maybe me. There is alot to unpick. In the video they test the clock divider module above. This links in nicely with my work on pulseview. Getting the duration an scale right it important.
The initial keyword means what is says and I guess the is pretty obvious what it does. The dumpvars first parameter determines the depth the measures are for. E.g. 0 is everything. I think 1 means just the clock_divider module.

// Defines timescale for simulation: <time_unit> / <time_precision>
`timescale 1 ns / 10 ps

// Define our testbench
module clock_divider_tb();

    // Internal signals
    wire    out;

    // Storage elements (set initial values to 0)
    reg     clk = 0;
    reg     rst = 0;            

    // Simulation time: 10000 * 1 ns = 10 us
    localparam DURATION = 10000;
    
    // Generate clock signal: 1 / ((2 * 41.67) * 1 ns) = 11,999,040.08 MHz
    always begin
        
        // Delay for 41.67 time units
        // 10 ps precision means that 41.667 is rounded to 41.67
        #41.667
        
        // Toggle clock line
        clk = ~clk;
    end
    
    // Instantiate the unit under test (UUT)
    clock_divider #(.COUNT_WIDTH(4), .MAX_COUNT(6 - 1)) uut (
        .clk(clk),
        .rst(rst),
        .out(out)
    );
    
    // Pulse reset line high at the beginning
    initial begin
        #10
        rst = 1'b1;
        #1
        rst = 1'b0;
    end
    
    // Run simulation (output to .vcd file)
    initial begin
    
        // Create simulation output file 
        $dumpfile("clk-div_tb.vcd");
        $dumpvars(0, clock_divider_tb);
        
        // Wait for given amount of time for simulation to complete
        #(DURATION)
        
        // Notify and end simulation
        $display("Finished!");
        $finish;
    end
    
endmodule

Memory

Overview

More concepts. Really enjoying some of my other knowledge joining up. In the video they mentioned the types of memory an fpga.

  • Distributed RAM - This is when the data is store in the Lookup tables (LUTs)
  • EBR (Embedded block RAM) - These are configurable memory chunks. You can set the width, depth and other characteristics
  • SPRAM (Single Port Synchronous RAM) - Larger than block RAM

On my fpga ice40up5k-b-evn we have

  • 4 x 256k SPRAM
  • 120 kbits (120000 bits) EBR

We have the following configurations for EB RAM

Signals for the EB RAM

Signals for the SPRAM

Once you use a block of RAM you cannot use another part of the block for something else.

Memory Module

Here is a basic memory module. The inputs, outputs are as expected and we read and write when the enabled is set.

// Inferred block RAM
module memory #(

    // Parameters
    parameter   INIT_FILE = ""
) (

    // Inputs
    input               clk,
    input               w_en,
    input               r_en,
    input       [3:0]   w_addr,
    input       [3:0]   r_addr,
    input       [7:0]   w_data,
    
    // Outputs
    output  reg [7:0]   r_data
);

    // Declare memory
    reg [7:0]  mem [0:15];
    
    // Interact with the memory block
    always @ (posedge clk) begin
    
        // Write to memory
        if (w_en == 1'b1) begin
            mem[w_addr] <= w_data;
        end
        
        // Read from memory
        if (r_en == 1'b1) begin
            r_data <= mem[r_addr];
        end
    end
    
    // Initialization (if available)
    initial if (INIT_FILE) begin
        $readmemh(INIT_FILE, mem);
    end
   
endmodule

Testing Again

So this shows a test bed where we read in from a file to drive the test.

`timescale 1 ns / 10 ps

// Define our testbench
module memory_tb();

    // Internal signals
    wire    [7:0]  r_data;
    

    // Storage elements (set initial values to 0)
    reg             clk = 0;
    reg             w_en = 0;
    reg             r_en = 0;
    reg     [3:0]   w_addr;
    reg     [3:0]   r_addr;
    reg     [7:0]   w_data;
    integer         i;

    // Simulation time: 10000 * 1 ns = 10 us
    localparam DURATION = 10000;
    
    // Generate clock signal: 1 / ((2 * 41.67) * 1 ns) = 11,999,040.08 MHz
    always begin
        #41.67
        clk = ~clk;
    end
    
    // Instantiate the unit under test (UUT)
    memory #(.INIT_FILE("mem_init.txt")) uut (
        .clk(clk),
        .w_en(w_en),
        .r_en(r_en),
        .w_addr(w_addr),
        .r_addr(r_addr),
        .w_data(w_data),
        .r_data(r_data)
    );
    
    // Run test: write to location and read value back
    initial begin
    
        // Test 1: read initial values
        for (i = 0; i < 16; i = i + 1) begin
            #(2 * 41.67)
            r_addr = i;
            r_en = 1;
            #(2 * 41.67)
            r_addr = 0;
            r_en = 0;
        end
        
        // Test 2: Write to address 0x0f and read it back
        #(2 * 41.67)
        w_addr = 'h0f;
        w_data = 'hA5;
        w_en = 1;
        #(2 * 41.67)
        w_addr = 0;
        w_data = 0;
        w_en = 0;
        r_addr = 'h0f;
        r_en = 1;
        #(2 * 41.67)
        r_addr = 0;
        r_en = 0;
        
        // Test 3: Read and write at same time
        #(2 * 41.67)
        w_addr = 'h0a;
        w_data = 'hef;
        w_en = 1;
        r_addr = 'h0a;
        r_en = 1;
        #(2 * 41.67)
        w_addr = 0;
        w_data = 0;
        w_en = 0;
        r_addr = 0;
        r_en = 0;
    end
    
    // Run simulation (output to .vcd file)
    initial begin
    
        // Create simulation output file 
        $dumpfile("memory_tb.vcd");
        $dumpvars(0, memory_tb);
        
        // Wait for given amount of time for simulation to complete
        #(DURATION)
        
        // Notify and end simulation
        $display("Finished!");
        $finish;
    end
    
endmodule

Device Utilization

Before doing the test they built the software with apio build -v and then showed that

apio build -v

Because of this you needed real pins in the PCF otherwise the build would fail. The point of this was to look at the output of the build and realize the usage. Useful when designing

Big Project (for me)

This is a big project. We are going to

  • collect a button sequence
  • store it in memory
  • play it back

Clock Divider

This is the same as above with parameters for count width and max_count.

Memory

This is the same as before but we pass the depth and the width of the memory we are using. This did introduce System Verilog and the use of the function $clog2. I think that the system verilog can be transpiled to verilog. Anyway here it is.

module memory #(

    // Parameters
    parameter   MEM_WIDTH = 16,
    parameter   MEM_DEPTH = 256,
    parameter   INIT_FILE = ""
) (

    // Inputs
    input               clk,
    input               w_en,
    input               r_en,
    input       [ADDR_WIDTH - 1:0]   w_addr,
    input       [ADDR_WIDTH - 1:0]   r_addr,
    input       [MEM_WIDTH - 1:0]  w_data,
    
    // Outputs
    output  reg [MEM_WIDTH - 1:0]  r_data
);

    // Calculate the number of bits required for the address
    localparam  ADDR_WIDTH = $clog2(MEM_DEPTH);

    // Declare memory
    reg [MEM_WIDTH - 1:0]  mem [0:MEM_DEPTH - 1];
    
    // Interact with the memory block
    always @ (posedge clk) begin
        
        // Write to memory
        if (w_en) begin
            mem[w_addr] <= w_data;
        end
        
        // Read from memory
        if (r_en) begin
            r_data <= mem[r_addr];
        end
    end
    
    // Initialization (if available)
    initial if (INIT_FILE) begin
        $readmemh(INIT_FILE, mem);
    end
    
endmodule

Debouncing

This is when you press once but it registers a few times. We don't want this and to avoid it we create a module with a state machine to control it.

 
module debouncer #(

    // Max counts for wait state (40 ms with 12 MHz clock)
    parameter                   COUNT_WIDTH     = 20,
    parameter [COUNT_WIDTH:0]   MAX_CLK_COUNT   = 480000 - 1
) (

    // Inputs
    input               clk,
    input               rst,
    input               in,
    
    // Outputs
    output  reg         out
);

    // States
    localparam  STATE_HIGH      = 2'd0;
    localparam  STATE_LOW       = 2'd1;    
    localparam  STATE_WAIT      = 2'd2;
    localparam  STATE_PRESSED   = 2'd3;
    
    // Internal storage elements
    reg [1:0]           state;
    reg [COUNT_WIDTH:0] clk_count;
    
    // State transition logic
    always @ (posedge clk or posedge rst) begin
    
        // On reset, return to idle state
        if (rst == 1'b1) begin
            state <= STATE_HIGH;
            out <= 1'b0;
            
        // Define the state transitions
        end else begin
            case (state)
            
                // Wait for increment signal to go from high to low
                STATE_HIGH: begin
                    out <= 1'b0;
                    if (in == 1'b0) begin
                        state <= STATE_LOW;
                    end
                end
                
                // Wait for increment signal to go from low to high
                STATE_LOW: begin
                    if (in == 1'b1) begin
                        state <= STATE_WAIT;
                    end
                end
                
                // Wait for count to be done and sample button again
                STATE_WAIT: begin
                    if (clk_count == MAX_CLK_COUNT) begin
                        if (in == 1'b1) begin
                            state <= STATE_PRESSED;
                        end else begin
                            state <= STATE_HIGH;
                        end
                    end
                end
                
                // If button is still pressed
                STATE_PRESSED: begin
                    out <= 1'b1;
                    state <= STATE_HIGH;
                end
                    
                // Default case: return to idle state
                default: state <= STATE_HIGH;
            endcase
        end
    end
    
    // Run counter if in wait state
    always @ (posedge clk or posedge rst) begin
        if (rst == 1'b1) begin
            clk_count <= 0;
        end else begin
            if (state == STATE_WAIT) begin
                clk_count <= clk_count + 1;
            end else begin
                clk_count <= 0;
            end
        end
    end
    
endmodule