Saturday, March 5, 2011

Does that Register?

I'll start this blog by attempting to explain what was, for me, and I believe many of my peers, one of the single greatest sources of confusion when "programming" in Verilog: the beloved keyword reg.

Basically what this amounted to was us compiling our code, getting the message "Non reg/integer/real/time/realtime cannot be used on the left hand side of this assignment" and then having the following discussion:

Lab partner A: "Huh... try throwing reg in front of it."
Lab partner B: "But it's not a register."
A: "Whatever, just try it."
B: "Okay."
A: "Oh, hey, it works. See?"
B: "Uh... what?"

So: What does reg mean? Does it mean register? Does it mean stateful variable? (Verilog has variables?) Can it be combinational? Or is it only for sequential logic? Can I use blocking assignments on regs? So many questions!

While doing research for this post (cause heck if I actually know what the deuce reg means), I came across the following documents:


I've downloaded this helpful little reminder sheet and I suggest you do, too. Print copies and give it to all your non-blocking friends!


It's over 9000 pages long (where 9000 == 828) but it's written in simple English and actually makes a lot of things very clear. It was my primary resource while writing this blog post.

Basically, here's the deal. Verilog has two basic data types: nets and variables. regs are one kind of variable, but first I'm going to introduce nets and then variables. If you think you have a good grasp on nets, you can go ahead and skip down to variables.

Nets

Nets are data types that "represent physical connections between structural entities, such as gates."[1] With the exception of trireg, they do not store a value. Ignore trireg for now, it's used to model wires that have capacitance. I've never used one in all my time at CMU.

Here is a list of net types[2]:
  • wire
  • wand
  • wor
  • tri
  • triand
  • trior
  • tri0
  • tri1
  • trireg
  • supply0
  • supply1
You're probably quite familiar with wire. It's used to connect inputs and outputs of modules. They have to be driven by something to have a value of 0 or 1. If nothing drives them, they hold the value "z" (which, in Verilog, means high impedance, i.e., disconnected). If multiple things drive them and they conflict (or either is x), then they have value x. [3]

As an aside, you might be wondering why these things are called "nets". I don't really know myself. I think "net" comes from "network" where each wire can form a network since it can split into many parts which go to different gates or modules or whatever. If anyone knows for sure, please let me know cause I'm curious.

Anyway, that's wire. Also, wire and tri are "identical in their syntax and functions"[4]. Same goes for wand and triand, wor and trior, which I haven't explained. Basically they behave differently when two things are trying to drive the same net at the same time. They're for another time, though.

Why two different names for things which are identical in syntax and function? Because labeling something as "tri" helps indicate that you intend for it to be driven by different things are different times. That is, you want for it to have tri-state logic (1, 0, and z). This happens when, say, there is a bus between two modules and sometimes module A is driving the bus and sometimes module B is driving the bus. When neither of them are driving it, the bus has a value of z on it. Use "wire" when you don't anticipate this usage (i.e., you expect exactly one thing to drive the net and to do so at all times e.g., a wire which directly connects two modules).

So, those are nets. Onto variables!

Variables

"A variable is an abstraction of a data storage element."[5] That is, they store a value from one assignment to the next.

Here's a list of variable types:
  • integer
  • real
  • realtime
  • time
and last but not least:
  • reg
Unlike nets, which initialize to z (with the exception of trireg), variables initialize to x (that is, "unknown" or "undefined") or 0.0 in the case of real and realtime.

I use integer pretty often in testbenches, but I don't really use others. reg is by far the most commonly used variable type.

Source of Confusion

Here comes what I believe to be the source of alll the confusion caused by the reg keyword: "Since the reg holds a value between assignments, it can be used to model hardware registers. [...] A reg needs not represent a hardware storage element since it can also be used to represent combinatorial logic." [6]

If that doesn't confuse you, it should. Combinational (combinatorial?) logic is logic that is stateless or memoryless. Its outputs are purely a function of the present inputs; they do not depend on prior inputs or events. For example, if I say "what is 5+7?" the answer doesn't change if I just asked you to calculate 2+2. The answer is 12 and you can determine this without knowing any thing about the past inputs I've provided you with.

Sequential logic, on the other hand, can potentially depend on all prior inputs. Whenever there is memory in a system, if it is being used, it is sequential, not combinational. For example, if I am inputting a series of numbers between clock cycles and the output is the sum of all the inputs up to the present, then that uses sequential logic. Computers would be pretty boring (and useless) without this.

