SystemVerilog for Design Note
This is my reading note of book “SystemVerilog for Design (2nd edition)". As a non-full-time RTL designer, it has opened my mind. But still, I’m sad about the antient tool that we are using to design hardware.
Chapter 2: SystemVerilog Declaration Spaces
Package
- Verilog shortage: no global declaration
package ... endpackage
- share user-defined type definitions across multiple modules
- independent of modules
- parameters cannot be redefined
- in package, parameter is similar to localparam, cos in module localparam cannot be directly redefined while instantiation
- referencing
::
the scope resolution operatorpackage_name::package_member
- use
import
to import package into current spaceimport package_name::package_member
- TIPS: importing an enumerated type definition will not import the labels automatically
import package_name::*
- what is used will be imported
$unit
declaration space
- TIPS: synthesis guide
- tasks and functions must be
automatic
- storage for automatic task/function is allocated each time it’s called
- cannot use
static
variables, which are supposed to be shared by all instances
- tasks and functions must be
$unit: compilation-unit declarations
- declaration space outside of package/module/interface/program
- BUT it’s not global
- if put variables and nets in $unit
- source code order can affect the usage of a declaration external to the module
- each compilation has one $unit
- single-file compilation
- multiple-file compilation: source order is tricky
- TIPS: coding guide
- DONOT make any declarations in $unit space, only import packages into $unit
- ILLEGAL to import the same package more than once into the same $unit
- NOTE: donot work for global variables, static task/function
// filename: def.pkg
`ifdef DEF_PKG
`define DEF_PKG
package def;
// ...
endpackage
`endif
// in every design or testbench file that need package "def"
`include "def.pkg"
- identifier search rules
- local
- package
- named first
*
wildcard second
$unit
- design hierarchy
- TIPS: synthesis guide
- use packages instead of $unit
- external task/function must be automatic
Named/unnamed statement blocks
- local variables in named blocks can be accessed hierarchically
- local variables in unnamed blocks (added in SV) has no hierarchical path
- protecting from external, cross-module referencing
Timing units and precision
- problem with Verilog’s timescale directive: file order dependent
- SystemVerilog improvements
- time value with time units: 5ns, 3.2ps
- NOTE: there is no space between number and unit
- time value with time units: 5ns, 3.2ps
- scope-level time units and precision: timeunit & timeprecision keywords
- must be immediately after module/interface/program declaration
- search order
- local
- parent module/interface
timescale
in effect while compilation- defined in $unit
- simulator default
Chapter 3: SystemVerilog Literal Values and Built-in Data Types
Literal value enhancement
- Verilog tricks to fill vector with all ones
data = ~0; // one's complement
data = -1; // two's complement
- SystemVerilog: apostrophe(tick) ( ' ) (Note: not back-tick ( ` ))
data = '1; // all 1's
data = 'z; // all z's
DEFINE enhancement
- String
// Verilog
`define print(v) $display("variable v = %h", v)
`print(data); // = $display("variable v = %h", data);
// SystemVerilog
`define print(v) $display(`"varaible v = %h`", v)
`print(data); // = $display("variable data = %h", data);
// SystemVerilog: escape with double `
`define print(v) $display(`"varaible `\`"v`\`" = %h`", v)
`print(data); // = $display("varaible \"data\" = %h", data);
- Construct identifier names: double back-tick w/o space will separate names that will allow 2 or more names to be replaced and form a new name.
`define MY_NET(index) bit my_net``index``_bit;
`MY_NET(00) // = bit my_net00_bit;
`MY_NET(15) // = bit my_net15_bit;
Variables
- Type
- Net: “wire” keyword, only 4-state
- Variable: “var” keyword, most of the time it can be omitted
- Data type: value system
- 2-state: “bit” keyword
- 4-state: “logic” keyword (to replace “reg” keyword)
- Explicit & implicit (shit-hole of SystemVerilog)
// 4-state 8-bit varaible
logic [7:0] busA;
// to be explicitly
var logic [7:0] busA;
// 2-state 32-bit variable
bit [31:0] busB;
// to be explicitly
var bit[31:0] busB;
// 4-state 8-bit net
wire [7:0] busC;
// to be explicitly
wire logic [7:0] busC;
wire reg [31:0] busD; // ILLEGAL
- Signed vs. Unsigned
- Concatenation automatic create
unsigned
result logic
are unsigned by defaultint
are signed by default- syntax:
<type> <signed/unsigned> <bit width> <name>;
- Concatenation automatic create
- TIPS: synthesis guide
- Because 2-state data types begins simulation with default 0 instead of X, if they are used in RTL may cause RTL behavior mismatch gate-level netlist. So they are mostly used in verification
- Converting from 4-state to 2-state, X and Z are mapped to 0
- High level data type:
- 2-state data type: used for abstract model or DPI (Direct Programming Interface) to work with C/C++ model
byte
: 8-bitshortint
: 16-bitint
: 32-bitlongint
: 64-bit
void
: no storageshortreal
: 32-bit single-precision = float in C, whilereal
= double in Cclasses
: not covered in this book
- 2-state data type: used for abstract model or DPI (Direct Programming Interface) to work with C/C++ model
- NOTE: Most signals can be declared as
logic
in RTLlogic
for single-driverwire
for multi-driver logic (wand/wor)
- Value drivers
- Any number of
initial
oralways
blocks- NOTE: it’s only for back-compatable with Verilog which is not really circuit behavior
- Single
always_comb/always_ff/always_latch
block - Single
assign
statement - Single
module/primitive output/inout
- Any number of
- Type casting
- Static casting (synthesizable)
- Size casting:
<size>'(<expression>)
- ex.
16'(2) // 16-bit wide
- ex.
- Sign casting:
<sign>'(<expression>)
- ex.
signed'({a, b}) // unsigned concatenation result to signed
- ex.
- Dynamic casting (has error check)
- ex.
cast(dest_var, source_exp);
- ex.
- Size casting:
- Static casting (synthesizable)
- Varaible initialization
- SystemVerilog in-line initialization is before time zero and does not cause a simulation event
- Testbench should initialize varaibles to their inactive state
- Static and automatic variables
static
vsautomatic
- Storage
- Automatic variables can be used for re-entrant tasks and recursive functions.
- Module level, all varaibles are static
begin … end
andfork … join
blocks, tasks and functions, all storage defaults to static- Automatic tasks and functions have all automatic storages
- Initialization
- Static variables are only initialized once
- Automatic variables are initialized each call
Constants
- Verilog
parameter
: can be redefined when instantiationspecparam
: can be redefined from SDFlocalparam
: elaboration-time constant, cannot be directly redefined
- SystemVerilog: C-like const keyword
const int N = 5;
Chapter 4: SystemVerilog User-Defined and Enumerated
typedef
keyword
Ex. typedef int unsigned uint;
- Local & shared
- Local: within a module/interface, scope is limited locally
- Shared: use package and import; or import to $unit
- Naming convention
- End with
_t
, the same as C
- End with
Enumerated types
- Verilog vs SystemVerilog
- Verilog: use constants to represent enumerated types
- But nothing would limit the value of enum varaibles, will cause error
- Need
fullcase
directive
- SystemVerilog: enum
- Self-doc, easier to debug
- Value check
- All tools deal with enum the same way
- Verilog: use constants to represent enumerated types
import package_name::*
- Import the enum type will not automatically import labels
- Define list of labels
enum {RESET, WAIT[2], WORK[3: 5], CLEAN[3: 0]} state;
// RESET, WAIT0, WAIT1, WAIT2, WORK3, WORK4, WORK5, CLEAN3, CLEAN2, CLEAN1, CLEAN0 ("[" and "]" are discarded)
- Label
- Must be unique in naming scope, so it’s required to add prefix to the label
- Values
- Default to be “int” and start from 0
- Can be specified explicitly
-
- One-hot, one-code, Johnson-count, Gray-code, etc.
- Must be unique
- Types
- Default to be
int
- Can be specified explicitly
Bit, logic
- Ex.
enum logic [2:0] {WAIT = 3'b001, LOAD = 3'b010, READY = 3'b100} state;
- 4-state could be Z or X
- But the next lable after Z or X should be given value explicitly (otherwise tools don’t know how to increase value automatically)
- Default to be
- Typed enumerated type vs. anonymous enumerated type
typedef enum {WAIT, LOAD, READY} state_t;
- Type check
- Most variables are loosely typed
- Any value can be assigned to a variable, just has to be cast implicitly
- Enum is strongly typed
- Most variables are loosely typed
typedef enum {
WAIT, LOAD, READY
} state_t;
state_t state, next_state; // actually is stored as int
int foo;
state = next_state;
foo = state + 1;
state = foo + 1; // ILLEGAL
state = state + 1; // ILLEGAL
state++; // ILLEGAL
next_state += state; // ILLEGAL
- Casting
next_state = state_t'(state++); // legal: synthesizable, no value check, maybe out-of-range
$case(next_state, state+ 1); // legal: not synthesizable, with value check, slower in simulation
- System methods (similar to C++ syntax)
${enum_varaible_name}.first/last/next(<N>)/prev(<N>)/num/name
*.name
return a string- If current value is not a valid value defined, next/prev return the first element
*.next/prev
will wrap around
Chapter 5: SystemVerilog Arrays, Structures and Unions
Struct
- Struct vs. array
- Array: collection of elements with the same type and size; reference by index
- Struct: collection of varaibles/constants can be diff types and sizes; reference by name
- Struct vs. interface
- Struct usually for variables, can be defined inside of interface
- Inferface are net type, cannot be defined inside of struct
- Use
typedef
to give a name tostruct
and reuse it - Assigning value to structures
- New “
{ }
tokenIW = '{100, 5, 8'hFF, 0};
- By name
IW.a = 100; IW.b = 5;
- Combine these two
IW = '{address:0, opcode:8'hFF, a:100, b:5}
- Default value
IW = '{default:0}; // all members by default is 0
IW = '{default:0, a:100}; // all members is 0 by default, except a is 100
- New “
- Packed vs. unpacked
- By default, it’s upacked
- Use
packed
keyword to defined packed structure- All members are stored as contiguous bits
- Then members can be reference as vector with bit/range position.
- Can only contain integral values (not real or shortreal)
signed
orunsigned
-
- Treat the whole vector of packed structure as singed/unsinged
- Each member can still be signed/unsigned independently
- Passing struct through module/interface ports and task/function argument
- NOTE: when passing unpacked struct, both sides should have exactly the same type, while anonymous struct declared in 2 diff modules, even if with the same names/members, are not the same type of struct
- Synthesis guide
- Both unpacked and packed struct are synthesizable
Union
- Union is a single storage element that can have multiple representation
- Ex. One 8-bit data can either be signed or unsigned with diff configuration
- The same with struct, use
typedef
to reuse - Unpacked union
- NOT synthesizable
- Tagged union
tagged
keyword- Check whether the union is used in a consistent way
- Write to one member, then read from another is dangerous
- Packed union
- Synthesizable
- All memebers have the same size
- Allow write to one format then read from another
// example: represent packed struct in array of bytes
Union packed {
data_package_t packet; // packed structure
logic[7: 0][7: 0] **byte**; // packed array
} datareg;
Array
- Unpacked arrays
- Verilog limitation: restrict access to arrays to just one element of the array at at time
- SystemVerilog refer Verilog style array as unpacked arrays
- Elements are stored independently, just grouped under the same array name
- SystemVerilog improvement: reference the entire/slice of an array
-
- A slice is one or more contiguously numbered elements within one dimension of an array
- Assignment of this type: left-hand & right-hand should have identical layout and types
- Simplified declarations
logic [31:0] data [1024]; // logic [31:0] data [0:1023]
- Verilog limitation: restrict access to arrays to just one element of the array at at time
- Packed arrays
- Vector = packed arrays
- Any vector operation can be performed on packed arrays
- Only bit-wise types can be packed arrays
- Bit/logic/reg or net types
- Vector = packed arrays
- Unpacked vs packed arrays
- Unpacked: model memories & abstract types
- Packed: vectors with sub-fields
- Assignment
- Packed array is assigned the same as vector
- Concatenated operator, repicate operation
- Unpacked array
{ }
and{n{ }}
keyword
- Default value
int a [0:7][0:1023] = '{default: 8'h55};
- Copy
- Unpacked array copy only can between the same array with the same number of dimensions and element size, and are of the same types
- Packed array is assigned the same as vector
- Indexing
logic [3:0][7:0] mixed_array [0:7][0:7][0:7];
// ______|-4-||-5-|_____________|-1-||-2-||-3-| <- order
- Typedef with array
- Array of struct and union
- Array in struct and union
- Passing
- Any number of dimensions can be passed through ports or task/function arguments
foreach
keyword to iterate array elements
int sum[1: 8][1: 3];
foreach(sum[i, j])
sum[i][j] = i + j;
- Array query system function
$dimensions(array_name)
- Number of dimension
$left/right/low/high(array_name, dimension)
- Boundary of dimension
$size(array_name, dimension)
- Size of dimension
$increment(array_name, dimension)
- 1 or -1
$bits(expression)
- Size-of “expression”, expression could be any type of data or slices
- All these system functions are synthesizable
- Dynamic types are covered in verification that’s NOT synthesizable
- Dynamic arrays
- Associative arrays
- Sparse arrays
- Strings (character arrays)
Chapter 6: SystemVerilog Procedural Blocks, Tasks and Functions
always
procedual block
- Verilog limitation
always
could be combinational or latched or sequential- EDA tool must infer design intent from cotent, which might differ from the real intent
- SystemVerilog improvement
- New keywords:
always_comb
&always_latch
&always_ff
- New keywords:
always_comb
(adv. vs.always @ *
)- NO need to specify sensitivity list, auto infer
- Eliminate the risk of incorrect sensitivity list
- Includes the signals read within any functions called from the block
- Some functions may not list all the signals their read as argument
- Assignment only happens in current block, to avoid multi-driven problem (which is legal in Verilog syntax)
- Clear design intent
- Tool will issue warning if content doesn’t represent combinational logic
- Automatic evaluation at time zero, after initial/always activation
- Important for some special case to get correct init value, instead of default value. The book here gave a very interesting corner case of a state machine, at page 145.
- NO need to specify sensitivity list, auto infer
- Extra good practice tip about break-down code into managable pieces: multi always block vs. functions
- Multi-always: many signals propagate through several procedural blocks
- Function is better (maybe even better if use unit test)
always_latch
- Same sensitivity list inferring with
always_comb
- Same sensitivity list inferring with
always_ff
- Tool will verify if content represent sequential logic (synthesize requirement)
- Every signal in the sensitivity list must be qualified with
posedge
ornegedge
- Event control must be from sensitivity list
- Every signal in the sensitivity list must be qualified with
- Tool will verify if content represent sequential logic (synthesize requirement)
task
/function
- Inferred
begin … end
return
- Return variable explicitly, instead of using function name variable
- End function before going through to the end of code
void
type function (C-style)- No return function
- But can have
output
- For synthesis, use
function void
instead oftask
- Argument
- Named argument when using
- Ex. divide(.denominator(b), numerator(a));
input/output/inout
argument- By default,
input
- By default,
- No argument
- To break-down code into managable pieces
- Default value
- Missing argument when using
- Arrays, structures and unions as argument
- Use
typedef
- Use
- Reference instead of copy
- Normally, inputs are copied when called, outputs are also copied when finished
- Verilog still can use
reference
with hardcoded hierarchical name of signals. But it’s very poor in reusablility. - New
ref
keyword (instead of input/output/inout)
- Named argument when using
- Only automatic task/function can have
ref
arguments- New
const ref
keyword to define read-only reference arguments - (used in task) Allows sensitivity to changes
- New
- Can be used to trigger event
- Can be used to read/write value in real-time (if have event/timing control)
- Restriction with
output/inout/ref
argument- Not from an event expression
- Not from a continuous assignment
- Not from outside procedural statement
- Restriction with
- Named task/function: better readability
endtask : <task_name>
endfunction : <function_name>
- Empty task/function as place holder
Chapter 7 SystemVerilog Procedural Statements
New opeators
++
&--
operatorsi++
is post-increment, while++i
is pre-increment- i = 10; j = i++; // j = 10, i = 11
- i = 10; j = ++i; // j = 11, i = 11
- Behave as blocking assignments, so avoid using them where non-blocking is required
- Combination of operation with assignment
+=
,-=
,*=
,/=
,%=
,&=
,|=
,^=
,<<=
,>>=
,<<<=
,>>>=
- Where
<<<
and>>>
are arithmetic shifting while treating target as signed number, and do sign expansion when needed
- Where
- Wildcard equality operator
==?
and!=?
- Allow don’t care bits: X, Z or ?
- While original
==
and!=
will return unknown (bit x) if either operand is X or Z - Notice: expand vector to same size before comparison. This can be dangerous.
- Synthesizable: right hand operand must be constant expressions
- Membership operator:
inside
if ( a inside {3'b001, 3'b010, 3'b100} )
=if ( (a==3'b001) || (a==3'b010) || (a==3'b100) )
- Right-hand value could be array
- Use X or Z to represent don’t care
- Can be used in case statement
case (instruction) inside
4'b0???: ...
4'b1000, 4'b1100: ...
default: ...
endcase
- Synthesizable: right hand operand must be constant expressions
Operand enhancement
- Type casting
- In Verilog, there is no explicit type casting method
type'(expression)
- Size casting
size'(expression)
- Sign casting
signed'(expression)
unsinged'(expression)
Enhanced for
loops
- Local variables within
for
loop- Ex.
for (int i=0; I <=15; i++)
- Prevent interference between for loops
- They are automatic type
- Doesn’t exist outside
for
loop- If want to be used after for loop, must be declared outside
- Ex.
- Multi
for
loop- Ex.
for (int i=1, byte j=0; i*j < 128; i++, j+=3)
- Ex.
- Named procedual blocks, to access local variables
- But variable within
for
loop cannot be accessed hierarchically, must be declared outsidefor
loop inside named procedual block
- But variable within
do … while
loops
- A while loop might not execute at all
- Synthesizable: statically determine how many times a loop will execute
foreach
loops
- To interate elements of single- and multi-dimentional arrays
break
, continue
, return
- C-style jump statements, to replace old
disable
statement - More intuitive and concise
Enhanced block names
- Named
end
to paire with namedbegin
- For readability
Statement lables
- For readability
- :
- Illegal to have both label and name
Enhanced case
unique case
- = parallel case + full case
- Runtime check
- Pros: actual value will be checked, no false alarm
- Cons: dependent on the thoroughneess of the verification tests
- Clear design intent
priority case
- = full case
- Runtime check
- Notice: if it’s not full case, latches can be inferred
Enhanced if ... else
unique if ... else
- The order of the decisions is not important
- Cannot have overlapping conditions (similar to parallel case)
priority if ... else
- Clearly defined design intent
Chapter 8: Modeling Finite State Machines with SystemVerilog
This chapter gives some simple example of FSM code featuring SystemVerilog new keywords, such as enum
, always_comb
, always_ff
, unique case
.
Modeling FSM with enum
- 3 blocks to model an FSM
- Incrementing state
- Determine the next state
- Set output
- Using
enum
without explicitly specified value- Cause mismatch in value between RTL and gate-level netlist
- Cause difficulty with assertion to work for both RTL and gate-level netlist
- So, DO specify value for
enum
- Can use
one-hot
,one-cold
,Gray code
, etc.
- Can use
- Synthesis compiler may try to optimize these explicitly defined values
- Reversed case statement
- The following example is seemly complicated (overkill), but actually have 2 advantages
- Eliminate the possibility to define wrong one-hot/cold state value
- Easier for future extension
- The following example is seemly complicated (overkill), but actually have 2 advantages
- Unique and parallel case (refer to Chapter 7)
module traffic_light (
output logic green_light,
yellow_light,
red_light,
input sensor,
input [15:0] green_downcnt,
yellow_downcnt,
input clk, rstb
);
// index of RED/GREEN/YELLOW bit in the state register
enum { R_BIT = 0,
G_BIT = 1,
Y_BIT = 2
} state_bit;
// state register
enum logic [2:0] { RED = 3'b001 << R_BIT,
GREEN = 3'b001 << G_BIT,
YELLOW = 3'b001 << Y_BIT
} state, next_state;
always_ff @ (posedge clk, negedge rstb) begin : step_forward
if (!rstb) begin
state <= RED;
end else begin
state <= next_state;
end
end : step_forward
always_comb begin : set_next_state
unique case (1'b1) // reversed case statement
state[R_BIT]: begin
if (sensor) begin
next_state = GREEN;
end
end
state[G_BIT]: begin
if (green_downcnt == 0) begin
next_state = YELLOW;
end
end
state[Y_BIT]: begin
if (yellow_downcnt == 0) begin
next_state = RED;
end
end
endcase
end : set_next_state
always_comb begin : set_output
unique case (1'b1)
state[R_BIT]: begin
red_light = 1'b1;
green_light = 1'b0;
yellow_light = 1'b0;
end
state[G_BIT]: begin
red_light = 1'b0;
green_light = 1'b1;
yellow_light = 1'b0;
end
state[Y_BIT]: begin
red_light = 1'b0;
green_light = 1'b0;
yellow_light = 1'b1;
end
endcase
end : set_output
endmodule
- Specify unused state values
- In Verilog, we used to have
default: next_state = 3’bxxx;
to tell synthesis compiler that the default case is an unused value. - In SystemVerilog, we can use either
unique case
orparallel case
to state that this is a full case statement.- Better, because of runtime check
- In Verilog, we used to have
- Always assign enumerated type variable with a label from its enumerated list, instead of a value or an expression, although sometimes they are legal after type casting. 2-state type in FSM
- The idea is dangerous, because 2-state type variables initialize to logic 0 instead of logic X before applying reset. So it’s not how real circuit behave.
Chapter 9: SystemVerilog Design Hierarchy
Module prototypes: extern
- Similar to C-Style *.h head file
- More convenient: no duplicate port definition needed
- Can be defined in one file, then ``include` to other files
- No necessarily
Named ending statements
endmodule : <module_name>
- Also apply for others
package … endpackage
interface … endinterface
task … endtask
function … endfunction
begin … end
Nested module
- Verilog limitations
- Module names are global
- No restriction to be accessed
- Cause name conflicts
- Module names are global
- SystemVerilog improvements: nested module
- Modules that are declared within modules
- Not visible outside the scope
- Can be instantianted by the parent module and the modules below
- Can also access variable/constant/task/function in $unit
- However, controversy with common moduel style that every module has it’s own file
- This kind of style is good for large designs; and good for utilizing VCS
- Use ``include` to keep the same style
Simplify module instance
- Named port connection is good for documenting the design intent, but too verbose
.name
connection.port_name(net_name)
->.port_name
if port_name equals to net_name
.*
connection- Matches all cases that port_name equals to net_name
- These simplification also apply to function/task
Net aliasing
alias
statementalias clk = clock = ck; // can be multiple aliases together
- Only applies for net types, with the same type, and the same size
wire [31:0] n1; wire [3:0][7:0] n2; alias n2 = n1;
- By default, infer
wire
if left-hand side names are not defined explicitly - Alias vs. assign
- Assign is copied from right to left whenever right is changing
- Alias is copied to all whenever either one is changing
- Alias with
.name
and.*
is powerful- On top-level module, even if the local net_name is different from the port_name of its instances, we can still use
alias
to make them the same and use.*
- On top-level module, even if the local net_name is different from the port_name of its instances, we can still use
Passing values through module ports
- SystemVerilog removes most port restrictions, except the following two
- Varaible can have only one single source
- Unpacked types must be identical
- Declared using the same
typedef
definition
- Declared using the same
Reference ports ref
- Reference the hierarchical source directly
- Warning: one variable can be written from multiple source
- NOT synthesizable
Enhanced port declaration
- Verilog-2001
- Port list =
- By default, type is wire
- SystemVerilog
- The first port’s direction could be optional, by default is
inout
- Later on, direction could be optional, by default is the same with previous one
- The first port’s direction could be optional, by default is
Parameterized types
- Net, variable of a module could be parameterized
module adder #(parameter type DATA_TYPE = shortint) (
input DATA_TYPE a, b, // redefinable
output DATA_TYPE sum, // redefinable
logic carry // direction is `output`, the same with sum
);
// ...
endmodule : adder
module top ();
adder #(.DATA_TYPE(int)) int_adder( /* ... */ );
adder #(.DATA_TYPE(int unsigned)) uint_adder( /* ... */ );
endmodule : top
Chapter 10: SystemVerilog Interfaces
Concepts
- How Verilog models connects between blocks
- Directly on physical connections in actual hardware level
- Disadvantage
- Port connection must be duplicated in several modules
- Communication protocols must be duplicated also
- Duplication leads to mistakes that is hard to debug
- Changes in spec involves lots of modification
- Details of connection must be defined in early design cycle (not good for top-down design paradigm)
interface
keyword- Several signals grouped together to represent as a single port
- And modules use interface as a single port
- Interface contents
- Discrete signals and port
- Communication protocol, defined as task/function
- Protocol checker and verification routines
- Interface vs. module
- Interface doesn’t have hierarchy
- Interface can be used as module port
- Interface can contain
modport
which can represent different usage env. How to declare an interface?
- Similar to module, with
interface … endinterface
keyword- Can have ports: external signals to interface
- Can use
.name
and.*
for connection abbreviation
- Declaration order
- Just as module, no order needed
- Global vs. local
- Just as module, global definition can be used anywhere, local definition can be used in certain scope (for IP) How to use interface?
- As module ports
- Explicitly named vs. generic
- Explicitly named interface port can only connect to an interface with the same name
module <module_name> ( <interface_name> <port_name);
- Generic interface can connec to any interface port
module <module_name> ( interface <port_name);
- Both are synthesizable
- Instantiate and connect interface
- ILLEGAL leave an interface port unconnected
.name
and.*
can be used to connect interface
- Referencing interface’s signals
- Use dot: <port_name>.<internal_signal_name>
Modport
- Differnet views of interface
- Ex. a interrupt sub-signal could be input to CPU, but output to peripheral modules
modport
means module port- Contains only direction and signal names, not vector size or types
- Selecting which modport to use
- In module instance
<module_name> <instance_name> ( .<port_name>(<interface_instance_name>.<modport_name>) );
- In module delcaration (better because of consistency)
module <module_name> ( <interface_name>.<modport_name> <port_name> );
- NOT use both methods above
- If no modport is specified
- all nets have
inout
direction, by default - all variables have
ref
type, by default
- all nets have
- Synthesizable for both
- Some synthesis tool will convert (expand) interface modport to normal ports automatically
- In module instance
- To define different sets of connections
- Hide certain signals in different modport: incompleleted signal list while defining one modport
- Internal signals that are not accessable from any modport
- Might be used for protocol checkers or other functionality
- Example
interface cpu_bus (input logic clk, rstb, test_en);
wire [15:0] data;
wire [15:0] addr;
logic [ 7:0] slave_cmd;
logic slave_req;
logic bus_grant;
logic bus_req;
logic slave_ready;
logic data_ready;
logic mem_ren;
logic mem_wen;
modport master (
inout data,
output addr,
output slave_cmd,
output slave_req,
output bus_grant,
output mem_ren,
output mem_wen,
input bus_req,
input slave_ready,
input data_ready,
input clk,
input rstb,
input test_en
);
modport slave (
inout data,
inout addr,
output mem_ren,
output mem_wen,
output bus_req,
output slave_ready,
input slave_cmd,
input slave_req,
input bus_grant,
input data_ready,
input clk,
input rstb,
input test_en
);
modport mem (
inout data,
output data_ready,
input addr,
input mem_ren,
inout mem_wen
);
endinterface
module top ();
// instance of an interface
cpu_bus bus ( .* );
processor proc1 ( .bus(bus.master), .* );
slave1 slave1 ( .bus(bus.slave), .* );
slave2 slave2 ( .bus(bus.slave), .* );
dual_port_ram mem ( .bus(bus.mem), .data_b(next_instruction), .* );
// test generator need to access everything in inside `bus`
test_generator test_gen ( .bus(bus), .* );
endmodule
Task/function in interface
- Implement the details of communication protocol
- Written once, shared by all modules connected using the same interface
- Values are passed to interface methods as input argument
import
when definingmodport
- Either use the name only, or the full prototype
- Latter is useful when task is defined somewhere else
- Either use the name only, or the full prototype
- Access using <interface_port_name>.<method_name>
- Task/function must be automatic to be synthesizable
- Exporting task/function are not synthesizable
- Define task/function in module, then export it to interface, and use it in other modules
- Export from module’s all instances
extern forkjoin
- Useful when you want to broadcast signals, such ask counting one module’s instances Procedural blocks in interface
- Used for verfication and protocol checker
- NOT synthesizable Reconfigurable interface
- Parameterized interface
- The same as modules
- Generate statement
- The same as modules
Chapter 11: A Complete Design Modeled with SystemVerilog
Interesting part: how to implement a latch-based LUT with SystemVerilog interface
// implement LUT (basically an SRAM/Register File with interface in SV)
interface if_look_up_table;
parameter int ADDR_SIZE = 8;
parameter int ADDR_RANGE = 1 << ADDR_SIZE;
parameter type DATA_TYPE = logic; // the bit-cell's type could be reconfigurable
DATA_TYPE mem [0:ADDR_RANGE-1];
// function to perform write operation
function void write (
input [ADDR_SIZE-1:0] addr,
input DATA_TYPE data
);
mem[addr] = data;
endfunction : write
// function to perform read operation
function void read (
input [ADDR_SIZE-1:0] addr
);
return (mem[addr]);
endfunction
endinterface
// Then in circuit where instantiate this LUT
typedef struct packed {
logic [`NUM_TX_PORT-1:0] FWD;
logic [11:0] VPI;
} cell_cfg_t;
if_look_up_table #(.ADDR_SIZE(8), .DATA_TYPE(cell_cfg_t)) lut();
always_latch begin
if (lut_wen) begin
lut.write(lut_waddr, lut_wdata);
end
end
always_comb begin
if (lut_ren) begin
lut_rdata = lut.read(lut_raddr);
end else begin
lut_rdata = 'hz;
end
end