Altifigence Academy

21 / 37 · Concept

function automatic and reusable expressions

Write functions that consume no simulation time and check return width, signedness and reference-model independence.

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.

SystemVerilog
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)};
endmodule

with_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.

Your choice applies to this browser. Change it any time using the footer.