Using parameterized aggregate datatype in ANSI-style module port list - system-verilog

The following code
module test #(A = 1, B = 2) (output t_my sig);
typedef struct {
logic [A-1:0] fA;
logic [B-1:0] fB;
} t_my;
initial $display("hello\n");
endmodule
returns the VCS error
Error-[SV-UIOT] Undefined interface or type
test.vs, 1
t_my, "sig"
The definition for the forward-referenced interface 't_my' is missing or
't_my' is the name of an undefined user type.
Check to see if the interface definition file/work library is missing or the
definition of the user type is missing.
If I instead do
typedef struct {
logic [A-1:0] fA;
logic [B-1:0] fB;
}t_my;
module test #(A = 1, B = 2) (output t_my sig);
initial $display("hello\n");
endmodule
then I get
Error-[IND] Identifier not declared
test.vs, 2
Identifier 'A' has not been declared yet. If this error is not expected,
please check if you have set `default_nettype to none.
Error-[IND] Identifier not declared
test.vs, 3
Identifier 'B' has not been declared yet. If this error is not expected,
please check if you have set `default_nettype to none.
Is there a way to do what I want using ANSI style module port lists? Note that I can accomplish this without ANSI style port lists as follows:
module test #(A = 1, B = 2) (sig);
typedef struct packed {
logic [A-1:0] fA;
logic [B-1:0] fB; } t_my;
output t_my sig;
initial $display("hello\n");
endmodule
module top;
logic topsig [2:0];
test test1 (.sig ({>>{topsig}}) );
endmodule

When you have a user-defined aggregate type in a port list, you need to put that type in a place where it will be compatible to make a connection, i.e. the module test, and the module above it that will make a connection to that port. There are two ways to accomplish this:
Pass the data type down from from a higher level using a type parameter.
module test#(type T) (input T sig);
parameter A = $bits(sig.fA);
parameter B = $bits(sig.fB);
initial #1 $display(A,, B);
endmodule
module top;
typedef struct {
logic [3:0] fA;
logic [4:0] fB;
} my_t;
my_t s;
test #(my_t) t2(s);
endmodule
displays
# 3 4
or you can put the type in a common package. However to make the struct parameterized, you need to wrap the type in a class. See section 6.25 of the 1800-2012 LRM (This is supposed to be synthesiable)
package p;
class T#(int A,B);
typedef struct {
logic [A-1:0] fA;
logic [B-1:0] fB;
} my_t;
endclass
endpackage : p
module test#(int A=1, B=2) (input p::T#(A,B)::my_t sig);
initial #1 $displayb(sig.fA,, sig.fB);
endmodule
module top;
import p::T;
T#(2,3)::my_t s = '{'1, '1};
test #(2,3) t(s);
endmodule

Related

SystemVerilog macro through task

