SystemVerilog RTL Tutorial
This
tutorial introduces some
the new features in SystemVerilog that will make RTL design easier and
more productive.
New Operators
SystemVerilog adds a number of new operators, mostly borrowed from
C.
These include increment (++) and decrement (--), and assignment
operators (+=, -=, ...). The wild equality
operators (=== and !==) act like the comparisons in a casex statement,
with X and Z values meaning “don’t care”.
New loop statements
Also from C is the do-while loop statement and break
and continue.
The
new foreach loop is used with array variables. The for
loop has been
enhanced, so that the following is permitted:
for (int i = 15, logic j = 0 ; i > 0 ; i--, j = ~j) ...
Labelling
In Verilog, you may label begin and fork
statements:
begin : a_label
In SystemVerilog the label may be repeated at the end:
end : a_label
This is useful for documenting the code. The label at the end must
be
the same as the one at the beginning. Modules, tasks and functions may
also have their names repeated at the end:
module MyModule ...
...
endmodule : MyModule
In SystemVerilog any procedural statement may be labelled:
loop : for (int i=0; ...
This is especially useful for loops, because they can then
be
disabled. Despite enhancing named blocks in this way, one reason for
using them is removed: in SystemVerilog variables may be declared in
unnamed blocks!
Relaxed Assignment Rules
Perhaps the hardest Verilog feature for beginners (and even
experienced
Verilog users are tripped up by it from time to time) is the difference
between variables and nets. SystemVerilog consigns the confusion to
history: variables may be assigned using procedural assignments,
continuous assignments and be being connected to the outputs of module
instances. Unfortunately, you still can'’t connect variables
to inout ports, although you can pass them using ref
ports.
This means that, in SystemVerilog, you would tend to use the logic
data type most of the time,
where in Verilog you would sometimes
use reg
and sometimes wire.
In fact reg
and logic
are completely
interchangeable, but logic
is
a more appropriate name.
There are some restrictions, though. You are not allowed to
assign
the same variable from more than one continuous assignment or output
port connection. This is because there is no resolution for variables
like there is for nets in the case of multiple drivers. Also, if you
assign a variable in one of these way, you may not assign the same
variable using procedural assignments.
Port Connection Shorthand
Suppose you are using Verilog-2001 and are writing a testbench for a
module which has the following declaration:
module Design (input Clock, Reset, input [7:0] Data, output [7:0] Q);
In the testbench, you might declare regs and wires:
reg Clock, Reset;
reg [7:0] Data;
wire [7:0] Q;
and you would instance the module like this:
Design DUT ( Clock, Reset, Data, Q );
or, better, like this:
Design DUT ( .Clock(Clock), .Reset(Reset), .Data(Data), .Q(Q) );
But this is a bit repetitive. SystemVerilog allows you to use the
following shorthand notation:
Design DUT ( .Clock, .Reset, .Data, .Q );
where appropriately named nets and variables have previously been
declared, perhaps like this:
logic Clock, Reset;
logic [7:0] Data;
logic [7:0] Q;
If even this is too verbose, you can also write this:
Design DUT ( .* );
which means “connect all ports to variables or nets with the
same
names as the ports”. You do not need to connect all the ports in
this way. For example,
Design DUT ( .Clock(SysClock), .* );
means “connect the Clock port to SysClock, and all the other
ports to variables or nets with the same names as the ports.”
Synthesis Idioms
Verilog is very widely used for RTL synthesis, even though it
wasn’t designed as a synthesis language. It is very easy to
write
Verilog code that simulates correctly, and yet produces an incorrect
design.For example, it is easy unintentionally to infer transparent
latches. One of the ways in which SystemVerilog addresses this is
through the introduction of new always keywords: always_comb,
always_latch and always_ff.
always_comb is used to describe combinational logic. It
implicitly
creates a complete sensitivity list by looking at the variables and
nets that are read in the process, just like always @* in
Verilog-2001.
always_comb
if (sel)
f = x;
else
f = y;
n addition to creating a complete sensitivity list automatically, it
recursively looks into function bodies and inserts any other necessary
signals into the
sensitivity list. It also is defined to enforce at least some of the
rules for
combinational logic, and it can be used as a hint (particularly by
synthesis tools)
to apply more rigorous synthesis style checks.Finally, always_comb adds
new semantics: it implicitly puts its
sensitivity list at the end of the process, so that it is evaluated
just
once at time zero
and therefore all its outputs take up appropriate values before
simulation
time starts to progress.
always_latch and always_ff are used for infering
transparent latches
and flip-flops respectively. Here is an example of always_ff:
always_ff @(posedge clock iff reset == 0 or posedge reset)
if (reset)
q <= 0;
else if (enable)
q++;
The advantage of using all these new styles of always is that the
synthesis tool can check the design intent.
Unique and Priority
Another common mistake in RTL Verilog is the misuse of the
parallel_case and full_case pragmas. The problems
arises because these
are ignored as comments by simulators, but they are used to direct
synthesis. SystemVerilog addresses this with two new keywords: priority
and unique.
Unlike the pragmas, these keywords apply to if statements
as well as
case statements. Each imposes specific simulation behaviour
that is
readily mapped to synthesised hardware.
unique
enforces completeness and uniqueness of the conditional; in other
words,
exactly one branch of the conditional should be taken at
run-time. If the specific conditions that pertain at run-time would
allow more than one
branch of the conditional, or no branch at all, to be taken, there is a
run-time
error. For example, it is acceptable for the selectors in a case
statement to
overlap, but if that overlap condition is detected at runtime then it
is
an error.
Similarly it is okay to have a unique case statement with no default
branch, or an if statement with no else branch, but at run time the
simulator will check
that some branch is indeed taken. Synthesis tools can use this
information,
rather as they might a full_case directive, to infer that no latches
should be
created.
priority
enforces a somewhat less rigorous set of checks, checking only that at
least one branch of the conditional is taken. It therefore allows
the possibility that more than one branch of the conditional could be
taken at
run-time. It licenses synthesis to create more extravagant priority
logic in such a
situation.


