Counters, Clock Dividers and the 7-segment Display#

Source

Exercise 128

How is time typically measured by electronic circuits?

Exercise 129

What is 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#

Source

Synchronous Binary Counters#

Exercise 130

What is a synchronous binary counter?

Asynchronous Binary Counter#

Exercise 131

  1. What is the structural difference of asynchronous binary counters compared to the synchronous ones?

  2. What is the dis/advantage of asynchronous counters over synchronous ones?

Decimal counters#

Exercise 132

  1. What is a decimal counter?

  2. How does it work?

Ring Counter#

Exercise 133

  1. What is a ring counter?

  2. What are dis/advantages?

Clock Dividers#

Source

Structural Implementation of an Asynchronous Counter#

Source

Warning

I could not find any Xilinx primitive called dff. For available primitives refer to the question below.

Exercise 134

  1. What are vendor primitives?

  2. What is the dis/advantage of using vendor primitives?

Exercise 135

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?

Exercise 136

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#

Source

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.