How to send macros as parameters through a task?
In the testbench:
`define CPU1 tb.top.dual_processor_db_wrapper_i.dual_processor_db_i.cpu1.inst
`define CPU2 tb.top.dual_processor2_db_wrapper_i.dual_processor2_db_i.cpu2.inst
initial begin
fork
cpu_init(`CPU1);
cpu_init(`CPU2);
join
// Other stuff with `CPU1 and `CPU2
`CPU1.write_data(addr, 4, data, resp); // Works
end
task cpu_init(cpu);
cpu.por_srstb_reset(1'b1); // Does not work
// Other init stuff
endtask
Error when compiling:
ERROR: [VRFC 10-2991] 'por_srstb_reset' is not declared under prefix
'cpu'
The type of the `CPUs is unknown (to me). Perhaps Xilinx has a type for it, since it references their MPSoC VIP?
I assume por_srstb_reset and write_data are tasks or functions from Xilinx MPSoC VIP, but I'm not sure.
Xilinx documentation is very sparse
I general, it is possible to pass a macro as an argument to a task. However, it is not possible to pass a hierarchical reference as an argument to a task (it is illegal).
Operations on hierarchical references are very limited, in general.
Your task declaration is equivalent to the following:
task cpu_init (input logic cpu);
The cpu variable is a 1-bit type. So, the following is legal:
`define CPU1 1'b1
cpu_init(`CPU1);
The type of the argument must match between the declaration and the task call.
There is another approach to this problem by using bind and abstract/concrete classes
package pkg;
interface class abstract_init;
pure virtual task init; // prototype for each method you need
endclass
abstract_init lookup[string]; // database of concrete classes for each instance
endpackage
module bind_module #(string lookup_name);
import pkg::*;
class concrete_init implements abstract_init;
function new;
lookup[lookup_name] = this; // register this instance
endfunction
virtual task init;
processor.reset(); // upwards reference
endtask
endclass
concrete_init c = new; // each instance of this module gets registered in lookup
endmodule
`define cpu1 top.dut.cpu1
`define cpu2 top.dut.cpu2
// macro turns any argument into a quoted string
`define Q(arg) `"arg`"
module top;
dut dut();
bind `cpu1 bind_module #(.lookup_name(`Q(`cpu1))) b();
bind `cpu2 bind_module #(.lookup_name(`Q(`cpu2))) b();
initial fork
pkg::lookup[`Q(`cpu1)].init;
pkg::lookup[`Q(`cpu2)].init;
join
endmodule
module dut;
processor cpu1();
processor cpu2();
endmodule
module processor;
initial $display("Starting %m");
task reset;
#1 $display("executing reset on %m");
endtask
endmodule
This is described more detail in my DVCon paper: The Missing Link: The Testbench to DUT Connection.

System Verilog Conditional Type Definition

