Introduction

In my essay The Case for a Better HDL I called the analogies between HDL design and software development "obvious and beautiful". From this observation, I suggested that a "software perspective" would be very useful for certain HDL design tasks. My goal with this essay is to put that idea into practice at the RTL level.

As an example, I chose a Gray counter that uses the Gray code word itself as the state. This example builds further on the reference model for Gray codes that I developed in my essay Why HDL designers should learn Python. That reference model can be used to verify the RTL design.

You can find prior art for this design on the web. In particular, Altera has published a Verilog and VHDL solution. However, I think we can do much better by using a "software perspective".

The algorithm

The first task is to develop an algorithm. Recall that successive words in a Gray count sequence differ in a single bit. The purpose of the algorithm is thus to find the bit to toggle for any given Gray code word. Let's consider the first steps in a 4-bit Gray count sequence:

step count
  0   0000
  1   0001
  2   0011
  3   0010
  4   0110
  5   0111
  6   0101
  7   0100
  8   1100

For even steps, the bit to toggle is always the lsb at position 0. As a single bit changes at each step, the corresponding words share a common property: even parity. This part of the algorithm is easy enough: for even parity words, toggle the lsb.

For odd steps, the solution is a little more complex. It turns out that you have to find the first 1 in the word, starting from the lsb. The bit to toggle is located at the next higher position.

The remaining problem is to combine even and odd steps in one algorithm. The idea I had is to construct a new word by shifting the Gray code word one bit to the left. For odd steps, the first 1 in that word gives you the exact position to toggle. Moreover, the lsb position is now vacant. If you put the even parity bit in there, the first 1 in the word should give you the position to toggle for all cases.

This works fine, except for Gray code word "1000". If you shift this word to the left, all 1's are gone, so we don't know what position to toggle. Upon inspection, this word should be followed by "0000". Therefore, the solution is to set the msb of the shifted word unconditionally to 1, as a fallback solution. The final algorithm is as follows:

Given a Gray code word G = { g[n-1] g[n-2] g[n-3] ... g[0] }, with g[k] the individual bits of the word:

  1. construct a new word W = { 1 g[n-3] ... g[0] p }, with p being the even parity bit of G
  2. starting from the lsb at position 0, look for the first 1 in W
  3. toggle the bit in G at the corresponding position

RTL coding

Coding at the RTL level means describing the hardware behavior in a single clock cycle. Therefore, if an algorithm requires a number of steps, you have to code it as an FSM so that the behavior depends on the state you are in. The resulting code is fairly low level. For example, a loop is emulated by explicit transitions between states.

However, there is an interesting special case. When the algorithm is simple enough to complete within a single clock cycle, you can use higher level features such as a for loop to describe the behavior in a straightforward way. Clearly, that is the case for the Gray counter example.

Here is the resulting MyHDL code:

def gray_counter(gray_count, enable, clock, reset):

    n = len(gray_count)
    gray = Signal(intbv(0)[n:])
    even = Signal(bool(1))

    @always_seq(clock.posedge, reset=reset)
    def seq():
        word = concat('1', gray[n-2:], even)
        if enable:
            found = False
            for i in range(n):
                if word[i] == 1 and not found:
                    gray.next[i] = not gray[i]
                    found = True
            even.next = not even

    @always_comb
    def alias():
        gray_count.next = gray

    return seq, alias

As you may not be familiar with MyHDL, let me first show the equivalent Verilog and VHDL. Synthesizable MyHDL can be converted to Verilog and VHDL automatically. The following is the relevant part of the output Verilog code, for a bit width of 8:

reg even;
reg [7:0] gray;

