Alchitry Io
Alchitry
The Alchitry IO is the perfect way to get your feet wet with digital design. The plethora of
buttons and LEDs lend themselves to fantastic beginner tutorials that will walk you
through all the basics!
4 7-segment LED digits
5 momentary push buttons
24 LEDs
24 DIP switches
Io Element
March 30, 2019Justin Rajewski
This tutorial will introduce the Io Element and walk through some basic examples to get
your feet wet. If you haven't already, I highly recommend reading through the Your First
FPGA Project and synchronous logic tutorials before continuing.
Create the Project
In Alchitry Labs, open the new project dialog (File->New Project...) and name it
whatever you want. I named mine Io Element Demo. Before you click Create,
select IO Element Base from the From Example drop-down menu. This will give you a
bare bones base to start working with the element.
The only difference between this project and the standard base project is that this has
the connections to the Io Element already defined for you.
Let's take a look at the top file. You can use the tabs to select the version for your
board.
Au
Cu
Mojo
1 module au_top (
2
input clk, // 100MHz clock
3
input rst_n, // reset button (active low)
4
output led [8], // 8 user controllable LEDs
5
input usb_rx, // USB->Serial input
6
output usb_tx, // USB->Serial output
7
output io_led [3][8], // LEDs on IO Shield
8
output io_seg [8], // 7-segment LEDs on IO Shield
9
output io_sel [4], // Digit select on IO Shield
10
input io_button [5], // 5 buttons on IO Shield
11
input io_dip [3][8] // DIP switches on IO Shield
12 ) {
13
14 sig rst; // reset signal
15
16 .clk(clk) {
// The reset conditioner is used to synchronize the reset signal to the
FPGA
17
18
// clock. This ensures the entire FPGA comes out of reset at the same
time.
19
20
reset_conditioner reset_cond;
21 }
22
23 always {
reset_cond.in = ~rst_n; // input raw inverted reset signal
24
rst = reset_cond.out; // conditioned reset
25
26
usb_tx = usb_rx; // loop serial port
27
28
led = 8h00; // turn LEDs off
29
30
io_led = 3x{{8h00}}; // turn LEDs off
31
io_seg = 8hff; // turn segments off
32
io_sel = 4hf; // select no digits
33
34
}
}
You can see we have a handful of inputs and outputs named io_*. These are the Io
Element specific signals.
io_led connects to the 24 LEDs. This signal is organized as a 2D array to make it easier
to access the three groups of 8 LEDs. For example, if you want to get the first group of
LEDs you would use io_led[0] and if you wanted to get the third LED in the first group,
you would use io_led[0][2].
Because io_led is a 2D array, when we set all the LEDs to 0, we have a little fancy
syntax.
30io_led = 3x{{8h00}}; // turn LEDs off
We need the dimensions of our constant to match io_led. In this case, io_led is a 3 by 8
array. We use 8h00 as the base constant which is a single dimensional array of width 8.
The x{} syntax takes the outermost dimension and duplicates it. If we wrote 3x{8h00},
we would end up with 24h000000 since the outer most dimension is 8. This isn't what
we want as it is still a 1D array. Instead, we need to first create a 1 by 8 array and
duplicate it three times. To do this we use the array building syntax, {}. The
value {8h00} is a 1 by 8 2D array. We then use 3x{{8h00}} to get a 3 by 8 array of all
zeros. Note that we could have also written {8h00, 8h00, 8h00}, but the duplication
syntax is a bit cleaner.
io_seg and io_sel are used to control the 4 seven-segment LED displays. They are
active low (0 = on, 1 = off) and they will be covered in more detail later.
io_button is simply the 5 push buttons. io_button[0] is up, io_button[1] is
center, io_button[2] is down, io_button[3] is left, and io_button[4] is right.
Finally, io_dip is the 24 DIP switches. This is arranged in a 2D array exactly the same
way as io_led.
Faking Pull-downs on the Cu
If you are using a Cu then you may have noticed your top module looks a little different
than the Au. There are two modules, pull_down and pull_down_2d, instantiated and
the io_button and io_dip signals are of type inout not input.
These module fakes pull-down resistors on the DIP switches and buttons by pulling the
pin low for a very short amount of time, releasing it, waiting a short period, then
sampling the input.
This is done because the Io Element was originally designed to use the pull-down
resistors embedded in the FPGA. However, the FPGA on the Cu only has internal pullup resistors.
If you take a look at the schematic, you'll see that the buttons and DIP switches are
connected to +3.3V via a 330 ohm resistor. This will prevent any damage to the FPGA if
the button or DIP switch is active and it tries to pull the pin to 0V.
This method of faking the pull-down resistor works because there is some amount of
capacitance on each input pin. So of it is internal to the FPGA and some is from the
board. When the switch isn't active, the pin is normally floating (not being pulled to
specific voltage). The FPGA the forces it to 0V and releases it. Since nothing is trying to
move it, the only sources to change the voltage are small amounts of leakage in the
FPGA and noise. These both take much longer than a couple clock cycles (30ns) to
significantly affect it. Therefore, when we read the state of the pin right after pulling it
down, it will still be 0 if nothing tried to change it.
If the button or switch is active, when the FPGA pulls the pin down, it will still get it
down. This is thanks to that resistor. However, once the FPGA lets go of the pin, the
parasitic capacitance will start to charge. We found it takes about 10ns-20ns for it to
reach a '1' state (3.3V) after letting go. This is why we wait for 30ns before sampling the
pin.
By repeating this process over and over, we can simulate having a normal pull-down
resistor.
Thanks to this module, you can ignore that any of this is even happening and simply
use its output as you would the input with a pull-down.
The only difference is instead of using io_dip or io_button you
use dip_pd.out and button_pd.out respectively.
Logic Gates
The first example we will work though is connecting a few DIP switches to the LEDs
through logic gates. If you aren't familiar with what logic gates (AND, OR, NOT, XOR)
are, you should take a look at the digital logic tutorial.
In this example we are going to start by making the first LED light up when switch
0 and 1 are turned on.
Au
Cu
Mojo
22always {
23 reset_cond.in = ~rst_n; // input raw inverted reset signal
24 rst = reset_cond.out; // conditioned reset
25
26 usb_tx = usb_rx; // loop serial port
27
28 led = 8h00; // turn LEDs off
29
30 io_led = 3x{{8h00}}; // turn LEDs off
31 io_seg = 8hff; // turn segments off
32 io_sel = 4hf; // select no digits
33
34 io_led[0][0] = io_dip[0][0] & io_dip[0][1];
35 }
Take a look at the last line. First we need to index the first LED. The first LED is in the
first group (group 0) and is the first in its group, so we used the index [0][0]. We then
index the first and second switches in the same manner. Finally, we use the & operator
to AND the bits together. & is the bit-wise AND operator.
Note that for the Cu this line will look a little difference since you need to use the output
from the pulldown modules.
37
io_led[0][0] = dip_pd.out[0][0] & dip_pd.out[0][1];
Notice that I didn't change the line where we assigned all LEDs a value of 0. This is
because the assignment to the first LED will simply be overruled by our second
assignment. It's good to remember that while always blocks have a sequential order of
precedence, they aren't actually sequential. This means that io_led[0][0] will only have
the value of the second assignment and it as if the first assignment never happened.
The tools will simply wire an AND gate to the two switch inputs and the led output.
Go ahead and build/load the project onto your board. Try playing with the first two
switches. If either switch is off, the LED should be off. Only when both switches are on
should the LED turn on.
You can now go back and replace the & operator with the operators for OR, |, XOR, ^,
and XNOR, ~^. Play with the switches and make sure you understand the different
operators.
Practice: Add two more lines to the always block so that the first LED of the first group
lights up as before, the first LED of the second group lights up when either (OR) of the
first two switches in the second group are on, and the first LED of the last group lights
up only when exactly one (XOR) switch of the first two switches in the last group are on.
Bit‐wise
The operators we have talked about so far are all bit-wise operators. That means that
they operate on two equally sized arrays and will perform their operation on each pair of
bits individually.
For example, if we want to light up the LED in the first group only when the
corresponding switch in the first and second groups are both on we could do the
following.
Au
Cu
Mojo
34io_led[0] = io_dip[0] & io_dip[1];
Note that io_led[0] is an array of width 8, and so is io_dip[0] and io_dip[1]. This single
statement will create 8 individual AND gates.
You can again use the OR, XOR, and XNOR operators in the same way.
We can also chain multiple gates together. For example, what if we want the LED to
light only when the corresponding switch in all three groups is on?
Au
Cu
Mojo
34io_led[0] = io_dip[0] & io_dip[1] & io_dip[2];
Bit-wise operators are evaluated from left to right so in this case io_dip[0] will be ANDed
with io_dip[1] and the result will then be ANDed with io_dip[2]. In the case of AND
gates, the order actually doesn't matter. However, if you start mixing and matching
operators the order can matter. You can also use parenthesis to make the order you
want things evaluated explicit.
Practice: Use two bit-wise operators so that the LEDs in the first group light up when
the corresponding switch in the third group is on or both switches in the first two groups
are on.
Reduction Operators
Reduction operators are similar to the bit-wise operators but they work on a single input
array and always output one bit. You could say they reduce an array to a single bit.
You can think of these operators as one huge logic gate with many inputs. The number
of inputs is equal to the size of the array the operator is used on.
In our example, we want to turn the first LED on only if every switch in the first group is
turned on. To do this we can use the & reduction operator.
Au
Cu
Mojo
34io_led[0][0] = &io_dip[0];
This is equivalent to ANDing each bit individually as shown below.
Au
Cu
Mojo
34
io_led[0][0] = io_dip[0][0] & io_dip[0][1] & io_dip[0][2] & io_dip[0][3] &
io_dip[0][4] & io_dip[0][5] & io_dip[0][6] & io_dip[0][7];
However, as you can tell, the reduction operator is much more compact.
It is even possible to use the reduction operators on multi-dimensional arrays. For
example, if we want the LED to turn on when any switch is on we could use the
following.
Au
Cu
Mojo
34io_led[0][0] = |io_dip;
I tend to use the AND version to easily detect if a signal is at its maximum value (all 1's).
The OR version basically tells you if the value is not 0 and the XOR version will tell you
if there is an odd number of 1's.
Each of these operators can also be negated with the bit-wise negate operator, ~. You
can simply place this in front of the expression to get the NAND, NOR, and XNOR
equivalents.
Practice: Using your newly acquired bit-wise and reduction operator skills, make the
first LED light up when any switch in the third group is on, or all the switches in the
second group are on and the first group has an odd number of switches on. This is a
fairly tricky challenge but everything you need has been mentioned in this tutorial. If you
get stuck feel free to head over to the forum for some help.
Math
This part of the tutorial we will look at addition, subtraction, and multiplication. We will
be performing different operations using the DIP switches as our number inputs. This
means everything will be in binary, so if you aren't familiar with binary check out
the ecodings tutorial.
Let's start with some addition. We will add the binary values from the first and second
groups of DIP switches.
Au
Cu
Mojo
22sig result [24]; // result of our operations
23
24 always {
25 reset_cond.in = ~rst_n; // input raw inverted reset signal
26
27
rst = reset_cond.out; // conditioned reset
28 usb_tx = usb_rx; // loop serial port
29
30 led = 8h00; // turn LEDs off
31
32 io_seg = 8hff; // turn segments off
33 io_sel = 4hf; // select no digits
34
35
36
result = io_dip[1] + io_dip[0]; // add the switch values
io_led = $build(result, 3); // convert result from a 24-bit array to a 3x8
37 array
38 }
Because the result of many of our operations will be more than 8 bits, we can create a
signal, result, to hold the value. You can think of signals (sig) simply as wires. They
don't add any cost to your design as they simply define the connection from something
to something else.
We set result to be the output of our addition. The addition of two 8 bit numbers results
in a 9 bit result. This is because an 8 bit number has the range 0-255 so adding two of
them together will have the range 0-510, which requires 9 bits to represent.
We then take result and connect it to the LEDs so that we can see all 24 bits. However,
this requires the use of the helper function $build(). This function takes a 1D array as
the first argument and splits it into a multidimensional array. In our case we pass 3 as
the second parameter which causes it to split the 24 bits three ways into a 3x8 array.
You can pass $build() more than one dimension to split. For
example, $build(result,3,2) would create a 3x2x4 array.
The helper function $flatten() does the opposite. It takes a multidimensional array and
flattens it into a 1D array.
Build and load your project to your board. Congratulations! You've just build a very basic
calculator. To make sure everything is working properly, let's calculate 2+2. 2 has the
binary representation of 0000 0010. Set both groups of switches to 2. The result on the
LEDs should be 4, or 0000 0100.
Now change the + to a - to subtract the first switch value from the second.
Au
Cu
Mojo
36result = io_dip[1] - io_dip[0]; // subtract the switch values
What happens when you set the second switch to 0 and the first to 1 so you're
performing 0-1? All 24 LEDs turn on! You can interpret this answer in two different
ways. The first is that the result simply underflowed and restarted back at the maximum
value. The second way is to assume the number is a 2's complement representation of 1. Both are valid interpretations and what you use will depend on the scenario.
Even though when the number is negative all 24 LEDs light up, you only need 9 bits to
represent any 8 bit by 8 bit addition or subtraction. This is because when the value
would be negative, dropping the leading 1's doesn't change its value.
Finally, change the - to * to test out multiplication.
Au
Cu
Mojo
36result = io_dip[1] * io_dip[0]; // multiply the switch values
You should notice that multiplying two numbers can generate a much bigger result than
the operands. The result will generally be twice the size of the inputs. So in our case, up
to 16 bits wide.
Multiplication is much more expensive to perform than a simple addition or subtraction.
This is because a multiplication is essentially many additions. Multiplying two 8 bit
numbers will result in a series of eight additions.
Also note that we didn't talk about division. This is because there isn't a division
operator. Division is much more complicated than even multiplication and there are
some trade offs you need to decide for your design so a default option doesn't make
sense. If you don't need real division, you can approximate it by multiplying with
fractions.
To approximate division, you can first multiply by any number and then shift the value to
the right. Shifting to the right effectively divides by a power of two (rounding down). For
example, if you want to divide a number by 3, you could multiply by 85 then shift right 8
bits. This will effectively multiply your value by 85/256 = 0.33203 which is pretty close to
1/3. If you need higher precision, you can use more bits. For example, you could
instead multiply by 21845 and shift 16 bits to the right. This is effectively multiplying by
0.333328, but 16 bit multiplication is significantly more expensive than 8 bit.
Seven‐Segment Displays
The Io Element has four seven-segment displays that are multiplexed. This means that
we have two groups of signals, the first, io_seg, connects to the segments of each
display.
Note that even though we call these seven-segment displays there are actually 8 LEDs
in each one because of the decimal point.
All four displays have all their segments connected together. That means if you apply
power to segment 0, all four displays segment 0 receive power! This is done to save on
the number of pins required to drive the displays. However, it means that we need a
way to disable all but one display so that we can show a unique number on each one.
This is where the second group of signals, io_sel, comes in handy. These are used to
select which digit is active. Typically, only one digit will be active at any time. If you have
more than one digit active, all active digits will show exactly the same thing since their
segments are wired together.
The way we are going to display four unique numbers is by cycling which digit is active
really fast. If we can do this fast enough, our eyes can't tell and it looks like all four digits
are always on.
However, before we do that, let's play around with the displays a bit to get familiar with
how they work.
Indexing Segments
We are going to setup some modules so that we light one segment at a time. To do this
we will be using some of the components built into Alchitry Labs.
Click Project->Add Components.... Under Miscellaneous check
off Counter and Decoder. Click Add to add them to your project.
You can now find the source for these components under Components in the left
project tree in Alchitry Labs. Note that while you are free to look at the source,
components are read-only (no editing!). If you ever want to modify a component, simply
copy and paste the code into a module of your own.
It's always a good idea to check out the source and read the description of the
component. Many components have parameters that you can set to customize them for
your use. We'll be doing exactly that with these two.
Au
Cu
Mojo
16
.clk(clk) {
// The reset conditioner is used to synchronize the reset signal to the
17 FPGA
18 // clock. This ensures the entire FPGA comes out of reset at the same
time.
19
reset_conditioner reset_cond;
20
21
.rst(rst) {
counter ctr (#SIZE(3), #DIV(25));
22
23 }
}
24
25
decoder num_to_seg (#WIDTH(3));
26
Here we add the counter and decoder modules to the top level module.
The counter component requires both a clock and reset signal so we have it in the
nested in both the .clk and .rst connection blocks. On the other hand, decoder doesn't
require either, so it is outside of both.
The counter is simply a counter. It will output a value starting from zero and increment
to whatever the maximum value you set is. After that it will reset back to zero. It is also
possible to configure it to count down from the max value to zero.
The counter has four parameters you can set to get it to operate how you want, but here
we only need to change two. SIZE dictates how wide the output of the counter is. In this
case we need it to count from 0 to 7 for all 8 segments of our displays. Three bits is
exactly 0 - 7. If we wanted to count to a value that wasn't conveniently a power of 2
minus one, we could set the parameter TOP to control its maximum value.
The DIV parameter is used to set how many bits to use to divide the clock. By setting
this to 25 (or 24 on the Mojo), the counter will only increment its output every 225, or
33,554,432, clock cycles (16,777,216 on the Mojo). The clock runs at 100MHz
(100,000,000 cycles per second) on the Au and Cu and 50MHz (50,000,000 cycles per
second) on the Mojo. If the counter incremented every clock cycle it would be way too
fast for us to see.
The decoder is a binary to one-hot decoder. If you need a refresher on one-hot
representation check out the encodings tutorial. Basically, the decoder will take our
three bits and convert it to eight bits. If the input is 0, then the 0th bit of the output will be
1 and everything else will be 0. If the input is 4, the 4th bit will be 1 and everything else
will be 0. This allows us to use the binary counter to light individual LEDs.
Note that the output of the decoder is 2WIDTH bits wide. So in our case, 23 = 8 bits wide.
Now we can hook up the modules.
Au
Cu
Mojo
30always {
31 reset_cond.in = ~rst_n; // input raw inverted reset signal
32 rst = reset_cond.out; // conditioned reset
33
34 usb_tx = usb_rx; // loop serial port
35
36 led = 8h00; // turn LEDs off
37
38 num_to_seg.in = ctr.value;
39
40
41
42
43
44
io_seg = ~num_to_seg.out; // connect segments to counter
io_sel = 4h0; // select all digits
result = io_dip[1] * io_dip[0]; // multiply the switch values
io_led = $build(result, 3); // convert result from a 24-bit array to a 3x8
45 array
46 }
We feed the binary counter value into the decoder and the output of the decoder
to io_seg. Note that io_seg and io_sel are both active low. This means that when the
signal is 0, it is active. To turn only one LED on we need to invert the output of the
decoder with the bit-wise inversion, ~, operator. This will make the signal zero-hot.
Also notice we need to set io_sel to 0 so that the LED segments are selected. Setting all
of them to 0 will turn them all on.
Build and load your project to your board. You should now see the segments of all 4
displays lighting one at time. Try to light only a single display by
setting io_sel to ~4h1 instead.
Now what if we wanted to cycle through each digit? We need another counter that
increments each time the original counter overflows. That way once each LED of a
specific digit is lit, the next digit is selected. However, we don't actually need another
counter to do this! We can simply make our existing counter 2 bits wider. By adding
extra bits, these will increment only when the lower three bits overflow, creating the
behavior we want.
Au
Cu
Mojo
16.clk(clk) {
// The reset conditioner is used to synchronize the reset signal to the
FPGA
17
18 // clock. This ensures the entire FPGA comes out of reset at the same
time.
19
reset_conditioner reset_cond;
20
21
.rst(rst) {
22
counter ctr (#SIZE(5), #DIV(25));
23
}
24
}
25
26 decoder num_to_seg (#WIDTH(3));
27 decoder num_to_digit (#WIDTH(2));
Here we increase the SIZE of ctr to 5. We also add another decoder with WIDTH of 2.
We need 2 since we have 4 digits to select from and an input of 2 bits gives us possible
values of 0-3.
Au
Cu
Mojo
31always {
32 reset_cond.in = ~rst_n; // input raw inverted reset signal
33 rst = reset_cond.out; // conditioned reset
34
35 usb_tx = usb_rx; // loop serial port
36
37 led = 8h00; // turn LEDs off
38
39 num_to_seg.in = ctr.value[2:0]; // lower three bits used for segments
40 num_to_digit.in = ctr.value[4:3]; // upper two bits used for digits
41
42 io_seg = ~num_to_seg.out; // connect segments to counter
43 io_sel = ~num_to_digit.out; // connect digit select to counter
44
45
46
result = io_dip[1] * io_dip[0]; // multiply the switch values
io_led = $build(result, 3); // convert result from a 24-bit array to a 3x8
47 array
48 }
We can select the lower three bits of ctr.value by using the bit selector [2:0]. This says
we want bits 2 down-to 0 inclusive. We can do the same thing for the upper two bits.
Build and load your project again. This time you should see only one digit and one
segment lit at any time. Once each segment of a digit has been lit, the next digit is
selected.
You may have also noticed that the segments are now much brighter. This is because
the resistor that is in-line with the LEDs is sized for a single LED, not all four. When you
have all four on at the same time, the current is split between them making them all
dimmer.
Getting Fancy With Numbers
Lighting up the segments is super cool and all, but it's much cooler to actually show
some numbers.
Before we jump into multiplexing, we need a way to convert a number into the segments
that need to be lit to represent that number. To do this we can use a look-up table.
Create a new module called seven_seg and add the following.
1
2 module seven_seg (
input char[4],
3
output segs[7]
4
) {
5
6
always {
7
case (char) {
8
0: segs = 7b0111111;
9
1: segs = 7b0000110;
10
2: segs = 7b1011011;
11
3: segs = 7b1001111;
12
4: segs = 7b1100110;
13
5: segs = 7b1101101;
6: segs = 7b1111101;
14
7: segs = 7b0000111;
15
8: segs = 7b1111111;
16
9: segs = 7b1100111;
17
18
default: segs = 7b0000000;
}
19 }
20 }
21
This module will take a binary number, char, and output the segments, segs, that need
to be on.
This modules uses a case statement. Case statements are equivalent to a bunch
of if statements. One of the entries will be selected based on the value of char. If char is
1, then only the block after the 1: is used. If none of the blocks match, the default: block
is used. The default block is optional, but it is generally a good idea to include it even if
you think you have all the cases covered.
If char is an invalid number (not 0-9), then all the segments will be off.
To test out our look-up table, let's connect it to our counter.
Au
Cu
Mojo
16.clk(clk) {
// The reset conditioner is used to synchronize the reset signal to the
FPGA
17
18 // clock. This ensures the entire FPGA comes out of reset at the same
time.
19
reset_conditioner reset_cond;
20
21
.rst(rst) {
22
counter ctr (#SIZE(4), #TOP(9), #DIV(25));
23
}
24 }
25
26 seven_seg seg;
We need ctr to count from 0-9 so we set TOP to 9 to cap its value. Also we need to
change SIZE to 4, as we only need 4 bits to represent 0-9.
Because we don't need the decoders anymore, we can remove them and add
our seven_seg module.
Finally, we need to wire it up the LEDs
Au
Cu
Mojo
30always {
31 reset_cond.in = ~rst_n; // input raw inverted reset signal
32 rst = reset_cond.out; // conditioned reset
33
34 usb_tx = usb_rx; // loop serial port
35
36 led = 8h00; // turn LEDs off
37
38 seg.char = ctr.value;
39
40 io_seg = ~seg.segs; // connect segments to counter
41 io_sel = ~4h1; // first digit only
42
43
44
result = io_dip[1] * io_dip[0]; // multiply the switch values
io_led = $build(result, 3); // convert result from a 24-bit array to a 3x8
45 array
46 }
io_sel is set to ~4h1 so that only the right most digit is on.
Build and load the project onto your board to make sure the first digit is correctly
counting from 0 to 9.
Now we need to make a module that will take four values in and display them on all four
digits instead of only using one.
Create a new module called multi_seven_seg and add the following code.
1 module multi_seven_seg #(
2
DIGITS = 4 : DIGITS > 0,
3
DIV = 16 : DIV >= 0
)(
4
5
input clk, // clock
6
input rst, // reset
7
input values [DIGITS][4], // values to show
8
output seg [7], // LED segments
9
output sel [DIGITS] // Digit select
10
) {
11
12
// number of bits required to store DIGITS-1
const DIGIT_BITS = $clog2(DIGITS);
13
14
.clk(clk), .rst(rst) {
15
16
counter ctr (#DIV(DIV), #SIZE(DIGIT_BITS), #TOP(DIGITS-1));
}
17
18
seven_seg seg_dec; // segment decoder
19 decoder digit_dec (#WIDTH(DIGIT_BITS)); // digit decoder
20
21 always {
22
seg_dec.char = values[ctr.value]; // select the value for the active
digit
23
seg = seg_dec.segs; // output the decoded value
24
25
digit_dec.in = ctr.value; // decode active digit to one-hot
26
sel = digit_dec.out; // output the active digit
27
}
28
}
29
This module is parameterized so that it could be used to drive more or less than 4 digits,
but the defaults are set to work well with our project.
The constant DIGIT_BITS is the number of bits we need to cover all DIGITS number of
digits. We use the function $clog2() which computes the ceiling log base 2 of a constant
value.
We only want the counter to count from 0 to DIGITS-1 so we set the TOP parameter
accordingly.
DIV is used so that we don't switch between the digits too fast. If we switch too fast, the
transistors that drive the LEDs don't have time to fully turn off and we get bleed between
digits.
The way this module works is ctr is used to select the active digit. The value
from values is then selected, decoded, and sent to the segments of the LEDs displays.
The binary value of the active digit is then decoded into a one-hot value as before and
used to select which digit is on. Because ctr keeps cycling the active digits, all the
displays will appear on.
We can tweak DIV, but if it is set too high, we will start to see the digits flicker. If it is set
too low, we will see bleeding between digits.
Let's test out this module by feeding it some constant numbers to show.
Au
Cu
Mojo
16
.clk(clk) {
// The reset conditioner is used to synchronize the reset signal to the
17 FPGA
18 // clock. This ensures the entire FPGA comes out of reset at the same
time.
19
reset_conditioner reset_cond;
20
21
.rst(rst) {
22
multi_seven_seg seg;
23 }
24 }
We don't need the counter or the seven_seg modules from before, but we need
the multi_seven_seg now.
Au
Cu
Mojo
28always {
29 reset_cond.in = ~rst_n; // input raw inverted reset signal
30 rst = reset_cond.out; // conditioned reset
31
32 usb_tx = usb_rx; // loop serial port
33
34 led = 8h00; // turn LEDs off
35
36
37
38
39
40
41
42
seg.values = {4h8,4h5,4h3,4h1};
io_seg = ~seg.seg; // connect segments to the driver
io_sel = ~seg.sel; // connect digit select to the driver
result = io_dip[1] * io_dip[0]; // multiply the switch values
io_led = $build(result, 3); // convert result from a 24-bit array to a 3x8
43 array
44 }
On the line starting with seg.values, we feed the values to display into the driver.
Build and load the design to your Mojo to make sure each digit is displaying the correct
number.
Decimal Counters
Alright, now that we have a way to show a value on our display, let's make a the display
count! For this we need a decimal counter. We could use a regular counter and convert
the binary value into four decimal values, but that's a bit trickier. Instead, we will create
a special counter that counts in base 10.
To do this we will create two modules. The first will be a single digit counter and the
second will chain these together for a multi-digit counter.
Create a new module called decimal_counter with the following code.
1
module decimal_counter (
input clk, // clock
2
input rst, // reset
3
input inc, // increment the counter
4
output ovf, // counter overflowed
5
output value[4] // current value
6
) {
7
8
.clk(clk), .rst(rst) {
9
dff val[4]; // value storage
10
11
12
13
}
always {
value = val.q; // output the value
14
15
ovf = val.q == 9 && inc; // if max value and incrementing, overflow!
16
17
18
if (inc) { // should add 1
if (val.q == 9) // if max value
19
val.d = 0; // reset to 0
20
else // otherwise
21
val.d = val.q + 1; // add one
}
22
23 }
}
24
25
Our decimal counter has an input inc that is used to signal when the value should be
increased. When the digit is about to overflow to 0, the flag ovf is set to 1. The current
value of the counter is output on value.
We now need to chain these together to make a multi-digit counter.
Create a new module named multi_dec_ctr and add the following code.
1
module multi_dec_ctr #(
DIGITS = 4 : DIGITS >= 2 // number of digits
2
) (
3
input clk, // clock
4
input rst, // reset
5
input inc, // increment counter
6
output digits[DIGITS][4] // digit values
7
) {
8
9
.clk(clk), .rst(rst) {
10
11
decimal_counter dctr [DIGITS]; // digit counters
}
12
13 always {
14
dctr.inc[0] = inc; // increment the first digit
15
digits = dctr.value; // output the values
16
// if the previous digit overflows, increment the next
17
dctr.inc[1+:DIGITS-1] = dctr.ovf[0+:DIGITS-1];
18
19
}
}
20
21
Here we take DIGITS decimal_counters and chain them together. Each
counter inc signal is connected to the previous counter's ovf signal. This means that
once a counter overflows from 9 to 0, the next one will be incremented. Of course, the
first counter doesn't have a previous counter's ovf signal, so we use an external
increment signal instead.
We use the bit selectors [1+:DIGITS-1] and [0+:DIGITS-1] to select bits
from dctr.inc and dctr.ovf. What [1+:DIGITS-1] means is starting from bit 1,
select DIGITS-1 bits going up. This is an easy way to select a specified number of bits
given some start index. You can use -: instead of +: to select bits going down from the
given start index instead of going up.
All that is left now is to generate the inc signal and connect the counter to our display
driver.
The inc signal needs to be a pulse that stays high for only one clock cycle every time we
want to increment our counter. To create this we are going to use the counter
component as before, but there is a problem with this. If we create a counter that only
outputs one bit, the bit will be high for have of the time and not just one cycle at a time.
To fix this we will use another component, the Edge Detector.
Go into the component selector and add the Edge Detector to your project. You can find
it under Miscellaneous.
This component takes a signal and sends out a pulse when it detects a rising, falling, or
either type of edge. You can configure which edges you care about.
Let's add all the modules to mojo_top.
Au
Cu
Mojo
16.clk(clk) {
17 // The reset conditioner is used to synchronize the reset signal to the
FPGA
18
// clock. This ensures the entire FPGA comes out of reset at the same
time.
19
20
reset_conditioner reset_cond;
21
22
edge_detector edge_detector (#RISE(1), #FALL(0));
23
.rst(rst) {
24
25
26
multi_seven_seg seg;
multi_dec_ctr dec_ctr;
counter ctr (#SIZE(1), #DIV(24));
27 }
28 }
For the edge detector, we set RISE to 1 and FALL to 0. This means we will generate a
pulse only on rising edges.
We now have the multi-digit driver, the multi-digit counter, and a way to generate a
pulse from the counter.
Let's wire them all up!
Au
Cu
Mojo
32always {
33 reset_cond.in = ~rst_n; // input raw inverted reset signal
34 rst = reset_cond.out; // conditioned reset
35
36 usb_tx = usb_rx; // loop serial port
37
38 led = 8h00; // turn LEDs off
39
40 edge_detector.in = ctr.value;
41 dec_ctr.inc = edge_detector.out;
42
43
44
seg.values = dec_ctr.digits;
io_seg = ~seg.seg; // connect segments to the driver
io_sel = ~seg.sel; // connect digit select to the driver
45
46
result = io_dip[1] * io_dip[0]; // multiply the switch values
47
48
io_led = $build(result, 3); // convert result from a 24-bit array to a 3x8
array
49
}
50
The value from the counter is fed into the edge detector. The pulse from the edge
detector is fed into the decimal counter. The decimals from the decimal counter are sent
to the multi-digit driver. Finally, the multi-digit driver is connected to the actual display.
This is what is so cool about FPGAs. Each one of these modules operates completely
independently from one another since they all exist in hardware!
This example is fairly complicated, but by breaking down the problem in little chunks
(modules) we were able to create a design that is relatively easy to understand and
flexible for future upgrades.
Now for the moment of truth, build and load the project onto your board! With any luck,
you should see the seven-segment display counting!
If you want to make it count slower or faster you can mess with ctr's DIV value.
https://www.sparkfun.com/products/15849_https://alchitry.com/blogs/tutorials/io-element_1-6-20