# SystemVerilog Basics SystemVerilog describes both hardware (digital circuits) and testbenches (software code to test them). Some constructs synthesize into hardware; others exist only for simulation. :::{admonition} Keywords & Features of SystemVerilog Avoided in this Course `reg`, `wire`, `assign`, `always` ::: SystemVerilog is a strict superset of old Verilog, which dates to 1984. Therefore is one of the most complex languages ever, with a lot of historical baggage and countless footguns, that have been since superseded by newer features that allow us to write clean code. To prevent wasting our limited time debating those, we will avoid the above. However, this page explains various details for the sake of completeness. ## Datatypes Old Verilog mainly used `wire` and `reg`, both four-state types: - A `wire` is driven continuously, for example by `assign` or a module output. - A signal assigned inside an `always` block must be declared `reg`. Despite its name, `reg` does **not** mean a hardware register. The statements in the `always` block determine the hardware. This old Verilog describes a combinational multiplexer, but `y` must still be a `reg`: ```systemverilog reg y; // named reg, but becomes combinational always @(*) begin if (sel) y = b; else y = a; end ``` SystemVerilog introduced `logic`, a four-state type (`0`, `1`, `x`, `z`) that works for most single-driver RTL signals. Use `logic` by default. Use an explicit net type such as `wire` for continuous or multiple drivers. `tri` behaves like `wire`, but emphasizes that tri-state or multiple drivers are expected. #### Four-State Logic `logic` can represent `x` when a value is unknown and `z` when a net is not driven. These values help simulation expose missing resets, conflicting drivers, and incomplete assignments. - `==` and `!=` can produce `x` when an operand contains `x` or `z`. - `===` and `!==` compare all four states and always produce `0` or `1`. Use `==` for normal RTL comparisons. Use `===` mainly in testbenches when checking explicitly for `x` or `z`; careless use can hide an unknown-value bug. #### Two-State Logic Often we use `bit` in testbenches, in the place of `logic`. It represents two-state logic, either `0` or `1`. ### Vectors A **vector** is an ordered group of bits treated as one value, such as `logic [7:0]`, an 8-bit vector numbered from 7 down to 0. Most built-in integral types are essentially convenient names for fixed-width vectors: | Type | Vector equivalent | States | |---|---|---| | `byte` | `bit signed [7:0]` | 2-state | | `shortint` | `bit signed [15:0]` | 2-state | | `int` | `bit signed [31:0]` | 2-state | | `longint` | `bit signed [63:0]` | 2-state | | `integer` | `logic signed [31:0]` | 4-state | | `time` | `logic [63:0]` | 4-state | Explicit packed vectors are usually clearer for RTL because their hardware width is visible in the declaration. SystemVerilog silently creates a one-bit wire for some undeclared names. Prevent misspellings from becoming implicit wires by placing this before the modules in a source file: ```systemverilog `default_nettype none ``` ### Packed and Unpacked Arrays Dimensions **before** the name are packed; dimensions **after** the name are unpacked: ```systemverilog logic [7:0] byte_value; // one packed 8-bit value logic [3:0][7:0] word; // one packed 32-bit value: four bytes logic [7:0] memory [0:255]; // 256 unpacked elements, each 8 bits logic [A-1:0][B-1:0][C-1:0] signal_name [D-1:0][E-1:0][F-1:0]; // Select one bit as signal_name[a][b][c][d][e][f]. ``` A packed array is one contiguous integral value so it supports arithmetic, bitwise operations, and slicing. An unpacked array is a collection of separate elements, useful for memories and lists. For `memory`, `memory[3]` selects an 8-bit element and `memory[3][0]` selects one bit within it. Packed arrays can be assigned directly to flat vectors of the same total width and vice versa; unpacked arrays cannot: ```systemverilog logic [B-1:0][A-1:0] signal_1; logic [B*A-1:0] signal_2; always_comb signal_2 = signal_1; ``` Some synthesis tools may not support some SV features. Yosys does not support packed array parameters. Therefore, we pass a flattned array as a parameter and then assign it to a local packed view for indexing: ```systemverilog parameter logic [B*A-1:0] P = '0; logic [B-1:0][A-1:0] p_view; always_comb p_view = P; ``` ### `typedef`, `enum`, and `struct` `typedef` gives a type a reusable name. An `enum` gives meaningful names to encoded values, which is especially useful for states and opcodes: ```systemverilog typedef enum logic [1:0] {IDLE, START, DATA, STOP} state_t; state_t state, next_state; ``` A `struct` groups related fields: ```systemverilog typedef struct packed { logic valid; logic [7:0] data; } packet_t; ``` A `packed struct` is one contiguous vector and can pass through a port or be assigned as a whole. An unpacked struct is a collection of separate members. ### Literals, Widths, and Casts An integer literal can specify its width and radix: ```systemverilog 8'b1010_0011 // 8-bit binary 12'hA5F // 12-bit hexadecimal 6'd42 // 6-bit decimal 4'o7 // 4-bit octal ``` The general form is `width'radix value`, where the radix is `b`, `o`, `d`, or `h`. Add `s` for a signed literal: `8'shFE`. Four-state literals may contain `x` (unknown) and `z` (high impedance). The literals `'0`, `'1`, `'x`, and `'z` fill the destination width: ```systemverilog mask = '1; // all 32 bits are 1 ``` Widths and signedness affect arithmetic, comparisons, and extension. Prefer sized literals and make conversions explicit: ```systemverilog logic signed [7:0] a; logic signed [8:0] sum; sum = 9'(a) + 9'sd1; // size cast count = $clog2(N)'(N); // expression sized to the counter width value = $signed(a) + $signed(sum); // interpret the bit pattern as signed ``` `$bits(value)` returns a type or expression's width. A cast changes how an expression is interpreted; it does not add hardware by itself. ## Operators ### Concatenation, Replication, and Slices ```systemverilog word = {upper_byte, lower_byte}; // concatenate extended = {{8{value[7]}}, value}; // replicate the sign bit ``` A fixed slice uses `[msb:lsb]`. An indexed part-select chooses a fixed number of bits from a variable starting point: ```systemverilog byte0 = word[7:0]; byteN = word[start +: 8]; // start, start+1, ..., start+7 byteN = word[start -: 8]; // start, start-1, ..., start-7 ``` ### Logical, Bitwise, and Reduction Operators Logical operators treat each operand as true or false and produce one bit. If a vector is all zeros, it is false, else it is true. Bitwise operators act independently on every bit. Reduction operators combine all bits of one vector into one bit: ```systemverilog valid = ready && enable; // logical AND, equivalent to: (ready != 0) AND enable != 0) masked = data & mask; // bitwise AND, equivalent to: for (i=0;i