• Top
    • Documentation
    • Books
    • Boolean-reasoning
    • Projects
    • Debugging
    • Std
    • Proof-automation
    • Macro-libraries
    • ACL2
    • Interfacing-tools
    • Hardware-verification
      • Gl
      • Esim
      • Vl2014
        • Warnings
        • Primitives
        • Use-set
        • Syntax
        • Getting-started
        • Utilities
        • Loader
        • Transforms
          • Expression-sizing
          • Occform
          • Oprewrite
          • Expand-functions
            • Vl-expr-expand-function-calls
            • Vl-expand-function-call
            • Vl-module-expand-functions
            • Vl-modulelist-expand-functions
            • Vl-check-bad-funcalls
            • Vl-funtemplate
            • Vl-funbody-to-assignments
            • Vl-assignlist->rhses
            • Vl-fundecl-expand-params
            • Vl-fun-stmt-okp
            • Vl-fundecllist-expand-params
            • Vl-remove-fake-function-vardecls
            • Vl-design-expand-functions
          • Delayredux
          • Unparameterization
          • Caseelim
          • Split
          • Selresolve
          • Weirdint-elim
          • Vl-delta
          • Replicate-insts
          • Rangeresolve
          • Propagate
          • Clean-selects
          • Clean-params
          • Blankargs
          • Inline-mods
          • Expr-simp
          • Trunc
          • Always-top
          • Gatesplit
          • Gate-elim
          • Expression-optimization
          • Elim-supplies
          • Wildelim
          • Drop-blankports
          • Clean-warnings
          • Addinstnames
          • Custom-transform-hooks
          • Annotate
          • Latchcode
          • Elim-unused-vars
          • Problem-modules
        • Lint
        • Mlib
        • Server
        • Kit
        • Printer
        • Esim-vl
        • Well-formedness
      • Sv
      • Fgl
      • Vwsim
      • Vl
      • X86isa
      • Svl
      • Rtl
    • Software-verification
    • Math
    • Testing-utilities
  • Transforms

Expand-functions

Expand away function declarations and calls.

This transform can eliminate a fairly reasonable subset of Verilog functions by replacing their uses with ordinary assignments.

Note: that our way of handling functions does not really preserve hierarchical identifiers that lead into functions.

Supported Subset

The automatic keyword may be provided or omitted, but the subset we support makes its use fairly meaningless (essentially we only implement functions that behave the same whether automatic or not.)

The return type can be :vl-signed or :vl-unsigned (which is our representation of a "plain" return type) and a range is permitted. But we do not support functions whose return type is integer, real, realtime, or time.

There may be any number of inputs of type :vl-signed or :vl-unsigned (which is our representation for plain and reg type inputs). Each input may optionally have a range. But we do not support inputs whose types are integer, real, realtime, or time.

