21 / 37 · Concept
function automatic and reusable expressions
Write functions that consume no simulation time and check return width, signedness and reference-model independence.
Lessons are free to read. Enroll to save your learning progress.
Reuse a calculation without hiding its contract
A SystemVerilog function returns a value without consuming simulation time. In synthesizable combinational RTL, keep it dependent on explicit inputs, assign the return value on every path, and do not put a delay or event control inside it. automatic gives each call its own argument and local-variable storage in the simulation model; it does not add a register or a clock cycle.
This function computes the XOR of eight input bits. The loop has a fixed bound. The corresponding circuit is a combinational parity reduction.
module parity_function (
input logic [7:0] data,
output logic parity,
output logic [7:0] with_parity
);
function automatic logic parity8(input logic [7:0] value);
logic p;
integer bit_index;
begin
p = 1'b0;
for (bit_index = 0; bit_index < 8; bit_index = bit_index + 1)
p = p ^ value[bit_index];
parity8 = p;
end
endfunction
assign parity = parity8(data);
assign with_parity = {data[6:0], parity8(data)};
endmodulewith_parity deliberately drops data[7] and appends parity. It is eight bits, not a nine-bit encoding of the complete input. This example makes the interface consequence visible: a helper function can be correct while its caller discards information.
Return width and signedness matter
The return type is one-bit logic. A helper that adds two eight-bit unsigned operands needs a nine-bit result if carry is part of the contract. Document signedness as well as width. Calling a function does not automatically preserve an extra carry or change a caller’s destination width.
Keep named constants and width rules near the interface. Avoid a function that quietly reads a changing module-global variable when the reader expects its explicit argument list to define the calculation. Such hidden dependencies make both reuse and verification harder.
A function call is not shared sequential hardware
Using the same helper twice does not by itself require synthesis to build one time-shared operator. The implementation tool may optimize combinational logic; serial sharing requires an explicit acceptance policy, state and result timing. Do not infer area or throughput merely by counting source-level calls.
Tasks are useful in a testbench for stimulus and checks that wait for events. A function and a timing task have different responsibilities. The testbench lesson uses a task to drive an input at a falling edge and check state after the next rising edge.
Practice · Use a different reference algorithm
Check data=0,1,3,8'h80,8'hFF. Expected parity values are 0,1,0,1,0. For with_parity, data=8'h80 gives 8'h01 because the high input bit is discarded and the parity bit is 1.
Exhaust all 256 input values. The checker should count set bits in an integer and compare the remainder modulo 2, rather than calling parity8 from the DUT. That verifies the function against a different expression of the requirement.
Change the loop bound to 7 and ensure that the single-bit 8'h80 case fails. A test set containing only low-bit values would not expose the missing high bit. Then make with_parity nine bits using {data,parity8(data)} and update its expected-value model. That is an interface change, not just a formatting improvement.