Design 8 – Four digits
If you tried to get digits to display in all four locations, it probably didn’t work. You could light up each digit individually, but you couldn’t light them all up at the same time. The trick is to light them up one at a time at a very rapid rate. Then they will appear to be lit up simultaneously.
The circuit design (known as a multiplexed display) basically reduces the amount of wiring needed to light up everything. (It also saves power.) Instead of the 36 wires (9 wires per digit + decimal point) needed for a nonmultiplexing design, there are only 12 wires (8 shared wires + 1 wire per digit). The more digits you want to display, the bigger the savings. If you want more savings, look into Charlieplexing.
The design I am going to show was originally developed without modularization. After the initial design was finished, it was modularized for reuse with other projects. I am going to cheat here and show the modularization first. This is primarily to keep the presentation uncluttered.
We take Design 7 and add a Hex4Display module. We will move the led_en assignment, and the Bit4To7Seg module into the new module.
The remaining code is modified in the following way:
- The displayable counter is widened to 16-bits (the full counter is 40 bits).
- A vector of decimal points is created. Switches will be used to turn them on and off.
- The decimal point becomes part of the 7-segment vector, matching the port convention for N_LED_7S.
- Names have been changed.
The resulting “top level” module looks like this in VHDL:
-- Implements a 4-digit hexadecimal counter using oscillator clock
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity Hex4Counter1 is
port (
OSC_CLK: in std_logic;
SW3, SW2, SW1, SW0: in std_logic;
N_LED_7S: out std_logic_vector(7 downto 0);
N_LED_7S_EN: out std_logic_vector(3 downto 0)
);
end Hex4Counter1;
architecture Behavioral of Hex4Counter1 is
signal count: std_logic_vector(39 downto 0);
signal value: std_logic_vector(15 downto 0);
signal dp: std_logic_vector(3 downto 0);
signal led_7s: std_logic_vector(7 downto 0);
signal led_7s_en: std_logic_vector(3 downto 0);
begin
-- convert switch signals to decimal point signals
dp <= SW3 & SW2 & SW1 & SW0;
-- convert 7-segment LED signals to active low logic
N_LED_7S <= not led_7s;
N_LED_7S_EN <= not led_7s_en;
U_COUNTER :
process (OSC_CLK)
begin
if rising_edge(OSC_CLK) then
count <= count + 1;
end if;
end process;
-- grab the 16 most significant bits of the counter
value <= count(39 downto 24);
U_HEX4DISPLAY :
entity Hex4Display port map (
clk => OSC_CLK,
value16 => value,
dp4 => dp,
led_7s => led_7s,
led_7s_en => led_7s_en
);
end Behavioral;
And it looks like this in Verilog:
// Implements a 4-digit hexadecimal counter using oscillator clock
module Hex4Counter1Ver (
input OSC_CLK,
input SW3, SW2, SW1, SW0,
output [7:0] N_LED_7S,
output [3:0] N_LED_7S_EN
);
reg [39:0] count;
wire [15:0] value;
wire [3:0] dp;
wire [7:0] led_7s;
wire [3:0] led_7s_en;
// convert switch signals to decimal point signals
assign dp = { SW3, SW2, SW1, SW0 };
// convert 7-segment LED signals to active low logic
assign N_LED_7S = ~led_7s;
assign N_LED_7S_EN = ~led_7s_en;
always @(posedge OSC_CLK)
begin
count <= count + 1;
end
// grab the 16 most significant bits of the counter
assign value = count[39:24];
Hex4Display U_HEX4DISPLAY (
.clk(OSC_CLK),
.value16(value),
.dp4(dp),
.led_7s(led_7s),
.led_7s_en(led_7s_en)
);
endmodule
We're now ready to look at the Hex4Display module. Following is the VHDL code to get the same behavior as Design 7. The clk and dp ports are currently unused.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity Hex4Display is
port (
clk: in std_logic;
value16: in std_logic_vector(15 downto 0);
dp4: in std_logic_vector(3 downto 0);
led_7s: out std_logic_vector(7 downto 0);
led_7s_en: out std_logic_vector(3 downto 0)
);
end Hex4Display;
architecture Behavioral of Hex4Display is
signal digit: std_logic_vector(3 downto 0);
begin
digit <= value16(3 downto 0);
led_7s_en <= "0001";
led_7s(7) <= '0';
U_BIT4TO7SEG :
entity Bit4To7Seg port map (
digit => digit,
led_seg => led_7s(6 downto 0)
);
end Behavioral;
First, the single assignment led_7s_en <= "0001"; is replaced by a finite state machine (FSM). Our FSM is simple enough to use a counter as the state sequencer. The FSM is split into two parts (processes), one part is the state sequencer (U_FSM_UPDATE), and the other is the output generator (U_FSM_OUTPUT). The outputs are usually control signals used to drive other circuits.
A two-bit register is used for state. The when others clause in the case statement is redundant, but ISE complains about incompleteness if it's not present. Other development software may be smarter.
U_FSM_UPDATE :
process (clk)
begin
if rising_edge(clk) then
state <= state + 1;
end if;
end process;
U_FSM_OUTPUT :
process (state)
begin
case state is
when "00" =>
led_7s_en <= "0001";
when "01" =>
led_7s_en <= "0010";
when "10" =>
led_7s_en <= "0100";
when "11" =>
led_7s_en <= "1000";
when others =>
led_7s_en <= "0001";
end case;
end process;
When the change is made, the result is a display of four digits, all the same, all counting in sync. However, there is a flaw, and it shows up when we try to display different digits in each position. Let's remove the single assignment to digit, and add assignments to digit in the FSM. Each assignment selects a different set of bits to translate, depending on which 7-segment LED (selected by led_7s_en) will be lit.
U_FSM_OUTPUT :
process (state, value16)
begin
case state is
when "00" =>
led_7s_en <= "0001";
digit <= value16(3 downto 0);
when "01" =>
led_7s_en <= "0010";
digit <= value16(7 downto 4);
when "10" =>
led_7s_en <= "0100";
digit <= value16(11 downto 8);
when "11" =>
led_7s_en <= "1000";
digit <= value16(15 downto 12);
when others =>
led_7s_en <= "0001";
digit <= value16(3 downto 0);
end case;
end process;
The results look pretty bad on my board. My initial design went differently - I started out too slow, and then sped up the rate. The results looked good, so this is an indication that the above code is too fast. Like the counter we are displaying, we need to slow down the FSM rate. We know that we can see changes when we divide down by a 24-bit counter, so let's do that:
-- Add these signals...
-- signal clk_div: std_logic_vector(23 downto 0);
-- signal fsm_clk: std_logic;
U_CLK_DIV :
process (clk)
begin
if rising_edge(clk) then
clk_div <= clk_div + 1;
end if;
end process;
fsm_clk <= clk_div(23);
U_FSM_UPDATE :
process (fsm_clk)
begin
if rising_edge(fsm_clk) then
state <= state + 1;
end if;
end process;
You can see that each digit is displayed clearly, and that we are turning on each digit in sequence. By changing which bit the FSM clocks on, we change the speed of the scan. I achieved a "solid" display with the following bit assignment:
fsm_clk <= clk_div(17);
This is divide-by-218. However, since each individual digit is lit every fourth time, each digit repeats at the divide-by-220 rate, which is approximately 47.7 Hz. The blink rate was verified with an oscilloscope.
And finally, we hook up the decimal points:
U_FSM_OUTPUT :
process (state, value16, dp4)
begin
case state is
when "00" =>
led_7s_en <= "0001";
digit <= value16(3 downto 0);
led_7s(7) <= dp4(0);
when "01" =>
led_7s_en <= "0010";
digit <= value16(7 downto 4);
led_7s(7) <= dp4(1);
when "10" =>
led_7s_en <= "0100";
digit <= value16(11 downto 8);
led_7s(7) <= dp4(2);
when "11" =>
led_7s_en <= "1000";
digit <= value16(15 downto 12);
led_7s(7) <= dp4(3);
when others =>
led_7s_en <= "0001";
digit <= value16(3 downto 0);
led_7s(7) <= dp4(0);
end case;
end process;
Using the slide switches SW0 - SW3, you can turn each of the decimal points on or off.
The complete Verilog version follows. For this version, I added the dp signal because I couldn't split reg and wire usage of led_7s in the same manner as the VHDL code.
module Hex4Display (
input clk,
input [15:0] value16,
input [3:0] dp4,
output [7:0] led_7s,
output reg [3:0] led_7s_en
);
reg [17:0] clk_div;
wire fsm_clk;
reg [1:0] state;
reg [3:0] digit;
reg dp;
// clock divider for digit scanning
always @(posedge clk)
begin
clk_div <= clk_div + 1;
end
assign fsm_clk = clk_div[17];
// this updates (sequences) the FSM (digit scanner) state
always @(posedge fsm_clk)
begin
state <= state + 1;
end
// FSM output for digit scanner
always @(state, value16, dp4)
begin
case (state)
2'b00:
begin
led_7s_en <= 4'b0001;
digit <= value16[3:0];
dp <= dp4[0];
end
2'b01:
begin
led_7s_en <= 4'b0010;
digit <= value16[7:4];
dp <= dp4[1];
end
2'b10:
begin
led_7s_en <= 4'b0100;
digit <= value16[11:8];
dp <= dp4[2];
end
2'b11:
begin
led_7s_en <= 4'b1000;
digit <= value16[15:12];
dp <= dp4[3];
end
default:
begin
led_7s_en <= 4'b0001;
digit <= value16[3:0];
dp <= dp4[0];
end
endcase
end
assign led_7s[7] = dp;
Bit4To7Seg U_BIT4TO7SEG (
.digit(digit),
.led_seg(led_7s[6:0])
);
endmodule