Abstract: This ’blog describes using a ValentF(x) LOGI-EDU board to make a 4-digit BCD (binary-coded decimal) counter using LOGI-EDU’s 4-digit seven-segment LED module. I used both LOGI-Pi and LOGI-Bone FPGA boards to implement the BCD counter logic.
Disclaimer: This ’blog is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. Incorrect board connections and/or an incorrect FPGA bitstream could cause damage to an FPGA and/or its connected circuitry, or cause a system to malfunction. Users must agree that the author has NO LIABILITY for any damages.
This is my third ’blog about ValentF(x) LOGI boards. To get started using LOGI FPGA boards, see my first two LOGI ’blogs First Experiments with the ValentF(x) LOGI-Bone and First Experiments with the ValentF(x) LOGI-Pi, and also the ValentF(x) Logi-Bone Quick Start Guide and LOGI-Pi Quick Start Guide.
The ValentF(x) LOGI-EDU is an educational expansion card for LOGI-Pi, though it can also be used with LOGI-Bone if you don’t need multiple features simultaneously. LOGI-EDU has a number of useful I/O devices including a VGA output with up to 512 colors, a multiplexed 4-digit seven-segment display, two PS/2 ports, two Nintendo NES controller ports, and an audio output using PWM or sigma-delta modulation. There’s also a small prototyping area. Here’s a picture of the board from the ValentF(x) wiki, copied with permission:
LOGI-EDU is primarily for LOGI-Pi, and element14 currently only sells it as a package with a LOGI-Pi. LOGI-EDU plugs into LOGI-Pi’s four Digilent Pmodsockets:
The Pmods are numbered 1-4 from left to right in the photos and connect to LOGI-EDU resources as follows:
- Pmod 1: VGA and +3.3V power from LOGI-Pi.
- Pmod 2: Seven-segment display.
- Pmod 3: Seven-segment display plus PS/2 #2 and VGA LSbs.
- Pmod 4: PS/2 #1, NES, and PWM.
All four Pmods provide two ground pins. For details on how the Pmods are connected, see the LOGI-EDU schematic.
You can use LOGI-EDU with LOGI-Bone, but since LOGI-Bone only has two Pmod sockets you can only use two adjacent LOGI-EDU plugs. In this ’blog, we use the seven-segment display so we need LOGI-EDU Pmods 2 and 3. They plug very nicely into the two LOGI-Bone sockets:
All power for the LEDs comes from FPGA I/Os so we don’t need the LOGI-EDU Pmod 1 +3.3V pins.
The seven-segment display is a fairly common LITEON LTC-4727JS: here’s the data sheet. The digits are numbered 1-4 from left to right, and the segments of each digit are numbered A-G in the usual way (see Wikipedia):
Here is part of the LOGI-EDU schematic, which shows how the display is wired up:
The LTS-4727JS is a multiplexed display [Wikipedia] to save pins, both on the display and the FPGA. Only one digit is turned on at a time, but they are refreshed quickly enough that normal eyes see all digits displayed at once.
The LTS-4727JS is a common cathode display, which means that the LEDs of a digit (including decimal point DP) have their cathodes wired together. To turn on a digit, the FPGA pulls a combination of anode signals SSEG0-A, SSEG1-B, ..., SSEG7-DP to +3.3V and also drives one digit select SEG_CTRL_A1, SEG_CTRL_A2, ..., SEG_CTRL_L high as well, setting the rest low. The selected SEG_CTRL turns on one transistor Q1-Q5, which pulls one common cathode pin A1-A4 or L low. This creates a current path from each high SSEG line to ground, turning on the selected segments of the selected digit.
Each SSEG line has a 100Ω resistor to limit current to about 10 mA. This would usually be too bright, but in normal operation each digit is only on 1/4 of the time so 10 mA gives good brightness. Since the selected digit’s segments including decimal point could all be on simultaneously, the combined cathode current could be as high as 80 mA. This is why a transistor pulls the common cathode down. An FPGA pin cannot sink that much current.
4-Digit BCD Counter
Now that we’re familiar with the display, let’s use it to make a 4-digit BCD counter with these features:
- Increment every tenth of a second.
- Display seconds and tenths of seconds in decimal from 0.0 to 999.9, wrapping every 1000 seconds.
- Display decimal point and suppress leading zeros.
- PB0 resets the counter to 0.0.
- PB1 holds the counter at its current value.
PB0 and PB1 have the same behavior as the 4-bit binary counter in First Experiments with the ValentF(x) LOGI-Bone and First Experiments with the ValentF(x) LOGI-Pi.
The Verilog code for the BCD counter is in LOGIdemo.v (part of LOGIdemo.zip) along with other sample code. Let’s take a look the Verilog modules that make up the counter, starting with SevenSeg which converts a BCD digit to its seven-segment representation:
module SevenSeg(X, Y);
input [3:0] X; // BCD digit.
output [6:0] Y; // Seven-segment code "ABCDEFG".
assign Y = X == 0? 7'b1111110: X == 1? 7'b0110000:
X == 2? 7'b1101101: X == 3? 7'b1111001:
X == 4? 7'b0110011: X == 5? 7'b1011011:
X == 6? 7'b1011111: X == 7? 7'b1110000:
X == 8? 7'b1111111: X == 9? 7'b1110011: 7'h00;
endmodule
Input X is a 4-bit BCD digit from 0000 to 1001. Output Y is a 7-bit seven-segment display code ABCDEFG with MSb A and LSb G. The body is simply a multiplexer. Xilinx logic synthesis converts it into a ROM look-up table.
Module DigitCounter is a divide-by-10 counter:
module DigitCounter(Mclk, reset, Cin, Cout, D);
input Mclk; // 50 MHz master clock.
input reset; // Asynchronous reset.
input Cin; // Carry in pulse.
output Cout; // Carry out if carry in and D = 9.
output [3:0] D; // Current value of digit.
reg [3:0] D; // Digit counter from 0 to 9.
assign Cout = Cin && D >= 9;
always @(posedge Mclk or posedge reset)
if (reset) D <= 0; else if (Cin) D <= Cout? 0: D+1;
endmodule
Mclk is the LOGI-Pi or LOGI-Bone 50 MHz master clock. It runs continuously. Cin is a “carry in” pulse that is high for one 20 ns clock cycle to increment the 4-bit counter. Cout is a “carry out” pulse that occurs when Cin is high and the current count D is 9 or more: Cout clears D to 0. D should never exceed 9, but “D >= 9” gets us out of a bad state quicker if D is ever reset to 10-15. D is a module output so we can display it. Finally, there’s an asynchronous reset to force D to zero immediately if you press PB0.
Module ClockDiv2K generates a single-cycle LEDclk pulse every 500 ms (2 KHz):
// Divide 50 MHz master clock by 25,000 to get 2 KHz LED
// multiplexer clock.
module ClockDiv2K(Mclk, LEDclk);
input Mclk; // 50 MHz master clock.
output LEDclk; // 2 KHz LED mux clock (clock-enable pulses).
reg LEDclk;
reg [14:0] Q; // Divide by 25,000 counter.
wire [15:0] nextQ = Q + 1;
wire Qcarry = nextQ[15]; // Carry out, pulses at 2 KHz.
always @(posedge Mclk)
begin
// Divide by 25,000: when nextQ has carry out,
// set Q = -25,000 = 32,768-25,000 = 7,768.
// Qcarry is sync reset.
if (Qcarry) Q <= 15'd7768; else Q <= nextQ[14:0];
LEDclk <= Qcarry;
end
endmodule
This counter uses the same technique as module ClockDiv in First Experiments with the ValentF(x) LOGI-Bone and First Experiments with the ValentF(x) LOGI-Pi. This is a rather unusual way to express a counter, but it maps well to Xilinx logic. People usually make a divide-by-n counter by resetting it to 0 and then detecting when it reaches n−1, as we did with DigitCounter. However, this requires logic to compare to n−1. So instead we preset the counter to −n and increment it until it reaches all ones, which we detect using the carry out of the counter’s adder chain.
Now that we have all the submodules, here’s the root module SevenSegDemo. First let’s take care of declaring the I/O pins and converting active-low PB0 to active-high reset:
module SevenSegDemo(Mclk, PB0, PB1, Y, dp, en1, en2, en3, en4);
input Mclk; // 50 MHz master clock.
input PB0; // Press to reset = active-low.
input PB1; // Press to hold = active-high enable.
output [6:0] Y; // Seven-segment code "ABCDEFG".
output dp; // Turn on decimal point for digit 3.
output en1, en2; // Enables for digits 1-4 (left to right).
output en3, en4;
wire reset = !PB0; // PB0 is active-low.
Next we need a divide-by-200 counter JK to convert the 2 KHz LEDclk produced by ClockDiv2K into a 10 Hz carry into the tenths digit.
wire LEDclk; // 2 KHz LED mux clock (clock-enable pulses).
reg [1:0] K; // Counter LSbs;
reg [5:0] J; // Counter MSbs.
wire [6:0] nextJ = J + (LEDclk && K == 3 && PB1);
wire resetJ = reset || nextJ[6]; // Synchronous reset.
Counter JK comes in two parts. The MSbs J[5:0] are a divide-by-50 counter which is reset by PB0 and held if you press PB1. The LSbs K[1:0] select a digit for LED multiplexing. K must increment continuously since multiplexing still needs to occur when pressing PB0 or PB1. K simply increments whenever LEDclk occurs (see below). J increments when K overflows and PB1 is not pressed, i.e., counting is enabled. J uses the same divide-by-n logic style as ClockDiv2K.
Here are the rest of the signal declarations. c1-c4 are the carries into digits 1 (MSD) through 4 (LSD). c4 is the carry out from counter JK.
wire c4 = nextJ[6]; // Carry into digit 4 (tenths).
wire c1, c2, c3; // Carry into digit 1-3.
wire dummy; // Carry out of digit 1 (unused).
wire [15:0] D; // Values of digits 1-4.
wire [3:0] Dsel; // Digit selected by K.
Here the instance of ClockDiv2K, followed by the logic to update counter JK:
ClockDiv2K cd(Mclk, LEDclk);
always @(posedge Mclk)
begin
// Always increment JK counter LSbs since they multiplex the digits.
if (LEDclk) K <= K+1;
// Reset JK counter MSbs if PB0 to get 500us reset response.
// Divide J by 50: when nextJ has carry out, set J = -50 = 64-50 = 14.
if (resetJ) J <= 6'd14; else J <= nextJ[5:0];
end
Next we have four instances of DigitCounter, one for each digit. The carry out of each digit is the carry in of the next higher digit. The four 4-bit digit values are combined into a 16-bit signal D[15:0].
// Digit counters for digits 1-4, starting with digit 4.
DigitCounter dc4(Mclk, reset, c4, c3, D[3:0]); // tenths
DigitCounter dc3(Mclk, reset, c3, c2, D[7:4]); // seconds
DigitCounter dc2(Mclk, reset, c2, c1, D[11:8]); // tens
DigitCounter dc1(Mclk, reset, c1, dummy, D[15:12]); // hundreds
// Select a digit using K.
assign Dsel = K == 0? D[3:0]: K == 1? D[7:4]: K == 2? D[11:8]: D[15:12];
Dsel is the digit of D[15:0] selected by K using a multiplexer. Module SevenSeg converts it to seven-segment code Y:
SevenSeg ss(Dsel, Y);
The decimal point is a special case: it’s turned on if we’re doing digit 3 (K = 1):
assign dp = K == 1;
The final logic enables each digit, according to the value of K. Digits 3 and 4 are always shown, so we just need to check K. Digits 1 and 2 are more interesting because we suppress leading zeros:
assign en4 = K == 0; // Always show digit 4.
assign en3 = K == 1; // Always show digit 3.
// Do not show d2 if digits 1 and 2 are both 0.
assign en2 = K == 2 && D[15:8] != 0;
// Do not show d1 if it's 0.
assign en1 = K == 3 && D[15:12] != 0;
endmodule
Running Xilinx ISE
Now that we have the Verilog code, we can compile it using Xilinx ISE (Integrated Software Environment). I’m using the ISE 12.4 free-as-in-beer WebPACK Editon on Ubuntu 12.04 LTS, which mostly works except for some graphical tools I don’t need. I’ve created an ISE project named LOGIdemo in directory LOGI, so the many files ISE generates -- including the bitstream -- will be in LOGI/LOGIdemo.
I’m not going to tell you how to install or use the Xilinx tools since that’s a long procedure and can be found elsewhere, for example Logi-Pi Quick Start Guide. Gadget Factory also has some tutorials: Install ISE WebPack and Papilio Xilinx ISE WebPack VHDL Getting Started.
SevenSegDemo synthesizes without any errors on ISE 12.4 and the warnings are reasonable. One of the hardest tasks for a new ISE user is learning which warnings can be ignored and which are important.
Now, before implementing the design you need to tell ISE how to assign signals to pins. This is fairly tedious for LOGI-EDU, because you must figure out how seven-segment display pins connect to LOGI-EDU Pmod pins using the LOGI-EDU schematic, and then use the LOGI-Pi and/or LOGI-Bone schematics to see how their Pmod pins connect to their FPGA pins. LOGI-Bone has the added complication that we’re plugging LOGI-EDU Pmods 2 and 3 into LOGI-Bone Pmods 1 and 2.
There’s a graphical tool for assigning pins in ISE, but IMO it’s a lot easier to create and edit a User Constraint File (UCF). For this ’blog I created two versions of the UCF, one for LOGI-Pi and one for LOGI-Bone. Here’s SevenSegPi.ucf for LOGI-Pi:
NET Mclk LOC="P85" | PERIOD=20ns;
NET PB0 LOC="P102"; # PB0 = press to reset
NET PB1 LOC="P101"; # PB1 = press to hold
# PMOD2
NET en2 LOC="P142"; # PMOD2-1
#NET enL LOC="P141"; # PMOD2-2
NET en3 LOC="P15"; # PMOD2-3
NET en4 LOC="P14"; # PMOD2-4
NET en1 LOC="P144"; # PMOD2-7
NET Y<3> LOC="P143"; # PMOD2-8
NET Y<2> LOC="P140"; # PMOD2-9
NET dp LOC="P139"; # PMOD2-10
# PMOD3
NET Y<0> LOC="P138"; # PMOD3-1
NET Y<4> LOC="P137"; # PMOD3-2
#NET xxx LOC="P124"; # PMOD3-3
#NET xxx LOC="P123"; # PMOD3-4
NET Y<5> LOC="P119"; # PMOD3-7
NET Y<6> LOC="P118"; # PMOD3-8
NET Y<1> LOC="P117"; # PMOD3-9
#NET xxx LOC="P116"; # PMOD3-10
Here’s SevenSegBone.ucf for LOGI-Bone:
NET Mclk LOC="P85" | PERIOD=20ns;
# Note: PB0 and PB1 are swapped in the R1.0 schematics, sheet 6.
NET PB0 LOC="P59"; # PB0 = press to reset
NET PB1 LOC="P83"; # PB1 = press to hold
# Use LOGI-Bone PMOD1 to talk to LOGI-EDU PMOD2.
NET en2 LOC="P112"; # PMOD1-1
#NET enL LOC="P111"; # PMOD1-2
NET en3 LOC="P67"; # PMOD1-3
NET en4 LOC="P66"; # PMOD1-4
NET en1 LOC="P62"; # PMOD1-7
NET Y<3> LOC="P61"; # PMOD1-8
NET Y<2> LOC="P58"; # PMOD1-9
NET dp LOC="P57"; # PMOD1-10
# Use LOGI-Bone PMOD2 to talk to LOGI-EDU PMOD3.
NET Y<0> LOC="P56"; # PMOD2-1
NET Y<4> LOC="P55"; # PMOD2-2
#NET xxx LOC="P46"; # PMOD2-3
#NET xxx LOC="P45"; # PMOD2-4
NET Y<5> LOC="P48"; # PMOD2-7
NET Y<6> LOC="P47"; # PMOD2-8
NET Y<1> LOC="P44"; # PMOD2-9
#NET xxx LOC="P43"; # PMOD2-10
Note that the pin numbers are completely different from LOGI-Pi except for Mclk.
Attach SevenSegPi.ucf or SevenSegBone.ucf -- whichever matches your LOGI FPGA board -- to root module SevenSegDemo using ISE’s Add Source command. Then run the ISE Implementation tools. This takes about a minute on my PC. There should be no errors and no significant warnings.
When placement and routing is done, I recommend checking the Pinout Report to make sure the pins agree with the UCF file.
The last step is to generate the bitstream. The default options are mostly OK, but we’re going to set the Drive Done option so the Done LED is more visible. Watch the ISE console log to see when it’s done generating the bitstream.
When ISE is done, copy SevenSegDemo.bit to Raspberry Pi and LOGI-Pi, or to BeagleBone and LOGI-Bone. This is described in First Experiments with the ValentF(x) LOGI-Bone and First Experiments with the ValentF(x) LOGI-Pi. When the transfer is successful, you’ll see the LED display start counting merrily from 0.0 to 999.9, and repeat. If you push PB0, the counter resets to 0.0. If you push PB1, the counter holds its current value.
The Seven-Segment BCD Counter source files LOGIdemo.v and UCFs are available in LOGIdemo.zip, along with SevenSegPi.bit and SevenSegBone.bit which are renamed copies of SevenSegDemo.bit implemented for LOGI-Pi and LOGI-Bone.
Conclusion
This was my first experience with LOGI-EDU and it went very smoothly. The board is well-designed and well-made, and provides many useful capabilities. I did have to cut a small notch in the front panel of my home-made open-frame RasPi case so that I could plug in LOGI-EDU. With four Pmod connectors it’s hard to push LOGI-EDU and LOGI-Pi together and even harder to pull them apart. Using various size screwdrivers as levers worked well. It’s much easier with the LOGI-Bone since there are only two Pmod connectors.
This is a simple introduction to using LOGI-EDU, and only plays with the seven-segment LED display. There are lots of other interesting projects, especially with the VGA output. I’m looking forward to playing with VGA when I get a chance.
[This document is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. To view a copy of this license, visit creativecommons.org/licenses/by-sa/3.0/. No warranty is expressed or implied. Raspberry Pi is a trademark of the Raspberry Pi Foundation.]
Top Comments