Introduction
Since its ratification in 2005, the SystemVerilog IEEE-1800 standard has experienced broad adoption in the verification and assertion space but has lagged for design constructs. Engineers may be wary of revamping current design methodologies, or they assume that SystemVerilog for design is not relevant to their projects, or they fear that field-programmable gate array (FPGA) synthesis tools do not fully support the new standard. All three of these concerns are either exaggerated or based on misconceptions. SystemVerilog is fully supported by leading synthesis tools, and the new design constructs are in fact relevant to most register-transfer level (RTL) coding styles and easy to learn and integrate into current methodologies.
The Benefits of SystemVerilog
The touted benefits of the design constructs include coding at higher levels of abstraction, compactness of code, and unification of design and verification environments. While it sounds appealing, in an engineer’s mind, a new language translates into a new learning curve, which translates into time to study, evangelize, and adapt to company standards. Most FPGA designers don’t have time to make their code more efficient because of all the code they have to write and would be better at keeping up with the latest standards if there weren’t so many to keep up with. Overwhelmed by tape-out schedules, designers can’t spend their days studying new languages, attending seminars, and perusing technical articles about the latest and greatest design trends.
The truth is, however, that the more pressure there is on design closure, the more relevant SystemVerilog is for design. Thanks to chip size and complexity, RTL is becoming more and more difficult to manage, re-use, and debug. In writing the new specification, the Accellera committee (an industry standards body) set out to address these problems. Its charter for the new SystemVerilog standard was to “extend Verilog IEEE 2001 to higher abstraction levels for architectural and algorithmic design, and advanced verification.”
The good news is that learning SystemVerilog for design is not difficut nor is it a quantum leap in methodology. It can (and should) be thought of as an extension to the existing Verilog standard, as opposed to a different language altogether. Leading FPGA synthesis tools provide support for its synthesizable constructs. Designers need only recognize that the new extensions are easy to adopt, integrate, and tape out into real hardware.
While there are a variety of extensions available, the sub-set flow examined here focuses on the higher level of abstractions offered to RTL design. Each construct builds on the other. The degree of abstraction and complexity is up to the designer.
User-defined types
As a starting point, SystemVerilog allows the definition of new data types based on existing types with the typedef keyword, similar to C, as shown below. This is a fundamental building block in ultimately modeling a complex design at abstract levels while still being accurate and synthesizable. Once it is defined, the user-defined type can be used as with any built-in data-type, making data-structures more readable:
typedef logic [1:0] opcode_t;
opcode_t [1:0] op1;
Note that the above example uses the naming convention of ending a user-defined type with the characters “_t”. This is done as a matter of readability and maintainability, since the user-defined type may have been defined elsewhere in the code or in another file.
In the above example, and the ones to follow, the logic data-type is used frequently so it is worth explaining. This is a replacement to the reg data-type, which is a misleading term since it implies a register is to be inferred, when in fact reg is a variable and may be implemented as sequential or combinational logic, depending on how it is used. The use of logic is recommended so as to avoid confusion. There are other differences between the reg data-type from Verilog-2001 and the logic type, but they will not be examined here.
Enumerated Types
Similar to VHDL’s enumeration data type, enumerated types in SystemVerilog provide the ability to declare a variable with a specific list of valid values, as shown below:
enum {ADD, SUB, MULT} opcode
The variable opcode is given a set of potential user-define names “ADD”, “SUB”, and “MULT” (also known as labels). This can be used in the following case statement (assume a, b, and c have been defined as variables):
…
case (opcode)
ADD: c = a + b;
SUB: c = a – b;
MULT: c = a * b;
endcase
…
The code shown above is readable and intuitive. One might argue that the same can be achieved in Verilog with use of the `define macroor parameter constants to define a set of names with specific values, such as shown below:
‘define ADD 2’b00
‘define SUB 2’b01
‘define MULT 2’b10
…
reg [1:0] opcode;
…
case (opcode)
‘ADD: c = a + b;
‘SUB: c = a – b;
‘MULT: c = a * b;
endcase
In the Verilog example above, the variables are defined as 2-bit vectors, so they can legally be assigned the value 2’b11—a value not accounted for in the above code. Designers typically have to be aware of this potential error. In addition to being more concise, enumerated types not only define a list of user-defined labels for a variable but also limit the variable to the set of valid values specified. By offering stronger type-checking in the language compiler, the code accurately describes designer intent.
Enumerated types can be used with the typedef keyword to create user-defined types with a set of valid values. This is useful when needing to make declarations in many places:
typedef enum {ADD, SUB, MULT} opcode_t;
opcode_t op1, op2;
Structures
SystemVerilog offers structures as the next level of abstraction. Structures are a means of aggregating variables or data-fields under a common name, usually those conceptually related in some way. With the struct keyword, users can intuitively describe data-structures and mask lower-level complexity as needed. Common applications for this include instruction registers, network packets, and bus packets.
struct {
logic [1:0] opcode;
int a, b;
logic [23:0] addr;
} instruction;
This is similar to C and to VHDL’s record construct. In Verilog, however, the members of a structure have to be defined as separate variables and managed individually, forcing the engineer to track how all the pieces fit together. By aggregating elements, data-structures can be represented in the HDL as they are understood conceptually.
Data elements (or members) within a struct can be accessed and manipulated using the struct and member names.
instruction.opcode = 2’b00
An entire struct can be a user-defined type to be re-used throughout in the code—effectively creating higher-level building blocks for design implementation.
typedef struct {
opcode_t opcode;
int a, b ;
logic [23:0] addr;
} instruction_t;instruction_t instr1, instr2;
In the above example, note that the user-defined type opcode_t is a member of the user defined type instruction_t. Similarly, structures can also be declared as members of other structures, allowing multiple layers of abstraction.
They can also be passed through tasks, functions, and module ports, allowing abstraction to be maintained through data flow.
Assign instr1 = instr2;
Unions
Unions enhance structs or any data model by allowing multiple definition for a single storage element. Each definition has to occupy the same storage space (e.g., vector length), different data-types can be used. In the following example6, the same storage space is being defined as the struct data with three members (source_address, destination_address, and data) and in another definition as a two-dimensional array bytes. Both of these definitions represent a 64-bit storage space identified as the union data_reg. Elements of the union are assigned values depending on the definition used (data or bytes).
union packed{
struct packed {
bit [15:0] source_address;
bit [15:0] destination_address;
bit [31:0] data;
} data;bit [7:0][7:0] bytes;
} data_reg;
data_reg.data = data_in; //assumes data_in is a 32-bit vector
dest_low_byte = data_reg.bytes[4];
The above example briefly introduces the concept of packed structures and unions. We will not go into detail on the concept of packed arrays, but know that it refers to all elements being represented as contiguous bits, i.e., as a single vector.
Interfaces
While user-defined types, structures, and unions allow abstract data-modeling, they do not provide a complete mechanism for abstraction of data flow. The interface construct solves this problem by encapsulating port connectivity and functionality related to module-to-module communication. More than just abstraction of data-structures, interfaces raise the abstraction of communication protocols within a design. This can reduce the size of code and improve maintainability.
The figure below shows a system-on-chip (SoC), maintained by the Open Cores Project, with processor (CPU), UART, and parallel input/output (PIO) interface, all communicating through a bus interface following the wishbone specification.
An interface is defined similarly to a module but with the interface keyword as shown below. The set of signals to be bundled in the interface are declared without input or output direction. Since each block in the SoC will see a port differently (input, output, or bi-directional), the subset of signals and port directions for each design block is defined in modports—each modport having a uniqe name. Interfaces support parameters, constants, tasks, functions, procedural blocks, program blocks, assertions, as well as any user-defined type.
When declaring a module in the design, an instance of the interface is used as a single module port, using the name of the interface as the port type. Signal details are masked with this single port declaration, as is all functionality embedded in the interface. As shown in the uart module below, individual signals are referenced within the module as needed.
One benefit of interfaces is bundling wires in one location, thereby masking inter-connectivity, reducing lines of code, and improving readability.
The more significant benefit is being able to localize communication logic between design blocks. The protocol details do not need to be duplicated in multiple modules so when the inevitable change of specification is made, modification to multiple areas of the code is minimized. These changes may include the addition or omission of a signal, a correction to a protocol error, or an entire revamp of the bus specification itself.
Conclusion
Beyond the constructs covered here, the standard provides a wide range of other extensions including enhancements to tasks, functions, loops, procedural blocks, and the addition of new operators, new jump statements, and packages.
SystemVerilog is more of an evolution than revolution in RTL design and does not require an abandonment of existing coding styles. With minimal effort for adoption, designers can realize its true benefits: coding at higher levels of abstraction and improved productivity by unifying the design and verification environment.
References
[1] April 2007 DeepChip Verification Survey
[2] “SystemVerilog enhancements for all chip designers”, Stuart Sutherland, EE Times, 2/26/2004
[3] DAC 2003 Accellera SystemVerilog Workshop
[4] SystemVerilog for Design: A Guide to Using SystemVerilog for Hardware Design and Modeling, Stuart Sutherland, Kluwer, Academic Publishers, Boston, MA, 2004, 0-4020-7530-8.
[5] IEEE Std. 1800-2005 IEEE Standard for SystemVerilog—Unified Hardware Design, Specification, and Verification Language, IEEE, 3 Park Avenue, NY, 2005.
[6] “Getting Ready for SystemVerilog at DAC [2004]” Presentation, Stuart Sutherland, Sutherland HDL, Inc.
[7] OPENCORES Project, opencores.org
8 thoughts on “How To Implement SystemVerilog for FPGA Design”