Discussion of the strategy for translating VL modules (and structs, interfaces, etc.) to SV modules.
This topic covers the general idea of how we translate a simplified VL design into an SV module alist. The top-level function for this is vl-design->svex-modalist, not to be confused with vl-design->sv-design which additionally runs the series of transforms necessary to simplify a design once loaded.
The input to this translation is a VL design which has had module parameters and generate blocks resolved, expressions sized, and always blocks eliminated, among other requirements. (Vl-design->sv-design performs the necessary transforms before calling vl-design->svex-modalist.)
The crux of this translation is the translation of VL expressions to svex expressions, which is discussed in vl-expr-svex-translation. Here we'll discuss how these expressions are built into an SVEX module hierarchy to mirror (and sometimes expand on) the VL hierarchy, in such a way that these modules can then be flattened and compiled into an SVEX-based FSM (see sv::svex-compilation).
The final goal of the SVEX translation and compilation process is to obtain a FSM-style flat table of expressions that gives formulas for wire values and next-states of stateholding elements. To do this, one of the major challenges is flattening a module hierarchy so that all the possible ways of referring to a given wire collapse into one canonical one. For example, take the following module hierarchy:
module c (input [3:0] ci, output [5:3] co); endmodule module b (input [5:2] bi, output [4:0] bo); c cinst (.ci(bi[5:4]+4'b10, .co(bo[4:2])); endmodule module a (); wire [3:11] w; b binst (.bi(w[3:6]), .bo({w[9:11], w[7:8]})); endmodule })
Here, the following expressions all refer to the same 3-bit chunk:
w[9:11] binst.bo[4:2] binst.cinst.co[5:3]
To make sense of these modules, if we have expressions within module
The svex compilation process (see sv::svex-compilation) deals with this by collecting a table of aliases among wires, and then using a union-find-like algorithm to find a canonical form for each wire (see sv::alias-normalization). The input for this algorithm that we want to collect for the above module hierarchy is the following list of alias pairings:
w[3:6] <--> binst.bi[5:2] {w[9:11], w[7:8]} <--> binst.bo[4:0] binst.bo[4:2] <--> binst.cinst.co[5:3]
(Note: We have one alias pair for each port connection except for the
These names are shown relative to the top-level module, but initially, in vl-design->svex-modalist, aliases are associated with the module in which they were generated and the names in those aliases are relative to that module. So we generate a module hierarchy something like this:
module c (); wire [3:0] ci; wire [5:3] co; endmodule module b (); wire [5:2] bi; wire [4:0] bo; c cinst (); assign cinst.ci[3:0] = bi[5:4]+4'b10; alias bo[4:2] = cinst.co[5:3]; endmodule module a (); wire [3:11] w; b binst (); alias w[3:6] = binst.bi[5:2]; alias {w[9:11], w[7:8]} = binst.bo[4:0]; endmodule
With this approach, relative-scoped hierarchical identifiers are dealt with automatically by alias normalization. This approach to aliasing lends itself rather naturally to dealing with complex datatypes and interfaces. We turn structs, unions, arrays, and interfaces into "modules" that each have some internal aliases to set up the relationships among the local wires. For example, suppose we have the following module with a struct-typed variable:
typedef struct { logic [3:0] a; logic [2:4] b; } mystruct; module a (); mystruct m; endmodule
This gets turned into a module hierarchy as follows:
module struct##mystruct (); logic [6:0] __self; // represents the whole struct logic [3:0] a; logic [2:4] b; alias __self[6:3] = a[3:0]; alias __self[2:0] = b[2:4]; endmodule module a (); // m becomes both a wire and also a module instance: logic [6:0] m; struct##mystruct m (); alias m = m.__self; endmodule
It wouldn't be possible in Verilog to have
This approach to array indexing also lets us deal straightforwardly with instance arrays; see vl-instarray-nested-aliases for details.
Given a module hierarchy like the examples from the previous section, it is straightforward to flatten the hierarchy into a list of assignments and aliases. Then the alias normalization algorithm is able to compute canonical forms for all aliased wires, and the names used in the assignments can be normalized.
One complication of this picture is that modules may contain nested scopes, in which variable names may shadow those in higher scopes. For example, generate blocks produce scopes within modules:
module a (); wire wa; wire wb = 1; if (1) begin : myblock wire wb = 0; // shadows module-global binding assign wa = wb; end wire wc = myblock.wb; // test: initial begin #10; $display("wa: %b", wa); $display("wb: %b", wb); $display("wc: %b", wc); end endmodule
This shows the 0,1,0 as the values of
module genblock##a.myblock (); wire wb; assign wb = 0; assign $upscope(1, wa) = wb; endmodule module a (); wire wa; wire wb; wire wc; genblock##a.myblock myblock (); assign wb = 1; assign wc = myblock.wb; endmodule