Failed Abstractions - the Verilog always block

Hardware Modeling

Verilog

Verilog is a hardware description language (HDL) widely used in both academia and industry. Its purpose is to provide an abstraction over using the particular logic gates of any one library by offering description capabilities on the register-transfer-level (RTL). This post discusses two language features that failed to abstract in a meaningful way and instead intermingle distinct concepts: The always block which can infer combinational logic, latches or registers, and the reg keyword which can be used to either declare register elements or transfer elements depending on the usage of the element within an always-block.

Register-Transfer-Level

The thought process on the RTL level is to separate between state that should be stored in registers and how this state is processed/transmuted/transfered by purely combinational logic, e.g. a multi-gate register-free circuit. Hardware concepts like drive strength and fan-out are completely abstracted away. It is interesting to observe that RTL is quite an imbalanced abstraction:

R: The register part is very close to the gate level. Commonly, some D-flip-flops with optional enable port and async/sync set/reset, are available in any gate library. A register in RTL can be a simple composition of these elements to form parallel-in-parallel-out-registers, shiftregisters or serial-parallel registers. I would describe this as raising the abstraction by one layer: It’s handy, but removing the abstraction would only slightly annoy me and does not change my thought process.

T: The transfer part can be described as a 1-cycle version of high-level synthesis. Writing down an expression that should be evaluated combinatorial/in one cycle, the synthesizer magically spits out a circuit that is always correct and can be heuristically tuned to be either small or flat or some combination thereof. Getting rid of this abstraction would completely change my thinking. I would have to analyse the concrete gate library very thoroughly and somehow come up with my own way to map my intention into concrete gates. The design time would increase by some factor. The focus would no longer lie on architecting the hardware, but on manual gate-mapping.

Due to these very different natures, I would strongly prefer a strict R vs. T separation in an HDL.

The Verilog always block

Registers

To infer a register the following Verilog construct must be used:

always @ (posedge clk) begin [..] end

More concretely a simple synchronous reset register with enable can be written as:

always @ (posedge clk)
begin 
    if (rst == 1) begin
        Q <= 0;
    end
    else begin
        if (en == 1) begin
            Q <= D;
        end
    end
end

where Q and D can have an arbitrary, but equal width. An asynchronous reset is achievable with only a small modification in the reactivity list

always @ (posedge clk, posedge rst)

The signal Q needs to be declared of type reg, which ostensibly makes sense as it is a register.

Combinational Logic

Unfortunately, always and reg are also used to infer combinational logic. The following code

always @ (*)
begin
    a <= b & c ^ d;
end

will simply synthesize a circit computing a without any registers. Nevertheless, a needs to be declared as reg, because all elements standing on the left hand side in an always-block are required to have this type.

Latches

In Verilog, latches are commonly infered by mistake. When combinational logic is intended, but the else-branch of an if-statement or the exhaustive enumeration of all cases of a case-statement is missing, Verilog will automatically infer a latch to keep the old state in the case of no matching branch.

always @ (*)
begin
    if (a == 1) begin
        b <= c;
    end
end

Why is Verilog like that?

In 1983 C was the cool kid on the block and Verilog wanted to belong. But it tried too hard to fit in by imitation. Instead Verilog should have realized that what made C cool was nailing the abstraction of machine code on single core processors at the time. Verilog could have done the same for hardware: Reflection about meaningful abstractions, type safety and useful standard library components in the hardware domain. It didn’t. But all is not lost.

SystemVerilog: A new hope

SystemVerilog does many things right. Here I will only focus on the evolution of the always block.

always_comb, always_ff, always_latch

Three new always blocks are available in SystemVerilog, they provide basic R vs. T separation.

always_ff makes the intention of a block clear and warns the designer if something else is infered accidently.

Additionally, SystemVerilog introduces the new type logic which basically gives up on typing and unifies wire and reg into one type.

Conclusion

Verilog can be better thought of as an event-driven hardware simulation language with additional hardware description capabilities. It is relatively intuitive to reason about how the contents of always blocks map to an event-driven simulation. However, describing hardware with Verilog can be learned, but remains unintuitive and error-prone.

My Quick Fix

Enough with the trash talk, let’s fix it. I believe that less is more. I find a reduction of the Verilog language to a subset to be more useful than an expansion of the language contructs. Meaningful restrictions can help to design good systems because they provide better shepherding. I also work with tools that only support Verilog and not SystemVerilog. (Somehow legacy and state-of-the art can be the same thing in the hardware world.)

My suggestion to reduce errors and increase developer productivity when working with Verilog on the RTL level is to enforce a clear R vs. T separation with minimal effort and with 100% compatibility to classical Verilog.

  1. Create an R library: One-time creation and many-time usage of a minimal library of stateful elements as standard components. This increases the available library elements and reduces the need for complexity in the language subset.

  2. Use Verilog to describe T components with always blocks. With this setup the synthesis report can be parsed to ensure that only modules belonging to the R-library lead to the creation of registers and latches, while all other modules remain combinatorial. Thereby, always effectively becomes always_comb outside of the whitelisted library of stateful components.

  3. Additonally, an FSM module should be part of your library. I will discuss its properties in the next post.