SystemVerilog guide#
SystemVerilog guide by Zachary Yedida includes a compact introduction to the well-known language features of SystemVerilog. In this section we discuss the chapters including chapter 7.
Introduction#
What is the typical difference of SystemVerilog to traditional programming languages?
Solution to Exercise 184
SV is for describing and simulating digital systems and is compiled to static logic gates. SV is not for generating machine instructions
SV code is typically executed in parallel, there may be no specific order in how the code is executed.
A brief history#
What is the difference between Verilog and SystemVerilog?
Solution to Exercise 185
Verilog was not enough to cater the needs of digital designs that have been continuously getting more complex
SystemVerilog adds some modeling and verification features
Verilog and Systemverilog were integrated in 2009 into the language Systemverilog
Gate-level combinational modeling#
What is the basic structure of a module?
Solution to Exercise 186
a block that begins with
module _name_
continues with parameter and input output signal declarations
signal declarations
other module instantiations
continuous assignments or procedural blocks
ends with
endmodule
Modules#
The logic
data type#
logic
data type is 4-state. What does this mean?
Solution to Exercise 187
It can store 0
, 1
, X
(don’t care or unknown) or Z
(high-impedance or floating).
What do the values
X
andZ
mean?
Solution to Exercise 188
these are typically relevant in the simulation
assigning
X
to a value means that we don’t care if it is0
or1
. The tool can use this information for optimizationsX
is also the default state in simulations. During simulation anX
shows that a signal has not been properly initialized.X
is also used if two signals drive (there are also different strengths, but we keep it simple here) another signal with different values at the same time.Z
is used if a signal is not driven by any strength. This is useful for modeling busses (e.g., CAN) where multiple participants can drive the same signal
Why should we not use int
to model signals?
Solution to Exercise 189
It is not 4-state. Using 4-state logic may ease the verification of circuits in simulation
We should use
int
only in for loops
What is the difference between wire
and var
(variable)?
Solution to Exercise 190
variable is one of the two data object groups in SystemVerilog beside nets.
wire
is a kind of net, i.e., a net type. (SV17-6.5 Nets and variables)Examples of other net types are are
uwire
,tri
,wor
(SV-2017-6.6 Net types)
A net (consequently also
wire
) can be assigned to only in a continuous assignment, but not procedurallyvar
is for data storage. It can be assigned to using procedural assignments including procedural continuous assignments.var
can be also written to using a continuous assignment likewire
.var
keeps the last value that has been written to.continuous assignment assigns values to nets or variables (SV17-10.2 Assignment statements - Overview)
procedural … to variables (ditto)
procedural continuous assignments are procedural statements which can be used outside procedural blocks (e.g.,
always
,initial
) to continuously drive a net or variable. These are:assign
,deassign
,force
,release
. (SV17-10.6 Procedural continuous assignments)
Is logic
a net or variable?
Solution to Exercise 191
logic
can be both a net or variable.logic
is a data type whereas nets and variables are data object groups.If we write
logic x
orvar x
this implicitly means:var logic x
(SV17-6.8 Variable declarations).If we want a net with a
logic
data type, then we can writewire x
, which implieswire logic x
.logic
is the default data typeinput
andoutput
s are nets as defaultThis is the reason why we have to augment the output declaration with
logic
(i.e.,output logic
), if we assign a value procedurally to an output port
Why is it sufficient to use the data type logic
in FPGA design?
Solution to Exercise 192
logic
is the most basic data type for modeling signalsWhen we declare a new signal as
logic
, then this signal is a variable as default (instead of net)Compared to nets, variables cannot have multiple (simultaneous) drivers
Typically we don’t have multiple drivers on a signal (e.g., inside the FPGA)
Actually we could also use var
instead of logic
for declaring logic
signals if we wanted to shorten our code.
Module instantiation#
In the example we see that and
, or
and not
are instantiated even they were not defined before. How is this possible?
Solution to Exercise 193
These are primitives that are included in SystemVerilog as builtin modules
How can we connect the ports of a module during instantiation? Show some of them and explain how they work.
Solution to Exercise 194
m m_i(.a(x), .b(y), .c(z))
orm m_i(.c(z), .b(y), .a(x))
: named assignment.a
,b
,c
are module ports and the rest are signals that we connect to the portsm m_i(x, y, z)
: positional assignmentm m_i(.a, .b, .c)
: automatic assignment to the signalsa
,b
andc
.m m_i(.*)
: automatic assignment to the signalsa
,b
andc
(ifm
has the portsa
,b
andc
)m m_i(.*, .c(x))
automatic assignment with an exception of.c(x)
m m_i(.a, .b, .c(x))
automatic assignment with an exception of.c(x)
Vectors#
Model a 1-bit 4-to-1 multiplexer using the 1-bit 2-to-1 multiplexer
mux
with inputsa
,b
,sel
and outputf
.
Solution to Exercise 195
module m (
input a, b, c, d,
input [1:0] sel,
output f
);
logic m1_o, m2_o;
mux m1(.a(a), .b(b), .sel(sel[1]), .f(m1_o));
mux m2(.a(c), .b(d), .sel(sel[1]), .f(m2_o));
mux mo(.a(m1_o), .b(m2_o), .sel(sel[0]), .f(f));
endmodule;
Literals#
How can we write integer literals?
Solution to Exercise 196
most basic form:
10
. This is a 32 bit signed decimal.providing the size:
2'10
providing the base:
2'b10
, (h
,o
,d
also exist)signedness:
2'sb10
More details are in SV2017-5.7.1.
What happens if we assign a variable as follows: x = '1
?
Solution to Exercise 197
All the bits of x
are assigned to 1
.
Parameters#
What is the difference between param
and localparam
?
Solution to Exercise 198
param
is a compile-time module constant. It is typically used to design modules that support variable input and output sizes.localparam
is a compile-time constant. It is used for constants used in the design
RT-level combinational modeling#
Continuous assignments#
Conditional operator#
How can we describe a 2-to-1 mux using a continuous assignment?
Solution to Exercise 199
assign f = sel ? a : b;
Bitwise operators#
How can we shorten the following code?
assign
x[0] = ~y[0],
x[1] = ~y[1],
x[2] = ~y[2];
Solution to Exercise 200
assign x = ~y;
Logical operators#
Write code for the following as continuous assignment: x
must be assigned 2
if the conditions a
and b
are true, and 3
otherwise.
Solution to Exercise 201
assign x = a && b ? 2 : 3;
Reduction operators#
You have five two-way switches placed on different walls of your room that can control your room light. Every switch can toggle the light. Write code using a reduction operator that models this behavior. The switches are modeled by the signal logic [4:0] switches
and the light as logic light
.
Solution to Exercise 202
assign light = ^switches;
Arithmetic operators#
Shift operators#
What is the difference between a logical and arithmetic shift?
Solution to Exercise 203
The arithmetic shift takes the sign of the number into account. For example if 4 bit -2
= 1110
is logically shifted to left, then we get 0110
= 7
. In case of arithmetic right shift, we get: 1111
= -1
.
Comparison operators#
Concatenate and replicate operators#
You have the signals a
, b
, c
. Create the concatenation of signals aabcabcabcb
and assign to f
.
Solution to Exercise 204
assign f = {a, {3{a, b, c}}, b};
Generate#
Always block for combinational design#
What is the advantage of using an always_comb
statement over a simple always
or a continuous assignment?
Solution to Exercise 205
always
statement typically needs a sensitivity list. always_comb
has an implicit sensitivity list for all signals that are read in the block.
always @*
is an alternative and executes the block whenever a read signal inside the block changes. However always_comb
has many advantages over always @*
. For example always @*
may not be executed in the beginning of a simulation which may lead to errors if some signals are initialized with constants. Constants are not signals, so they won’t trigger the always
block. Another advantage is that the variables on the left-hand side of the assignments may not be written by other always
blocks which may lead to errors. For other differences refer to 2017-9.2.2.2.2.
2. Advantage over continuous assignment: We can use procedural code like if/else statements which are more general than the ternary operator in continuous assignments.
Blocking assignment#
Why should we use blocking assignments for combinational circuit design and non-blocking assignments for sequential circuits?
Solution to Exercise 206
When we describe combinational logic procedurally, we typically want to break a long chain into smaller pieces with temporary variables, where every piece may be described using a single assignment. In this case we want a behavior like in C
, every statement should be executed immediately.
When we describe sequential logic, we typically want to assign a signal value a
to another signal value b
if an event happens. Even we can use blocking assignments for basic code, using them can lead to problems like this: https://i.stack.imgur.com/rrgHa.png
If statements#
Case statements#
When does the compiler infer a mux from a case statement?
Solution to Exercise 207
Conditions:
For every case an output
y
is assigned to an input signal.All conditions are mutually exclusive, in other words, two cases cannot be true at the same time.
Unique case#
What is the difference between case
and unique case
?
Solution to Exercise 208
Case statement is like if/else and can lead to a priority encoder if the compiler misses the conditions for a multiplexer. unique case
will try to a multiplexer (and will probably warn if not possible), e.g., even not all cases are complete. For example:
logic [1:0] sel;
always_comb
unique case (sel)
'b00: f = a;
'b01: f = b;
endcase
For other cases of sel
, f
can be assigned to any value.
Case inside#
Modeling sequential circuits#
Always block#
Why should we typically use always
with a sensitivity list (e.g., @(posedge clk)
or always_comb
)?
Solution to Exercise 209
always
without a sensitivity list corresponds to an infinite loop if there is no statement that blocks the execution. This could prevent other blocks from running.
More info: https://stackoverflow.com/a/62487956
Non-blocking assignments#
We say: a non-blocking assignment is deferred and executed later. (1) When is it executed? (2) What happens if we have a later assignment to the same destination variable?
Solution to Exercise 210
At the end of the procedural block, after all the procedural assignments.
If there is another assignment to the same data object, then only the last one is executed.
The D flip-flop and register#
You are describing a flip-flop. Is there a difference between q <= d
and q = d
in the template below? In other words, would it behave differently?
module m (input clk, d, output logic q);
always_ff @(posedge clk)
q <= d; // or `q = d`
endmodule
Solution to Exercise 211
In this example the behavior will be the same — d
will be assigned to q
whenever clk
rises. Indeed synthesizing that using Vivado outputs an FDRE
.
More on blocking vs non-blocking#
Imagine you want to implement a 3 bit register that shifts a bit at every cycle. What is the problem with the following code?
always_ff @(posedge clk) q0 = d;
always_ff @(posedge clk) q1 = q0;
always_ff @(posedge clk) q2 = q1;
assign d = q2;
Solution to Exercise 212
the blocking assignments cause a race condition. The behavior depends on which always
block executes first. Typically we should use non-blocking assignments in always_ff
blocks.
Variations of the D FF#
The D latch#
You want to implement a multiplexer. (1) Do you see a problem with the following code? (2) What does the compiler synthesize out of this code?
module m (input [1:0] sel, input a, b, c, output logic y);
always_comb
case (sel)
'b01: y = a;
'b10: y = b;
'b11: y = c;
endcase
endmodule
Solution to Exercise 213
To infer a multiplexer we have to assign a value to
y
for every possible inputsel
.y
is not assigned a value in case of'b00
.First we get a warning:
WARNING: [Synth 8-87] always_comb on 'y_reg' did not result in combinational logic
When we look at the report:
+----------+------+---------------------+
| Ref Name | Used | Functional Category |
+----------+------+---------------------+
| IBUF | 5 | IO |
| OBUF | 1 | IO |
| LUT5 | 1 | LUT |
| LUT2 | 1 | LUT |
| LDCE | 1 | Flop & Latch |
+----------+------+---------------------+
we see that LDCE
is inferred, which is a latch. We did not provide an assignment for y
in case of sel
='b00
, which means that we want to keep the previous value of y
. If we fix the error, e.g. by providing a default value:
module m (input [1:0] sel, input a, b, c, output logic y);
always_comb
case (sel)
'b01: y = a;
'b10: y = b;
'b11: y = c;
default: y = 0;
endcase
endmodule
We see that the latch is gone:
+----------+------+---------------------+
| Ref Name | Used | Functional Category |
+----------+------+---------------------+
| IBUF | 5 | IO |
| OBUF | 1 | IO |
| LUT5 | 1 | LUT |
+----------+------+---------------------+
Small muxes are typically implemented using LUT*
s in AMD FPGAs.
Another alternative is to use unique case
which implies that we covered every case for y
and the compiler can assign anything for sel
='b00
:
module m (input [1:0] sel, input a, b, c, output logic y);
always_comb
unique case (sel)
'b01: y = a;
'b10: y = b;
'b11: y = c;
endcase
endmodule
+----------+------+---------------------+
| Ref Name | Used | Functional Category |
+----------+------+---------------------+
| IBUF | 5 | IO |
| OBUF | 1 | IO |
| LUT5 | 1 | LUT |
+----------+------+---------------------+
General sequential circuit design#
Draw the structure of typical sequential circuits. Use state register
, next-state logic
and output logic
.
Solution to Exercise 214
For example: Mealy machine
The input signals are input to the next-state logic
which is combinational. The next-state logic
uses additionally the state register
to determine the next-state which is applied in the next clock cycle. The output signals are determined by the output logic
which depends on both state register
and input signals.
Example: shift register#
Modeling finite state machines#
What is the difference between a Mealy and Moore machine?
Solution to Exercise 215
The output only depends on the state registers in case of Moore. Mealy’s output additionally depends on the input signals.
State diagrams#
What is the difference in Mealy and Moore state machine diagrams?
Solution to Exercise 216
Mealy: The output values are provided in the transitions, because the output depends both on state registers and input signal
Moore: The output values are provided in the state circles.
What are some differences between Mealy and Moore state machine circuit behaviors?
Solution to Exercise 217
Moore tends to require more states than Mealy, because Moore requires an individual state for every output. Mealy can tweak the output using combinational logic if required.
Mealy’s output may change during the clock cycle many times, as it depends on combinational logic. This may pose a problem for some circuits.
General FSM circuit design#
Enumerations#
How can we use enumerations in state machine design?
Solution to Exercise 218
We use enum
s to declare the state names, e.g.: typedef enum {START, BLINK, WAIT} state_t
FSM code development#
Additional info in State machine example
Mealy versus Moore#
Modeling memory#
Register file#
How can we model a register file which consists of 32x 64bit registers?
Solution to Exercise 219
localparam REG_COUNT = 32, WIDTH = 64;
logic [WIDTH-1:0] reg_file [REG_COUNT]; // same as `... [0:REG_COUNT-1]
RAM#
How can we model a RAM?
Solution to Exercise 220
We need (1) an array (2) address (3) a write-enable signal. We must synchronously read and write the RAM. If we do not write to it, we get a ROM:
module ram#(
SIZE_LG2 = 10,
localparam SIZE = 2**SIZE_LG2
)(
input clk, wen
logic[SIZE_LG2-1:0] raddr, waddr,
byte din,
output byte dout
);
byte mem[SIZE];
always_ff @(posedge clk) begin
if (wen)
mem[waddr] <= din;
dout <= mem[raddr];
end
endmodule
Dual-port RAM#
Where are dual-port RAMs useful?
Solution to Exercise 221
Most FPGAs implement dual-port RAMs which can be used as a buffer between two producer consumer circuits.
Preloading data#
Where do we have to place $readmemh()
to initialize a memory array?
Solution to Exercise 222
initial