I think I've figured out why this happens, but I would like to confirm it, and to see if there is a better solution.
Consider the following module which has a function where the default for one of the parameters is bound to some register inside the module:
module m;
reg a, b;
wire out;
function f1 (input x, input y = b);
f1 = x & y;
endfunction :f1
// ...
assign out = f1(a);
endmodule
The issue that I'm seeing (which wasn't easy to track down) is that in this case, the sensitivity list of the assignment only has a. So if b changes, and then a changes, out will be properly updated. However, if a changes, and then b changes, because b isn't in the sensitivity list for the assignment of out, out will not be updated and will still be set to the old value.
Is there a preferred way to add b to the sensitivity list so out will be updated when it changes?
I see a few possible options:
f1(a, b)
always_comb out = f1(a)
or always @(*) out=f1(a)
always @(a, b) out = f1(a)
Personally I think option 1 is the best (even though it would replicate the optional parameters every location it is called), but I'm curious if there are other explanations, or if there is a better solution.
Option 1 defeats the default argument feature. You would have to make sure nobody supplies a default in the declaration so you don't run into this problem again.
Option 3 is a not an option anymore as you discovered—it is a nightmare to debug.
The always_comb
construct was specifically designed for this kind of coding style. In fact, people wanted to write functions with no inputs or outputs simply as a code structuring mechanism making the code easier to manage.
always_comb begin
phase_one_stuff;
phase_two_stuff;
if (mode==A)
mode_A_stuff;
else
mode_B_stuff;
phase_three_stuff;
end
Do not use @(*)
in SystemVerilog. always_comb
replaces it and has the benefit of dealing with time 0 initial values that @(*)
may not be sensitive to.