Counters, Clock Dividers and the 7-segment Display#
How is time typically measured by electronic circuits?
Solution to Exercise 130
Quartz crystals and silicon oscillators output a swinging signal with a specific frequency. Electronic circuit can count the number of oscillations. If the number of oscillations correspond to the frequency (number of oscillations per second), then the circuit knows that a second have passed.
What is a master clock?
Solution to Exercise 131
Subsystems in an electronic circuit can work at different frequencies. Typically a precise clock source is used to create subclocks for these subsystems. This clock is called a master clock
Learning Goals#
Know how to describe counters in Verilog
Understand clock dividers and how to design them
Understand procedural statements in Verilog
Background#
Counters#
Synchronous Binary Counters#
What is a synchronous binary counter?
Solution to Exercise 132
A synchronous binary counter is typically based on \(n\) registers which are interconnected in such a way that the value output is incremented in a natural counting sequence after the activation of an enable or clock signal. They typically count up to \(2^n-1\).
Asynchronous Binary Counter#
What is the structural difference of asynchronous binary counters compared to the synchronous ones?
What is the dis/advantage of asynchronous counters over synchronous ones?
Solution to Exercise 133
(1) The incrementer circuit is removed. (2) Only the least-significant bit of the counter is clocked with the system clock. The remaining flip-flops are clocked by the output of the less significant bit. (3) Every flip-flop’s output is inverted feed back to the flip-flop’s input.
(1) An asynchronous counter does not need an incrementer circuit with carry compared to a synchronous counter. Thus an asynchronous counter requires less resources and can achieve a higher frequency. (2) We can implement a synchronous circuit using a short behavioral code. An asynchronous circuit requires a structural description.
Decimal counters#
What is a decimal counter?
How does it work?
Solution to Exercise 134
Every digit of a binary counter is a binary number. A decimal counter creates decimal digits by using a binary counter. This is the reason why a single digit output by this counter is called binary-coded decimal. The 4 bit counter is reset after 9.
This behavior is achieved by appending an additional logic to the output of a binary counter which resets the counter if a 9 is detected.
Ring Counter#
What is a ring counter?
What are dis/advantages?
Solution to Exercise 135
A circular shift register where the last bit is inverted before it is feed back to the first bit in the register.
(1) It requires minimal amount of combinational resources compared to a synchronous binary counter so it can run very fast. (2) It can only count up to \(2N-1\).
Clock Dividers#
Structural Implementation of an Asynchronous Counter#
Warning
I could not find any Xilinx primitive called dff
. For available primitives refer to the question below.
What are vendor primitives?
What is the dis/advantage of using vendor primitives?
Solution to Exercise 136
Primitive is a basic building block. A primitive typically represents a physical block that is available on an FPGA. Primitives can be specific to an FPGA or a FPGA manufacturer. Such a primitive is called a vendor primitive.
Disadvantage: (1) Vendor primitives are typically specific to a vendor and designs containing them cannot be used in other FPGAs. (2) These are structural elements which can lead to bloated code which is hard to understand and maintain.
Advantage: Using vendor primitives we can create optimized designs for a certain FPGA.
In summary, vendor primitives should only be used when needed. A typical example is a clock generator, because FPGAs have dedicated clock generator blocks which typically cannot be synthesized using behavioral code.
For example Xilinx compiles its primitives in different libraries. One of them is Unisim. According to UG900 – Using Xilinx simulation libraries – Table: Simulation Library Locations, Verilog modules included in Unisim can be found on the path <Vivado_Install_Dir>/data/verilog/src/unisims
.
Unisim is a functional simulation library. There are also other libraries for fast simulation (Unifast) and simulation of more complex blocks like PCIe (Secureip). More info about these libraries can be found in UG900 – Using Xilinx simulation libraries.
Imagine that you want to describe a fast and space-efficient counter. You opt for using vendor flip-flop primitives. Which primitives are available at your disposal?
Solution to Exercise 137
In Exercise 136 we found out that the primitives are available on a path similar to <Vivado_Install_Dir>/data/verilog/src/unisims
. Let us search for flip-flop primitives there:
cd /opt/Xilinx/Vivado/2022.1/data/verilog/src/unisims
grep -ri flip-flop
FDCE.v:... D Flip-Flop with Clock Enable and Asynchronous Clear
FDPE.v:... D Flip-Flop with Clock Enable and Asynchronous Preset
FDRE.v:... D Flip-Flop with Clock Enable and Synchronous Reset
FDSE.v:... D Flip-Flop with Clock Enable and Synchronous Set
IDDR.v:... Dual Data Rate Input D Flip-Flop
ODDR.v:... Dual Data Rate Output D Flip-Flop
FD{C,P,R,S}E
are standard flip-flops. The rest are double data rate flip-flops.
Let us also search for latches:
cd /opt/Xilinx/Vivado/2022.1/data/verilog/src/unisims
grep -r Latch
AND2B1L.v:... Two input AND gate implemented in place of a CLB Latch
LDCE.v:... Transparent Data Latch with Asynchronous Clear and Gate Enable
LDPE.v:... Transparent Data Latch with Asynchronous Preset and Gate Enable
OR2L.v:... Latch used as 2-input OR Gate
OSERDESE1.v:// Latch to compensate for clkdiv and bufg_clk clock skew
OSERDESE1.v:// Latch to allow skew between CLK and CLKDIV from BUFGs
LD{C,D}E
are standard latches.
Note
These primitives do not necessarily exist on the FPGA as physical elements. The physical architecture of FPGAs may change a bit from generation to generation, but as a user we can expect that a primitive will behave the same across different architectures.
For example Xilinx 7 Series FPGAs Configurable logic block user guide (UG474) – Storage Elements describes actual physical flip-flop cells on a 7 series FPGA like Spartan 7.
Let us compare the flip-flop primitives with the physical flip-flop cells. All the primitives have clock enable signals and feature both asynchronous and synchronous re/set operations. Control signals chapter confirms that the flip-flops on a 7 series FPGA supports clock enable and a/synchronous re/set operations.
The tutorial describes how we can implement a clock divider using a structural description. Try to come up with a behavioral description of the described circuit
Seven Segment Controller#
The article describes an example design for a seven segment controller example.
Requirements#
Design a clock divider based on an asynchronous counter#
Create a clock divider that uses a structural asynchronous counter built from Xilinx flip-flop primitives. The counter uses the main 100MHz clock as an input, and it should generate a clock signal below 1Hz to drive the LED.
Hint: Use an asynchronous counter. Refer to Structural Implementation of an Asynchronous Counter.
Solution
Note
We do not need to explicitly write a generate
statement as shown in the clock divider tutorial. Refer to Loop generate constructs – Systemverilog 2017
module cntr_asynch_xilinx
#(parameter SIZE=27)(
input clk, rst,
output o
);
// Create signals for the flip-flops:
logic [SIZE-1:0] d, q;
// Least-significant flip-flop
FDCE ff0 (.C(clk), .CLR(rst), .D(d[0]), .CE(1'b1), .Q(q[0]));
// Rest of the flip-flops
// `genvar` implies that a variable which will be used to generate modules in
// an automated fashion. We need a label which will be prepended to the
// instantiated modules, e.g., ffs[1].ff0, ffs[2].ff0. This is required to
// differentiate the instantiations
genvar i;
for (i=1; i<SIZE; i=i+1) begin:ffs
// Use the output of the less significant flip-flop as clock input
// Rest is similar to the least significant flip-flop
FDCE ff0 (.C(q[i-1]), .CLR(rst), .D(d[i]), .CE(1'b1), .Q(q[i]));
end
assign
d = ~q,
o = q[SIZE-1];
endmodule
module cntr_asynch_xilinx_tb
#(parameter SIZE=27);
logic clk = 0, rst, o;
cntr_asynch_xilinx #(SIZE) dut(clk, rst, o);
always #1 clk = ~clk;
initial begin
$dumpfile("signals.vcd");
$dumpvars();
rst = 0;
#2 rst = 1;
#2 rst = 0;
#16 $finish;
end
endmodule
# Set Bank 0 voltage
set_property CFGBVS VCCO [current_design]
# Configuration bank voltage select
set_property CONFIG_VOLTAGE 3.3 [current_design]
# These attributes help Vivado to spot for errors
# More info: https://support.xilinx.com/s/article/55660
set_property -dict {PACKAGE_PIN F14 IOSTANDARD LVCMOS33} [get_ports {clk}]
# On-board LEDs
set_property -dict {PACKAGE_PIN G1 IOSTANDARD LVCMOS33} [get_ports {o}]
# On-board Buttons
set_property -dict {PACKAGE_PIN J2 IOSTANDARD LVCMOS33} [get_ports {rst}]
Design a clock divider based on an synchronous binary counter#
Define a 28-bit synchronous binary counter that uses the 100MHz clock, and a second 4-bit counter that uses one of the bits from the 28-bit counter as a clock. Select a bit from the 28-bit counter that toggles at about .5Hz to use as a clock for the 4-bit counter. Connect the 4-bit counter outputs to four LEDs. Configure your Boolean board, and verify the LEDS toggle at the correct rate.
Solution
module cntr_as_a_clk_div_driving_another_cntr
#(
parameter CLK_DIV_CNTR_SIZE=28,
parameter SYNC_CNTR_SIZE=4
)(
input clk,
output [SYNC_CNTR_SIZE-1:0] o
)
;
// Synchronous counter-based clock divider
logic [CLK_DIV_CNTR_SIZE-1:0] clk_div_cntr_q = 0;
always @(posedge clk)
clk_div_cntr_q += 1;
// Synchronous counter clocked by the clock divider
logic sync_cntr_clk;
logic [SYNC_CNTR_SIZE-1:0] sync_cntr_q = 0;
always @(posedge sync_cntr_clk)
sync_cntr_q += 1;
// Interconnect
assign
sync_cntr_clk = clk_div_cntr_q[CLK_DIV_CNTR_SIZE-1],
o = sync_cntr_q;
endmodule
module cntr_as_a_clk_div_driving_another_cntr_tb
#(
parameter CLK_DIV_CNTR_SIZE=3, // Use low values to see an output
parameter SYNC_CNTR_SIZE=2
);
logic clk = 0;
logic [SYNC_CNTR_SIZE-1:0] o;
cntr_as_a_clk_div_driving_another_cntr
#(CLK_DIV_CNTR_SIZE, SYNC_CNTR_SIZE)
dut(clk, o);
always #1 clk = ~clk;
initial begin
$dumpfile("signals.vcd");
$dumpvars();
#100 $finish;
end
endmodule
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property -dict {PACKAGE_PIN F14 IOSTANDARD LVCMOS33} [get_ports {clk}]
# On-board LEDs
set_property -dict {PACKAGE_PIN G1 IOSTANDARD LVCMOS33} [get_ports {o[0]}]
set_property -dict {PACKAGE_PIN G2 IOSTANDARD LVCMOS33} [get_ports {o[1]}]
set_property -dict {PACKAGE_PIN F1 IOSTANDARD LVCMOS33} [get_ports {o[2]}]
set_property -dict {PACKAGE_PIN F2 IOSTANDARD LVCMOS33} [get_ports {o[3]}]
Digital systems typically have a reset signal. The implementation above does not implement any and the code does not have to contain any initial values for the logic
signals. If we provide neither initial values nor a reset, then the flip-flops are initialized with 0. This behavior can be seen in the flip-flop primitive FDCE
:
Excerpt from <Vivado_Install_Dir>/data/verilog/src/unisims/FDCE.v
:
...
module FDCE #(
...
parameter [0:0] INIT = 1'b0,
...
The INIT
parameter is set if we initialize a signal
But why did we include then an initial value? If we want to do a functional simulation (not synthesized code), then the default value will be X
, in other words, undefined though. So we have to provide some initial values for simulation.