Image of Basys 3 Board

LED Counter Circuit Using A Shift Register

I am going to build an LED counter circuit. The LED counter uses the configurable counter circuit and the configurable shift register created earlier. Using those two components I will build a circuit that cycles through the LEDs. Recall from the configurable shift register post that we needed a way to slow down the shift register so that the changes are slow enough for the human eye to see. And now that we have a configurable counter we have what we need to do that!

Let’s get started. First, I create a new Vivado project and copy in the Basys 3 master design constraints file. I then edit the constraints file to uncomment the lines for the following signals:

  • Clock signal
  • LEDs (all 16 of them)
  • Button C (center push button)

LED Counter Design Constraints

The complete constraint file, with commented lines removed, is shown below. And notice that I am using the center pushbutton in this project. This project provides a good opportunity to continue to explore the various on-board peripherals that the Basys 3 provides. For this project I am going to connect the center pushbutton to the rst signal. That provides a way to physically reset the hardware circuit if needed.

## This file is a general .xdc for the Basys3 rev B board
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project

## Clock signal
set_property PACKAGE_PIN W5 [get_ports clk]
	set_property IOSTANDARD LVCMOS33 [get_ports clk]
	create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports clk]

## LEDs
set_property PACKAGE_PIN U16 [get_ports {led[0]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[0]}]
set_property PACKAGE_PIN E19 [get_ports {led[1]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[1]}]
set_property PACKAGE_PIN U19 [get_ports {led[2]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[2]}]
set_property PACKAGE_PIN V19 [get_ports {led[3]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[3]}]
set_property PACKAGE_PIN W18 [get_ports {led[4]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[4]}]
set_property PACKAGE_PIN U15 [get_ports {led[5]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[5]}]
set_property PACKAGE_PIN U14 [get_ports {led[6]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[6]}]
set_property PACKAGE_PIN V14 [get_ports {led[7]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[7]}]
set_property PACKAGE_PIN V13 [get_ports {led[8]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[8]}]
set_property PACKAGE_PIN V3 [get_ports {led[9]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[9]}]
set_property PACKAGE_PIN W3 [get_ports {led[10]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[10]}]
set_property PACKAGE_PIN U3 [get_ports {led[11]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[11]}]
set_property PACKAGE_PIN P3 [get_ports {led[12]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[12]}]
set_property PACKAGE_PIN N3 [get_ports {led[13]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[13]}]
set_property PACKAGE_PIN P1 [get_ports {led[14]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[14]}]
set_property PACKAGE_PIN L1 [get_ports {led[15]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {led[15]}]

##Buttons
set_property PACKAGE_PIN U18 [get_ports btnC]
	set_property IOSTANDARD LVCMOS33 [get_ports btnC]

## Configuration options, can be used for all designs
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property CFGBVS VCCO [current_design]

And The Verilog Code

The complete Verilog code for this project is shown below. Though the code is not long, there are a few parts worth further discussion. The first of which are the parameters for our top-level module, more about those later. The next item of note is the addition of btnC as an input signal. This input connects the physical pushbutton signal to our circuit. After that we have some internal connections and state in the form of wLed, shift_clk and rLedState.

And then we have the instantiation of both a configurable counter and a configurable shift register. Recall that the counter raises a tick signal when the count wraps back to 0. In this circuit our counter will count up to 2 ^ 21, then raise the tick signal which I have connected to shift_clk. So when the counter wraps it generates a clock signal, shift_clk, that I have connected as the clk input of the shift register. In this way the shift register is activated only once every 2 ^ 21 ticks of the 100Mhz Basys 3 clock.

The outputs of the shift register, q, connect to wLed which in turn connect to the led output. And finally, in the always block we handle the reset condition signaled by raising btnC to true, by setting the input to the shift register, rLedState, to 1. When the top LED is on, wLed[LED_BITS – 1] == 1, we set rLedState to 0. At that point the shift register starts shifting in 0 bits. That continues until the top LED is off and rLedState is set to 1 to repeat the process.

module shift_led
    #(parameter COUNTER_BITS = 21, LED_BITS = 16)
    (
    input clk,
    input btnC,
    output [LED_BITS - 1:0] led
    );
    
    wire [LED_BITS - 1 : 0] wLed;
    wire wShiftClk;
    reg rLedState = 1;
    
    // create a counter to generate new clock for shift reg 
    counter_n #(.BITS(COUNTER_BITS)) counter(.clk(clk), .rst(btnC), .tick(wShiftClk));
    
    // create a shift register that runs every 2 ^ COUNTER_BITS clock cycles
    shift_reg #(.BITS(LED_BITS)) shifter(.clk(wShiftClk), .rst(btnC), .d(rLedState), .q(wLed));
    
    always @(posedge wShiftClk, posedge btnC)
        if (btnC)
            rLedState = 1'b1;
        else if (wLed[LED_BITS - 1] == 1'b1)
            rLedState <= 1'b0;
        else
            rLedState <= 1'b1;
             
    assign led = wLed;
    
endmodule

Why Parameterize The Top-Level?

Let’s talk about the parameters. Why are they there? They are there so that I can run the top-level either directly on the hardware, or as part of a simulation run. As mentioned previously, iterating on a design using the simulator is typically the most efficient way to develop new code.

At the same time the true proof of a new design is to validate it running on real hardware. Vivado provides the ability to do both from the same project. In addition to design sources, you can add simulation sources to your project. The simulation source is the top-level module for simulation. The simulation source normally sets up the clock, reset and stimulus signals and instantiates the top-level design.

Yes, but still you might ask why the top-level design has parameters. The reason is that by parameterizing the top-level design source, the simulation can instantiate the counter and a shift register with fewer bits. Having fewer bits in the counter and shift registers makes viewing and debugging the circuits easier. Rather than scroll though the simulation timeline waiting for a counter to count up to 2 ^21 I can set the counter to be only a few bits in length.

Let’s take a look at the test bench I used for simulation. In the code below you can see on line 9 where I instantiate the top-level Verilog file shift_led.v and name that instance shifter. You can see that I set the DELAY_BITS parameter to 3 rather than the default of 21. This simplifies simulation. Also on line 9 you can see where I connect the simulation test_clk signal to the clk input, the simulation rst signal to the rst input, and the test_led output to the led output.

module shift_led_test();

    localparam T = 20;
    localparam LED_BITS = 16;
    
    reg test_clk, rst;
    wire [LED_BITS - 1:0] test_led;

    shift_led #(.DELAY_BITS(3)) shifter(.clk(test_clk), .rst(rst), .led(test_led));

    // generate clock signal    
    always
    begin
        test_clk = 1'b1;
        #(T/2);
        test_clk = 1'b0;
        #(T/2);
    end

    // reset the circuit for first half clock cycle
    initial
    begin
        rst = 1'b1;
        #(T/2);
        rst = 1'b0;
    end

    initial
    begin
        // wait for reset to end
        @(negedge rst);

        repeat(2 ** LED_BITS) @(negedge test_clk);
                        
        $stop; 
    end    
endmodule

And that is it! I now have an LED counter that can run on the Basys 3. Here is a short video of this project running on hardware so you can see how it looks.

That’s it for today! The source code for this project is available on github. If you have feedback, a question or a suggestion, please leave a comment!

Discover more from FPGA Coding

Subscribe now to keep reading and get access to the full archive.

Continue reading