-
Notifications
You must be signed in to change notification settings - Fork 0
SystemVerilog Cheatsheet
The below are a reference to a variety of useful SystemVerilog features and the expected behavior that results.
Interfaces, while extremely handy for simplifying module connections, have a couple of quirks that are non-intuitive.
Parenthesizes come after the array delimiters.
axi4_intf m_axi[INTF_CNT]();
While it is possible to define an array of interfaces in most current SV compilers, the Xilinx Vivado compiler at least does NOT support iterating over such an array in a combinatorial for loop. This means that some code like this is illegal.
for(int i = 0; i < INTF_CNT; i++) begin
data = axi4_s[i].w_data;
end
At the same time, you can iterate over an array of interfaces with a generate for loop, so this code is legal.
for(genvar i = 0; i < INTF_CNT; i++) begin
assign data = axi4_s[i].w_data;
end
There is a weird semantic gap when it comes to instantiating a parameterized interface that can cause some confusion. With a parameterized interface, the modification to the parameter is done with the interface that is used to connect to the module that uses the interface. The parameter value is not set in the module's port list.
This means that the following is incorrect.
module axi_endpoint #(int parameter DATA_WIDTH = 32)(axi4_intf.slave #(.DATA_WIDTH(DATA_WIDTH)) axi4_s);
The interfaces in a module's port list is not where the parameterization happens. It instead happens in whatever is the top level declaration of the interface instance.
axi4_intf #(.DATA_WIDTH(32)) axi4_s();
axi_endpoint axi_endpoint_inst(axi4_s);
The module that the parameterized interface instance is connected to will inherit the specified width.
It is possible to get the parameter value of an interface by accessing it as if it were an interface member like so:
localparam int DATA_WIDTH = s_axis.DATA_WIDTH;
The issue is that not all synthesis tools support this. Internally the first version of Vivado that supported this was 2022.1.
An alternative is to make use of the $bits function, like so.
localparam int DATA_WIDTH = $bits(s_axis.tdata);
This way one does not need to pass down duplicate parameter values that may not match that of the interfaces used.
Datatypes of the packed type can be flattened out into an array of bits and concatenated in various fashions. The following demonstrate the various ways in which the ordering works.
typedef struct packed {
logic [7:0] upper_byte; // [23:16]
logic [7:0] middle_byte; // [15:8]
logic [7:0] lower_byte; // [7:0]
} packed_t;
logic [7:0] upper_byte;
logic [7:0] lower_byte;
logic [15:0] packed_vector;
assign {upper_byte, lower_byte} = packed_vector;
Bindfiles are an extremely useful construct for simulation purposes, but they do have a couple of cases where a syntax template is useful.
Bindfiles can also have parameters like regular modules, but they require manual connection in the bind command like so.
bind rtl_module bind_module #(.DATA_WIDTH(PARENT_DATA_WIDTH)) bind_module_inst(.*);
In the above, the PARENT_DATA_WIDTH parameter passed in is the name of a parameter that exists in the rtl_module that bind_module is being bound to. This needs to be done for all parameters one wants to have overridden in the bind module.