Sequential circuits#
Learning goals#
Apply basic features of the language to describe sequential logic
Introductory problem#
Implement a circuit with the following requirements:
Create a 4-bit counter that continuously counts from 0 to 15.
The counter must work at 1 Hz
The circuit must have an interface similar to:
module cntr #(
int N = 4
) (
input clk,
output logic [N-1:0] o
);
endmodule
Implementation on the board#
Use the least-significant LEDs on the board as counter output.
Tasks#
Read section 5 of SV Guide.
Quiz#
TODO
Mini-lecture#
Processes#
Structured procedures#
initial
always procedures
always
always_comb
always_latch
always_ff
final
task
function
initial
procedure executed:before
always
only once
final
executed after$finish
We should prefer the specific
always_*
_ff
stands for flip-flop. For describing sequential logic_comb
stands for combinational. For describing combinational logic
module tb;
initial $display("initial");
always #1 $display("always");
final $display("final");
initial begin
#5 $finish;
end
endmodule
initial
always
always
always
always
- tb.sv:7: Verilog $finish
always
final
Block statements#
Timing controls#
We can advance the simulation time using the following methods:
delay control using
#
event control using
@
wait statement: similar to
@
+while
Delay control#
a procedural statement following a delay control:
#delay_in_time_units statement;
For example a clock generator:
module tb;
logic clk = 1;
always #1 clk ^= 1;
initial begin
$dumpfile("signals.fst");
$dumpvars();
#10 $finish; // Otherwise our simulation takes forever
end
endmodule
What is the frequency of clk
in the previous module?
Event control#
module tb;
logic clk = 0;
integer edge_counter1 = 0, edge_counter2 = 0;
always #1 clk <= !clk;
initial forever @(edge clk)++edge_counter1;
// Same:
always_ff @(edge clk)++edge_counter2;
initial begin
$dumpfile("signals.fst");
$dumpvars;
repeat (5) begin
@(posedge clk);
#1 assert (edge_counter1 == edge_counter2);
$display("%d == %d", edge_counter1, edge_counter2);
end
$finish;
end
endmodule
Solution for the introductory problem#
Clock divider#
module tb #(
int unsigned DIVIDE_BY = 6
);
logic clk = 1, o;
always #1 clk = !clk;
clkdiv #(DIVIDE_BY) clk_div_i (
.i(clk),
.o(o)
);
initial begin
$dumpfile("signals.fst");
$dumpvars();
#(DIVIDE_BY * 10) $finish;
end
endmodule
module clkdiv #(
int unsigned DIVIDE_BY = 100e6
// Must be divisible by two
) (
input i,
output logic o = 0
);
// We toggle at DIVIDE_BY/2
logic [$clog2(DIVIDE_BY/2)-1:0] cntr_for_toggling;
always_ff @(posedge i) begin
if (cntr_for_toggling != $bits(cntr_for_toggling)'(DIVIDE_BY / 2 - 1))
cntr_for_toggling <= cntr_for_toggling + 1;
else begin
o ^= 1;
cntr_for_toggling <= 0;
end
end
endmodule
Counter#
module tb #(
int N = 4
);
logic clk = 1;
logic [N-1:0] o;
assign #1 clk = !clk;
cntr #(N) cntr_i (.*);
initial begin
$dumpfile("signals.fst");
$dumpvars();
#(2 ** (N + 1)) $finish;
end
endmodule
module cntr #(
int N = 4
) (
input clk,
output logic [N-1:0] o
);
always_ff @(posedge clk)++o;
endmodule
Putting clock divider and counter together#
module tb #(
int CNTR_WIDTH = 4,
int CLK_FREQ = 6,
localparam int NLed = CNTR_WIDTH
);
logic clk = 1;
logic [NLed-1:0] led;
assign #1 clk = !clk;
top #(
.CNTR_WIDTH(CNTR_WIDTH),
.CLK_FREQ (CLK_FREQ)
) cntr_i (
.*
);
initial begin
$dumpfile("signals.fst");
$dumpvars();
#(2 ** CNTR_WIDTH * CLK_FREQ + 1) $finish;
end
endmodule
module top #(
int CNTR_WIDTH = 4,
int CLK_FREQ = 100e6,
localparam int NLed = CNTR_WIDTH
) (
input clk,
output [NLed-1:0] led
);
logic clk_divided;
clkdiv #(CLK_FREQ) clkdiv_i (
.i(clk),
.o(clk_divided)
);
cntr #(CNTR_WIDTH) cntr_i (
.clk(clk_divided),
.o (led)
);
endmodule
Homework#
Implement a configurable clock divider with the following interface:
module clkdiv (
input i,
integer unsigned divide_by,
// If 0 and 1, then output permanently low and high, respectively.
// Must be at least 2 for creating a divided clock.
output logic o
);
Seven segment display is often used as a low-cost and simple alternative to LCD screens. It is useful to display numbers, e.g., the current temperature. Implement a seven segment display controller.
Read this page to get an idea how to drive a seven segment display.
Requirements:
Use the following files and templates.
The controller supports only unsigned integer input.
Understand what the provided code does.
Complete the array
bcd2seg
inseven_segment_controller_pkg
.Complete the
always_ff
block inseven_segment_controller
.Verify
seven_segment_controller
usingseven-segment-controller/tb
. Understand what theassert
lines do.Implement a module called
seven_segment_counter_test_boolean
for the board usingseven_segment_controller
that counts up to 9999 ms on a 4 digit seven segment display. The second seven segment display displays the same values as the other one. Use the templateseven_segment_controller_test_boolean
.You can use the clock divider that we implemented before.
Digit and segment signals must be active-high in
seven_segment_controller
. If the board where the circuit will be deployed requires active-low signals, the signals must be negated on the top layer.Verify your module for the board implementation using
seven_segment_counter_test_boolean/tb
. Understand whatwait
statement does.
Notes:
The package
seven_segment_controller_pkg
contains types and constants which are useful for the implementation and the module interface, respectively. You don’t have to augment the package.The files below make use of many parameters. In the beginning you may also work with constant numbers by assuming a 4 digit and 7 segment display if you find it easier.
package seven_segment_controller_pkg;
localparam int unsigned
DigitCountLg2 = 2,
DigitCount = 2 ** DigitCountLg2,
SegmentCount = 7,
MaxReprNumber = 10 ** DigitCount - 1,
BcdCount = $clog2(
10
);
typedef logic [BcdCount-1 : 0] bcd_t;
// Single BCD digit
typedef logic [$bits(bcd_t) * DigitCount - 1 : 0] bcds_t;
// Many BCD digits
typedef logic [$clog2(MaxReprNumber) - 1 : 0] number_t;
typedef logic [SegmentCount-1 : 0] segment_t;
segment_t bcd2seg[10] = '{
0: 'b0111111,
}; // gfedcba
// Converts a BCD to its seven segment representation.
// a
// f b
// g
// e c
// d
function automatic bcds_t bin2bcd(number_t bin);
// Converts a binary digit to its BCD representation.
// Uses double-dabble algorithm (add 3 if greater than 4, then shift).
// https://en.wikipedia.org/wiki/Double_dabble
bcds_t bcds = '{default: 0};
for (int i = 0; i < $bits(bin); ++i) begin
// Check every BCD digit if > 4
for (int j = 0; j < DigitCount; ++j) begin
// $displayh(" i: ", i, " j: ", j,
// " bcds: ", bcds[BcdCount*(j+1)-1 -:BcdCount]);
// For debugging
if (bcds[BcdCount*(j+1)-1-:BcdCount] > 4) bcds[BcdCount*(j+1)-1-:BcdCount] += 3;
end
// Shift
bcds = {bcds[$left(bcds)-1 : 0], bin[$left(bin)-i]};
end
return bcds;
endfunction
endpackage
// Seven segment controller for unsigned integers
// - Uses only seven segments without dot or colon.
// - Seven segment signals are shared among digits.
module seven_segment_controller
import seven_segment_controller_pkg::*;
(
input clk,
rst,
input number_t number,
output logic [DigitCount - 1 : 0] digit, // Selects currently active digit
output segment_t segment // Currently active segments
);
logic[DigitCountLg2 - 1 : 0] digit_addr;
// Digit address stores the current digit id which is being illuminated. For
// example 1 would be the second least significant BCD digit. In every clock
// cycle the digit address is incremented which in turn determines which
// segments to illuminate dependent on the input `number`.
always_ff @(posedge clk, posedge rst) begin
digit_addr <= rst ? 0 : digit_addr + 1;
digit <=
};
// `digit` is initialized with 1 and continuously shifted to the left.
// If `digit` would be created combinationally using `digit_addr`, then
// the more significant digits (e.g., 3 and 4) will be faded out, because
// the more significant digit signals will be less active compared to the
// less significant signals. The address logic requires more time to
// propagate to the more significant bits.
end
assign segment = bcd2seg[
{ bin2bcd(number) }
[BcdCount * digit_addr +: BcdCount]
];
endmodule
module tb;
import seven_segment_controller_pkg::*;
logic clk = 0, rst;
logic [$clog2(MaxReprNumber) -1 : 0] number;
logic [DigitCount -1 : 0] digit;
logic [SegmentCount -1 : 0] segment;
seven_segment_controller dut (.*);
/// Test `bin2bcd`
bcds_t number_to_bcd_map[type(dut.number)] = '{
4: {4'd0, 4'd0, 4'd0, 4'd4},
1234: {4'd1, 4'd2, 4'd3, 4'd4},
9999: {4'd9, 4'd9, 4'd9, 4'd9}
};
initial begin
foreach (number_to_bcd_map[number]) begin
$display("Testing %d", number);
assert (dut.bin2bcd(number) == number_to_bcd_map[number]);
end
end
///
/// Test digit and segment signals
always #1 clk ^= 1;
initial begin
rst = 1;
#2 rst = 0;
// Check the number 4's seven segment representation
#10 number = 4;
wait (digit == 'b1000);
assert (segment == bcd2seg[0]);
wait (digit == 'b0100);
assert (segment == bcd2seg[0]);
wait (digit == 'b0010);
assert (segment == bcd2seg[0]);
wait (digit == 'b0001);
assert (segment == bcd2seg[4]);
#10 $finish;
end
///
initial begin
$dumpfile("signals.fst");
$dumpvars;
#1000 $display("Timeout");
$finish;
end
endmodule
import seven_segment_controller_pkg::*;
module seven_segment_counter_test_boolean #(
int unsigned CLKDIV_FOR_1MS = 100e6 / 1000, // 1 ms
int unsigned MAX_NUMBER = 9999
) (
input clk,
input [3:0] btn,
output [3:0] D0_AN,
D1_AN,
output [7:0] D0_SEG,
D1_SEG
);
module tb;
logic clk;
logic [3:0] btn;
logic [3:0] D0_AN, D1_AN;
logic [7:0] D0_SEG, D1_SEG;
seven_segment_counter_test_boolean #(.CLKDIV_FOR_1MS(4)) dut (.*);
always #1 clk ^= 1;
initial begin
btn = 1; // Reset
#2 btn = 0;
// Check the least significant digit of 8, where every segment must be
// illuminated. Note that both D*_AN and D*_SEG are active-low.
wait (dut.cntr == 8 && D0_AN == 4'b1110);
// Wait for counter to reach 8 and the D0_AN to select the
// least-significant digit.
assert (D0_SEG[6:0] == ~7'b1111111);
#100 $finish;
end
initial begin
$dumpfile("signals.fst");
$dumpvars;
#200 $display("Timeout");
$finish;
end
endmodule