L17: Verilog Arithmetic
Let’s practice Boolean algebra!
Objective
Practice usage of theorems 5-17 of Boolean algebra.
Additional Materials & Formats
Check Box for the most up-to-date versions of this lecture’s materials.
Before the Lecture
Required Textbook Reading:
- 3.5 (Design of Arithmetic Circuits Using CAD Tools)
Optional Supplemental Instruction:
Design of Arithmetic Circuits Using Verilog
In the previous section, we saw that an $n$-bit adder can be built by connecting together $n$ full-adders in a ripple-carry chain. Verilog gives us a clean way to describe that same structure: first write a module for the basic building block, then build a higher-level module by instantiating that block repeatedly.
This is an example of hierarchical design. The idea is simple:
- Design the smallest useful circuit first, such as a full-adder.
- Put that circuit into its own Verilog module.
- Use several copies of that module inside a larger module.
For a full-adder, the inputs are $C_{in}$, $x$, and $y$, and the outputs are $s$ and $C_{out}$. Verilog can describe this circuit in more than one way. One option is to use gate-level primitives, where the logic is written directly with AND, OR, and XOR gates. Another option is to use continuous assignments, where the Boolean equations are written as expressions.
module fulladd (Cin, x, y, s, Cout);
input Cin, x, y;
output s, Cout;
xor (s, x, y, Cin);
and (z1, x, y);
and (z2, x, Cin);
and (z3, y, Cin);
or (Cout, z1, z2, z3);
endmodulemodule fulladd (Cin, x, y, s, Cout);
input Cin, x, y;
output s, Cout;
xor (s, x, y, Cin);
and (z1, x, y),
(z2, x, Cin),
(z3, y, Cin);
or (Cout, z1, z2, z3);
endmodulemodule fulladd (Cin, x, y, s, Cout);
input Cin, x, y;
output s, Cout;
assign s = x ^ y ^ Cin;
assign Cout = (x & y) | (x & Cin) | (y & Cin);
endmodulemodule fulladd (Cin, x, y, s, Cout);
input Cin, x, y;
output s, Cout;
assign s = x ^ y ^ Cin,
Cout = (x & y) | (x & Cin) | (y & Cin);
endmoduleAll styles describe the same hardware. The choice is mostly a matter of readability and design preference. Gate-level code looks close to a schematic, while assignment-based code looks closer to the Boolean equations for the circuit.
Once the full-adder module is available, we can build a 4-bit ripple-carry adder by instantiating that module four times. Each instance represents one stage of the adder. The least-significant stage takes the external carry-in, and each stage passes its carry-out to the next stage’s carry-in. This is why the design is called a ripple-carry adder: the carry “ripples” from one bit position to the next.
module adder4 (carryin, x3, x2, x1, x0, y3, y2, y1, y0, s3, s2, s1, s0, carryout);
input carryin, x3, x2, x1, x0, y3, y2, y1, y0;
output s3, s2, s1, s0, carryout;
fulladd stage0 (carryin, x0, y0, s0, c1);
fulladd stage1 (c1, x1, y1, s1, c2);
fulladd stage2 (c2, x2, y2, s2, c3);
fulladd stage3 (c3, x3, y3, s3, carryout);
endmodule
module fulladd (Cin, x, y, s, Cout);
input Cin, x, y;
output s, Cout;
assign s = x ^ y ^ Cin;
assign Cout = (x & y) | (x & Cin) | (y & Cin);
endmoduleThe port list in each instantiation must match the order of the full-adder module’s ports. In this case, the signals are connected in the order $C_{in}$, $x$, $y$, $s$, $C_{out}$. The instance names, such as stage0 and stage3, must be unique so that each copy of the module can be identified separately.
This modular approach has two major advantages. First, it makes the design easier to understand because the circuit is broken into smaller parts. Second, it makes the code reusable: the same full-adder module can be used in many different adder designs, not just the 4-bit version.
When the Verilog code is synthesized, the resulting circuit has the same structure as the hand-drawn ripple-carry adder. In other words, hierarchical Verilog lets us describe the hardware at a higher level without losing the underlying circuit structure.
Using Vectored Signals
A cleaner approach is to use vectors, which are multibit signals in Verilog. Vectors let us group related wires together and refer to them by bit position.
Declaring a Vector
input [3:0] X;This declaration makes X a four-bit vector.
X[3]is the most-significant bit (MSB)X[0]is the least-significant bit (LSB)X[2:1]refers to the two middle bitsXrefers to the full vector
Four-Bit Adder with Vectors
Using vectors, the ripple-carry adder can be written more compactly:
module adder4 (carryin, X, Y, S, carryout);
input carryin;
input [3:0] X, Y;
output [3:0] S;
output carryout;
wire [3:1] C;
fulladd stage0 (carryin, X[0], Y[0], S[0], C[1]);
fulladd stage1 (C[1], X[1], Y[1], S[1], C[2]);
fulladd stage2 (C[2], X[2], Y[2], S[2], C[3]);
fulladd stage3 (C[3], X[3], Y[3], S[3], carryout);
endmoduleCarry Connections
The internal carry signals are stored in the vector C[3:1].
carryinfeeds the first full-adder stageC[1]connects stage 0 to stage 1C[2]connects stage 1 to stage 2C[3]connects stage 2 to stage 3carryoutis the final carry from stage 3
Bit Ordering
The bit range is written with the MSB first and the LSB last, as in X[3:0].
X[3]is the MSBX[0]is the LSB- Reverse ordering is also allowed, such as
Z[0:3]
When vectors represent numbers, MSB and LSB describe significance. When vectors represent general signals, the indices simply identify bits.
Using a Generic Specification
Why a generic specification is useful
- A four-bit ripple-carry adder is fixed in size.
- A scalable design can be reused for 4-bit, 32-bit, or n-bit adders.
- Verilog parameters make the module width adjustable.
Parameterized vectors
-
An n-bit vector can be written as
X[n−1:0]. -
Example: if
parameter n = 4;, thenXhas range[3:0]. -
The ripple-carry adder can be described by:
s_k = x_k ⊕ y_k ⊕ c_kc_{k+1} = x_k y_k + x_k c_k + y_k c_k -
These equations can replace repeated full-adder instantiations.
Generic n-bit adder
X,Y, andSare n-bit vectors.Cis an(n+1)-bit carry vector.C[0]is the carry into the LSB.C[n]is the carry out of the MSB.- So
C[0] = carryinandcarryout = C[n].
Verilog for loops in always blocks
- The
forstatement is procedural and belongs inside analwaysblock. - Signals assigned inside
alwaysmust be declaredreg. - In Figure 3.24,
S,C, andcarryoutareregsignals. - The sensitivity list is
X,Y, andcarryin.
Loop behavior
- The loop variable
kruns from0ton−1. - Each iteration computes one adder stage.
- Verilog does not use
++or--; usek = k + 1ork = k - 1. kis an integer control variable, not a circuit wire.- For
k = 2, the loop expands into the corresponding explicit assignments for bit 2.
Example module outline
module addern (carryin, X, Y, S, carryout);
parameter n = 32;
input carryin;
input [n-1:0] X, Y;
output reg [n-1:0] S;
output reg carryout;
reg [n:0] C;
integer k;
always @(X, Y, carryin)
begin
C[0] = carryin;
for (k = 0; k < n; k = k + 1)
begin
S[k] = X[k] ^ Y[k] ^ C[k];
C[k+1] = (X[k] & Y[k]) | (X[k] & C[k]) | (Y[k] & C[k]);
end
carryout = C[n];
end
endmodule- With
n = 32, this implements a 32-bit adder.
Using the Generate Capability
Main idea
- The
generateconstruct can instantiate repeated hardware. - It is useful when the design contains many identical subcircuits.
- A
forloop insidegeneratecan create n copies offulladd.
genvar
- The loop index in a
generateblock must be declaredgenvar. genvaris used only in generate blocks.- It is similar to
integer, but it only takes positive values.
Generated instances
- Each generated instance gets a unique name.
- Example names include
addbit[0].stage,addbit[1].stage, …,addbit[n−1].stage. - The generated circuit matches the behavior of the procedural version.
Example generate-based adder
module addern (carryin, X, Y, S, carryout);
parameter n = 32;
input carryin;
input [n-1:0] X, Y;
output [n-1:0] S;
output carryout;
wire [n:0] C;
genvar i;
assign C[0] = carryin;
assign carryout = C[n];
generate
for (i = 0; i <= n-1; i = i+1)
begin : addbit
fulladd stage (C[i], X[i], Y[i], S[i], C[i+1]);
end
endgenerate
endmodule
module fulladd (Cin, x, y, s, Cout);
input Cin, x, y;
output s, Cout;
assign s = x ^ y ^ Cin;
assign Cout = (x & y) | (x & Cin) | (y & Cin);
endmoduleNets and Variables in Verilog
Modeling in Verilog
- A logic circuit can be described by interconnected logic elements.
- It can also be described with procedural statements.
- Connections between logic elements are called nets.
- Signals produced by procedural statements are called variables.
Nets
- A net is a node in a circuit.
- The main synthesis net type is
wire. - A wire connects one logic element output to another element input.
- A wire may be scalar or vector.
- In Figure 3.22,
c3,c2, andc1are scalar nets. - In Figure 3.23, the carries are collected into a vector
C. - Nets are assumed by default, but vectors must be declared when needed.
Variables
- Variables describe behavior in procedural code.
- A variable keeps its value until it is reassigned.
- Verilog variable types include
regandinteger. - Signals assigned in procedural statements must be declared as variables.
- In Figure 3.24,
carryout,S, andCareregvariables. - The loop variable
kis aninteger. - These variables usually do not map directly to physical circuit wires.
Arithmetic assignment statements
Verilog supports arithmetic assignment on vectors (e.g., S = X + Y;) to represent combinational arithmetic hardware such as n-bit adders. Vectors participating in an arithmetic expression are automatically zero-extended on the left if the result requires more bits.
Example: n-bit adder (behavioral)
input [n-1:0] X, Y;output [n-1:0] S;- the statement
S = X + Y;implements an n-bit adder producing S.
Carry-out and overflow:
- For unsigned operands, carry-out from the MSB indicates overflow of n bits.
- For signed operands, arithmetic overflow must be generated (see Section 3.3.5).
Behavioral Verilog example (simple adder):
module addern (carryin, X, Y, S);
parameter n = 32;
input carryin;
input [n-1:0] X, Y;
output reg [n-1:0] S;
always @(X, Y, carryin)
S = X + Y + carryin;
endmoduleAdding carry-out and overflow signals:
- Carry-out from MSB (bit n-1) can be computed as:
carryout = X[n-1] & Y[n-1] | X[n-1] & S[n-1] | Y[n-1] & S[n-1]; - Overflow (for signed numbers) can be derived from MSB bits; one common form is
overflow = (X[n-1] & Y[n-1] & ~S[n-1]) | (~X[n-1] & ~Y[n-1] & S[n-1]);
Behavioral Verilog with carry-out and overflow (illustrative):
module addern (carryin, X, Y, S, carryout, overflow);
parameter n = 32;
input carryin;
input [n-1:0] X, Y;
output reg [n-1:0] S;
output reg carryout, overflow;
always @(X, Y, carryin) begin
S = X + Y + carryin;
carryout = (X[n-1] & Y[n-1]) | (X[n-1] & S[n-1]) | (Y[n-1] & S[n-1]);
overflow = (X[n-1] & Y[n-1] & ~S[n-1]) | (~X[n-1] & ~Y[n-1] & S[n-1]);
end
endmoduleAlternative: use an (n+1)-bit intermediate to capture carry-out directly. Create reg [n:0] Sum and compute Sum = X + Y + carryin; then S = Sum[n-1:0]; carryout = Sum[n];
Concatenation shortcut:
- You can avoid an intermediate vector by concatenating carryout to S on the left side of the assignment:
{carryout, S} = X + Y + carryin;
Full-adder (behavioral) example:
module fulladd (Cin, x, y, s, Cout);
input Cin, x, y;
output reg s, Cout;
always @(x, y, Cin)
{Cout, s} = x + y + Cin;
endmoduleNotes:
- These behavioral forms describe arithmetic intent; synthesis tools infer the appropriate adder implementation for the target technology.