There may be any number of reg declarations, each of which may be signed or not and may have a range. We do not permit regs with array dimensions. We also do not permit regs with initial values (per the Verilog grammar this shouldn't be possible, but our vl-vardecl-p representation does not forbid it.)

There may be any number of plain parameter and localparam declarations. By plain, we mean that we do not allow ranges, the signed keyword, etc. (There doesn't seem to be any way to initialize parameters in a function, so this is basically just a way to define function-local constants.) We do not allow parameters that have the same names as other parameters in the module, because this can lead to subtle issues related to declaration order.

We do not permit variable declarations (e.g., integer variables, real variables, etc.) or event declarations.

The body of the function must contain a flat list of assignments, starting with assignments to variables that have been declared within the function (if any), and ending with an assignment to the function's name (in all cases). We do not allow any branching, looping, timing, case statements, etc.

This restriction is overly severe and parts of it may eventually be lifted. At present we at least do some basic rewriting of the statement, which allows us to flatten sub-blocks. In the long run, we may eventually want to try to support at least some if-then-else statements, case statements, etc.

Finally, we require that every variable declared in the function is completely written before it is read. This restriction is intended to ensure that a function's result depends only upon its inputs and the other, current state of the module that it is inspecting. For instance, we allow you to write functions such as:

function foo ;
  input a;
  input b;
  reg r1;
  reg r2;
  begin
   r1 = a & b;
   r2 = r1 & c;  // c is presumably a wire in the module
   foo = r1 ^ r2;
  end
endfunction

But we do NOT allow you to switch the order of these statements, e.g.,

begin
 r2 = r1 & c;  // using the old value of r1
 r1 = a & b;
 foo = r1 ^ r2;
end

Because this can lead to very strange, timing-sensitive interactions when there are multiple calls of the function, and generally seems like an unreasonable thing to do.

We do not support recursive functions. I mean, come on. Do you think we are Lisp programmers or something?

Transformation Approach

Given the above restrictions, there are a couple of basic approaches that we could take for eliminating functions and replacing them with ordinary assignments. Here is a sample module that we can consider.

module foo (...);
  wire w = ...;

  function f ;
    input a;
    input b;
    f = (a ^ w) | tmp;
  endfunction

  wire r1 = f(v1, v2) & f(v2, v3);
  wire r2 = f(v3, v4);
endmodule

An inlining approach would be to mangle the names of the wires in the function and lift its assignments up into the containing module, e.g.,

module foo (...);
  wire w = ...;

  wire _tmp_f1 = (v1 ^ w) | v2;
  wire _tmp_f2 = (v2 ^ w) | v3;
  wire r1 = _tmp_f1 & _tmp_f2;

  wire _tmp_f3 = (v3 ^ w) | v4;
  wire r2 = _tmp_f2;
endmodule

Alternately a submodule approach would be to write a new module that implements the function, and replace calls of the function with instances of this submodule. For instance:

module foo$f (o, a, b, w);
  input a, b, w;
  output o;
  assign o = (a ^ w) | b;
endmodule

module foo (...);
  wire w = ...;

  wire _tmp_f1, _tmp_f2, _tmp_f3;

  foo$f _finst_1 (_tmp_f1, v1, v2, w);
  foo$f _finst_2 (_tmp_f2, v2, v3, w);
  wire r1 = _tmp_f1 & _tmp_f2;

  foo$f _finst_3 (_tmp_f3, v3, v4, w);
  wire r2 = _tmp_f3;
endmodule

Both approaches have some good and bad things about them. Inlining is nice because we don't have to think very hard about how the use of non-inputs works. For instance, in the submodule approach we have to realize that f reads from w, an ordinary wire in the module, and so foo$f has to take this extra input. But with the inlining approach, using w is no big deal and we can probably avoid some of this complication.

Another nice thing about inlining is that most hierarchical references that originate within the function will probably still be working correctly. That is, if we refer to submod.wire from within the function, then this is still a submodule that we can see from the expanded code.

The submodule approach is kind of nice in that it avoids introducing a pile of additional wires and assignments into the main module, and probably helps to keep the transformed output smaller when the functions involved are large. It would probably be possible to account for hierarchical identifiers that are being used within the function through some kind of flattening, e.g., if submod.wire is used by the function's code, then the submodule we create could have an extra input that receives it.

Either approach has problems with hierarchical identifiers that point into the function itself. I'm not sure how to resolve that, but it doesn't seem particularly meaningful to write a HID that points at, say, foo.f.a, since there are multiple instances of f and so which one is being referred to?

For now, I use the inlining approach because it seems somewhat simpler. But it would probably not be too difficult to switch to the submodule approach.

Subtopics

Vl-expr-expand-function-calls
Expand function calls throughout an expression.
Vl-expand-function-call
Main routine for expanding a single function call.
Vl-module-expand-functions
Eliminate functions from a module by inlining functions wherever they are called.
Vl-modulelist-expand-functions
(vl-modulelist-expand-functions x ss) maps vl-module-expand-functions across a list.
Vl-check-bad-funcalls
Cause fatal warnings if function calls are in unacceptable places.
Vl-funtemplate
Function expansion templates, the intermediate representation of functions we use while inlining function calls.
Vl-funbody-to-assignments
Transform a function's body into a list of assignment statements if it is safe to do so.
Vl-assignlist->rhses
(vl-assignlist->rhses x) maps vl-assign->expr across a list.
Vl-fundecl-expand-params
Eliminate parameters from a function.
Vl-fun-stmt-okp
Ensure that a function's statement conforms to the Function Rules in 10.4.4.
Vl-fundecllist-expand-params
Eliminate parameters from a list of functions.
Vl-remove-fake-function-vardecls
Vl-design-expand-functions
Top-level expand-functions transform.