Is there a way to conditionally select between two types based on a parameter value?
typedef struct packed {
logic a;
} my_type_1_t;
typedef struct packed {
logic [1:0] a;
} my_type_2_t;
parameter type type_t = my_type_1_t;
if (MY_PARAM == 1) begin
typedef my_type_1_t type_t;
do something...
end else begin
typedef my_type_2_t type_t;
do the same something as above with different struct...
end
type_t my_signal;
As you can see, I need to do the same operations in the else clause as in the if clause, but on a different struct. This seems redundant to me, and I am curious if there is a way to avoid duplication.
No, you can't do this because the type_t name will be local to the generate-if block. But it wouldn't make much sense if you could because code that references my_signal is going to want to access my_signal.b and it would not exist in certain cases.
The only way to mimic the behavior you want is to use parameterized modules.
Here is a simple example:
typedef struct {
logic a, b;
} type1_t;
typedef struct {
logic [1:0] a1, b1;
} type2_t;
module top(input logic clk);
pmod#(.T(type1_t)) inst1(clk);
pmod#(.T(type2_t)) inst2(clk);
endmodule
module pmod#(type T=int) (input logic clk);
logic [1:0] a, b;
T t;
if ($typename(T) == "type1_t") begin
always #(posedge clk) begin
a = t.a;
b = t.b;
end
end
else if ($typename(T) == "type2_t") begin
always #(posedge clk) begin
a = t.a1;
b = t.b1;
end
end
// do something with a and b
endmodule
First, question to ask yourself is whether you are needing synthesizable code?
If not, you simply can use a parameterized class with different struct definitions, effectively giving you parameterized structs.
Note that it would be possible for synthesis tools to synthesize such code, yet your mileage with each tool varies, and last time I checked, most synthesis tools didn't even support this limited use of class construct as a scoping mechanism.
Packages, which have much more synthesis tool support unfortunately do not have an option for parameterizing packages/structs. The best I've done is to emulate parameterizable structs using macros, e.g.:
// Macro and struct definitions could be moved to common header file if shared across multiple modules
// Acts like a parameterized typedef
// Note: You could even extend multiple nested levels if needed
// This mechanism requires that the struct definitions take the same form, i.e., logic[0:0] instead of just logic
`define my_type_t(W) struct packed { logic [W-1:0] a; }
// module used as example, but would work in most contexts
module mod;
parameter MY_PARAM = 1;
// In a particular parameterized context, can resolve the macro
// into a real typedef if desired
typedef my_type_t(MY_PARAM) type_t;
type_t my_signal;
// do something that works with either struct definition
endmodule;
Please check my syntax above as I took a previously working example and modified it to match the question from OP, without testing final code segment.

Can you either, forward declare a type to be used as a port type or can you use an interface as an external port?

I'm trying to design some hardware in SystemVerilog and I've run into a problem I can't find the answer to. The situation is that I have a top level module (tracer), which needs to have an output port of a particular type. This type is a typedefed struct which is specified inside an parameterised interface, as this type definition needs to be shared among some of the submodules of the top level module. I want to be able to have an output port that is of a type specified in the interface and to be able to pass that type into various submodules for other purposes.
Crucially I'm also trying to make this hardware as a piece of Custom IP in Vivado (this is one part of a larger project) so I'm aiming for a top-level module where there are some parameters I can define and the rest of the module is fully encapsulated. I realise this will involve placing a Verilog wrapper on top of everything in the end but at the moment I'm just focussing on getting it to run in simulation correctly.
The problem I've had is that because interfaces have to be instantiated you can't use one as a top-level port because where would you instantiate the interface? However because all I'm doing is referring to typedefs inside the interface surely I can forward declare these somehow so that I can refer to the type as a port type and pass it into other submodules?
So essentially does anyone have any idea as to how I might achieve this? If I'm going about this entirely incorrectly then feel free to tell me, the idea of bundling up the typedefs into an interface was provided by dave59 here (https://verificationacademy.com/forums/systemverilog/parameterized-struct-systemverilog-design), and I've spent a day trying to solve this and have got nowhere. Any help gratefully appreciated and any further clarifications I'll happily provide.
Here is the code for each as it currently stands. These are both declared in the same file just for clarity.
Interface Definition
interface trace_if #(
parameter TDATA_WIDTH = 32,
parameter INSTR_ADDR_WIDTH = 32,
parameter INSTR_DATA_WIDTH = 32,
parameter DATA_ADDR_WIDTH = 32);
... (IF_DATA etc. defined here)
typedef struct packed {
bit [INSTR_DATA_WIDTH-1:0] instruction;
bit [INSTR_ADDR_WIDTH-1:0] addr;
bit pass_through;
IF_data if_data;
ID_data id_data;
EX_data ex_data;
WB_data wb_data;
} trace_format;
trace_format trace_output;
endinterface
Top-Level Module
module tracer
#(
parameter INSTR_ADDR_WIDTH = 32,
parameter INSTR_DATA_WIDTH = 32,
parameter DATA_ADDR_WIDTH = 32,
parameter TDATA_WIDTH = 32,
parameter TRACE_BUFFER_SIZE = 64
)
(
... other ports declared
trace_if.trace_output trace_data_o
);
// Instantiating the interfaces for communication between submodules
logic if_data_valid;
trace_if #(TDATA_WIDTH, INSTR_ADDR_WIDTH, INSTR_DATA_WIDTH, DATA_ADDR_WIDTH) if_data_o();
logic id_data_ready;
trace_if #(TDATA_WIDTH, INSTR_ADDR_WIDTH, INSTR_DATA_WIDTH, DATA_ADDR_WIDTH) id_data_o();
logic ex_data_ready;
trace_if #(TDATA_WIDTH, INSTR_ADDR_WIDTH, INSTR_DATA_WIDTH, DATA_ADDR_WIDTH) ex_data_o();
logic wb_data_ready;
// Instantiating the submodules
if_tracker if_tr (.*);
id_tracker #(INSTR_DATA_WIDTH, DATA_ADDR_WIDTH, TRACE_BUFFER_SIZE) id_tr(.if_data_i(if_data_o), .*);
ex_tracker #(DATA_ADDR_WIDTH, 256, TRACE_BUFFER_SIZE) ex_tr(.id_data_i(id_data_o), .wb_previous_end_i(previous_end_o), .*);
wb_tracker #(TRACE_BUFFER_SIZE) wb_tr(.ex_data_i(ex_data_o), .wb_data_o(trace_data_o), .*);
... other module related stuff
endmodule
These things are easier if you publish an MCVE. I think what you are asking is this:
interface i #(parameter p = 1);
typedef struct packed {
bit [p-1:0] b;
} s_t;
s_t s;
endinterface
module m #(parameter p = 1) ( /* how can I have a port of type s_t ? */ );
i i0 ();
endmodule
If so, I think you need to put your struct in a package:
package pkg;
localparam l = 1;
typedef struct packed {
bit [l-1:0] b;
} s_t;
endpackage
interface i #(parameter p = pkg::l);
pkg::s_t s;
endinterface
module m #(parameter p = pkg::l) ( pkg::s_t s );
i i0 ();
endmodule
https://www.edaplayground.com/x/4zQ_

How to pass parameterized class to a module instance?

include "cls.sv"
module top();
int x;
spl_cls #(10) o_spl_cls;
/*Code Continues, here o_spl_cls object has been created using
new();*/
dummy i_dummy(o_spl_cls); //I instantiated another module and passed
//object
endmodule
//This is my dummy module
module dummy(spl_cls i_spl_cls);
....
endmodule
//my spl_cls is
class spl_cls#(int data=0);
//...rest code
endclass
I am getting Error.
How to pass parameterized object to another module instance or class?
You will need to parameterize all references to spl_cls that you want to make assigments to.
module dummy(spl_cls#(10) i_spl_cls);
....
endmodule
A better solution would be to use a typedef
module top();
typedef spl_cls #(10) spl_cls_10;
int x;
spl_cls_10 o_spl_cls;
/*Code Continues, here o_spl_cls object has been created using
new();*/
dummy #(spl_cls_10) i_dummy(o_spl_cls); //I instantiated another module and passed
//object
endmodule
//This is my dummy module
module dummy #(type C)(C i_spl_cls);
....
endmodule

System Verilog: enum inside interface

I have an interface:
interface my_intf();
typedef enum logic [1:0] {
VAL_0 = 2'b00,
VAL_1 = 2'b01,
VAL_2 = 2'b10,
VAL_3 = 2'b11
} T_VAL;
T_VAL val;
endinterface
My module uses this interface:
my_intf intf;
The problem is to assign the val with a value from the enum.
I can assign it as:
intf.val = 0; (and receiving warning or error)
but not as:
intf.val=VAL_0;
Nor as
intf.val = my_intf.T_VAL.VAL_0
How I overcome that problem?
I have only dealt with packages for containing enums before, and avoid interfaces. This is how I use packages. Import the package before the module definition with which you want to use it:
import my_intf_pkg::* ;
module bla(
output my_val_t intf
);
initial begin
intf = VAL_0 ;
end
endmodule
The package containing enums might look like:
package my_intf_pkg;
typedef enum logic [1:0] {
VAL_0 = 2'b00,
VAL_1 = 2'b01,
VAL_2 = 2'b10,
VAL_3 = 2'b11
} my_val_t;
endpackage : my_intf_pkg
Note that the VAL_0 etc are global and not tied to the T_VAL typedef. Therefore I often make them a bit more unique including the typedef in the name. T_VAL_0 for T_VAL typedefs etc.
Here is an example on EDAplayground.
intf.val = 0; should be an error because you are trying to assign a integral type to an enum without a cast.
intf.val = VAL_0; is an error because VAL_0 is not defined in the current scope.
You should be able to do
intf.val = intf.VAL_0;
However, the best solution in to put shared types in a package, an import the package where needed.