always @(posedge clock, posedge reset) begin: GRAY_COUNTER_8_SEQ
    integer i;
    reg [8-1:0] word;
    reg found;
    if (reset == 1) begin
        even <= 1;
        gray <= 0;
    end
    else begin
        word = {1'b1, gray[(8 - 2)-1:0], even};
        if (enable) begin
            found = 1'b0;
            for (i=0; i<8; i=i+1) begin
                if (((word[i] == 1) && (!found))) begin
                    gray[i] <= (!gray[i]);
                    found = 1'b1;
                end
            end
            even <= (!even);
        end
    end
end

And this is the equivalent VHDL code:

signal even: std_logic;
signal gray: unsigned(7 downto 0);
...
GRAY_COUNTER_8_SEQ: process (clock, reset) is
    variable word: unsigned(7 downto 0);
    variable found: std_logic;
begin
    if (reset = '1') then
        even <= '1';
        gray <= (others => '0');
    elsif rising_edge(clock) then
        word := unsigned'("1" & gray((8 - 2)-1 downto 0) & even);
        if bool(enable) then
            found := '0';
            for i in 0 to 8-1 loop
                if ((word(i) = '1') and (not bool(found))) then
                    gray(i) <= stdl((not bool(gray(i))));
                    found := '1';
                end if;
            end loop;
            even <= stdl((not bool(even)));
         end if;
    end if;
end process GRAY_COUNTER_8_SEQ;

Analysis of the RTL code

You can now go over the code in your favorite HDL. As this is a hardware description, I have added a clock, reset, and enable signal. Also, I made even part of the state by toggling it at each step.

Apart from this, the code is a straightforward implementation of the algorithm. As expected from a Gray counter, it is clear that a single bit is toggling. Moreover, anyone with some programming experience can easily reconstruct the algorithm from the code. The clarity of the code is a great advantage, because code is read much more often than it is written.

Variables word and found play a key role. word combines information like suggested in the algorithm, so it can be used in a for loop later on. found keeps track whether the bit to toggle has been found already. The power of variables is that they provide this type of "combinatorial" and "dynamic" semantics within a clocked process.

Those unfamiliar with this coding style may worry about synthesizability. However, synthesis tools have no problem with this kind of description. They will unroll the for loop and map the decision tree to a multi-level logic network.

The message

At this point, I would like to elaborate on what I mean with "thinking software at the RTL level." I have solved a hardware design problem by using a "software perspective". First, I developed an algorithm. Then I have used HDL language features to write code that resembles it as closely as possible. I haven't talked about hardware concerns much, because I know that synthesis tools create efficient implementations from this kind of description.

The alternative, much advocated strategy is "thinking hardware". Initially, this may sound like obviously good advice. However, upon reflection, "thinking hardware" is meaningless without a specification of the kind of abstractions one should use.

In practice, RTL hardware thinkers have their abstractions wrong: they try to think like a synthesis tool themselves, instead of like synthesis users. They typically advocate to "visualize" the design schematic first and then code the HDL accordingly. Also, they want to understand the direct hardware implications of every single line of HDL code, which is often a futile exercise. The problem with this approach is clear: one will never come up with a clear, algorithm-driven solution like the one I have presented for the Gray counter.

Let me be very clear though. The message of this essay is not that hardware concerns don't matter. On the contrary, the message is that one must understand synthesis capabilities in order to have more time for concerns that actually do matter. RTL synthesis tools will not help with tasks like architectural choices, design partitioning or power management. That is where valuable engineering time should go to, not to tasks that synthesis tools can do much better. And to complete the analogy: in concept this is exactly how good software engineers relate to their compilers.

If you would like to check the design yourself, you can find the code here. I have used the MyHDL converter to generate Gray counters in Verilog and VHDL for a number of bit widths between 4 and 32.

A warning to conclude

As a final point, I have to warn you that the conventional wisdom in HDL design is a major obstacle to put the presented ideas into practice. In particular, many guidelines and experts strongly discourage the use of variables in synthesizable code, and consequently the coding paradigm that I have used here. This matters, because the choice of a coding paradigm is not neutral. A solution that is straightforward with one paradigm may be quite cumbersome with another. The design problem in this essay is a good example.

The conventional wisdom has it all wrong. There is ample evidence that local variables in clocked processes are very useful, safe and fully supported. May this essay be an invitation for HDL designers to form their own opinions based on real evidence and experiments.