Now, getting back to the excerpt above: regs hold values between assignments (i.e., they have memory) and yet they can represent combinatorial logic (which is memoryless).

How can this be?

Well, technically combinational logic can be thought of as a subset of sequential logic. To make this clear, think about the constant function f(x) = c. Is this a constant function or is it a linear function? Well, it's both. It really comes down to what we mean by constant vs. linear. Does linear exclude constant? Does the linear term need to be non-zero for the function to be linear? Are squares rectangles? You get the idea.

Combinational logic is sequential logic where the past state doesn't show up in the function for determining the outputs. For example "A or B" is combinational logic, but in a way it is sequential logic that happens to not depend on any prior inputs. (Now, usually the word "sequential logic" means that it does depend on prior state just as rectangle usually means excluding squares. So, don't go using the word this way, you'll confuse the hell out of everyone and make yourself look dumb.)

So, when we want to model combinational logic, we can use regs; we just need to make sure their contents do not depend on prior state. If they do, we no longer have combinational logic, we have sequential logic. Obviously reg isn't confusing when we actually want a register, it's only confusing when we use it in a context where we want combinational logic, so let's look at that.

Some Sample Code

Okay, so where's an example of having to use reg when we want combinational logic? Let's start with something simple: a multiplexer.
/* WRONG multiplexer */
module mux(y,sel,a,b);
output y;
input sel,a,b;

always @(*) begin
if(sel)
y = b;
else
y = a;
end

endmodule
Recall that a multiplexer simply selects one of 2^n inputs to drive a single output using n select-bits. This is a purely combinational function as the input selected is determined entirely by the present input "sel".

So, what's wrong with the code above? Answer: y is not declared as a reg. Keep in mind that y does not represent a register. When this code is synthesized to a device, there won't be any register. So why do we need to declare it as a reg? Well, it's because y, while not modeling a register, does have state--in the simulator. This is because y will continue to have whatever value it presently holds until something in the sensitivity list of the always block changes its value, thus triggering the execution of the next blocking assignment for y. In this case, when "sel" changes, y will change in a way entirely determined by the value of sel.

What is important to keep in mind here, and what gives rise to this whole issue, is that there is a difference between simulation and synthesis. From the perspective of the simulator, y has state since it continues to store its value until it must be recalculated when sel changes. From the perspective of synthesis, however, no actual register is necessary to implement this function since we have a definition for what y should be in all input cases (sel being 0 or 1) and it is the same every time i.e., it is combinational.

What would happen if we were to take out the "else" clause in the if-else block above? Well, then the simulator would not change y when sel becomes 0. From the simulator's perspective, this isn't really different in any fundamental way from the code before; y is still stateful.

The function, however, is no longer combinational because now our value for y depends on whether or not sel has been set to 1 yet. If it hasn't, y is x (remember that the default value for variables is x). Consequently, the synthesizer must implement y as an actual register now. Your simulator will warn you that a latch (a flip-flop or one-bit register) has been inferred. Hence the fine name of this blog!

You should now understand why reg is needed even when dealing with combinational logic in procedural blocks.

In case you're curious, how is another way that we could have written the above code to avoid using the keyword reg:
/* RIGHT multiplexer */
module mux(y,sel,a,b);
output y;
input sel,a,b;

assign y = (sel) ? b : a;

endmodule
This takes advantage of continuous assign. Since this is not inside of a procedural block (unlike the always block in the prior code), it is not something that is getting executed upon certain events. y has been assigned to the logical function of being b when sel is 1 and a when sel is 0. It's function is set in stone and its not as though y contains a state or value. It is simply a wire, now, which propagates the function of a multiplexer. This is why you cannot assign something more than once: it's not something that gets executed as the simulation runs through time. It's not within a procedural block so when would it even get executed?

Before ending, let's look at yet one more way to specify a multiplexer:
module mux(y,sel,a,b);
output y;
input sel,a,b;

and(y0,~sel,a);
and(y1,sel,b);
or(y,y0,y1);

endmodule
Here we've specified the mux entirely with structure of primitive logic gates. Thus we only specifying the connections between input and output ports. That means we don't need reg since wires are what specify port connections. They are driven by whatever the outputs and inputs of the various modules are.

Hopefully this makes clears up the whole issue. If any of this is confusing or flawed, please let me know! Happy coding non-blockers!

[2] Ibid., p. 25
[3] Ibid.
[4] Ibid.
[5] Ibid., p. 22
[6] Ibid., p. 31

No comments:

Post a Comment