Arithmetic Operators
Section 4.2 was the grammar. This section is the operators that go inside expressions, and it starts with arithmetic, because that is where Module 2’s adders and multipliers come back as single characters. The characters are easy. What matters is knowing what hardware each one produces and, above all, how wide the result is, because a wrong width gives a wrong answer with no error and no warning.
The operators and their hardware
wire [7:0] a = 8'd100, b = 8'd50;
wire [7:0] sum = a + b; // an adder; the carry out of bit 7 is lost
wire [8:0] sum9 = a + b; // the same adder, carry kept
wire [7:0] diff = a - b; // an adder with b inverted and carry-in 1
wire [15:0] prod16 = a * b; // a multiplier; the full product needs 16 bits
wire [7:0] quot = a / b; // a divider: large, slow, avoid
wire [7:0] rem = a % b; // likewise
+ becomes a ripple-carry or lookahead adder depending on the tool and the timing constraint. - is the same adder with the second operand inverted and the carry-in set, exactly as in section 2.2. * becomes a multiplier; on an FPGA the tool puts it in a DSP block if one is free, and an 18-by-18 multiply fits one block on most parts. / and % with a non-constant divisor become a combinational divider, which for 32 bits is thousands of cells and will not meet timing at any useful clock. They are in the language for simulation and for constants.
Width
This is the rule that causes the most bugs. In an assignment, an arithmetic expression is evaluated at the width of its widest operand or of the target, whichever is wider. So
wire [3:0] a = 4'hF; // 15
wire [3:0] b = 4'h1; // 1
wire [3:0] sum_4 = a + b; // computed at 4 bits: 16 does not fit, sum_4 = 4'h0
wire [4:0] sum_5 = a + b; // computed at 5 bits because the target is 5 bits: 5'h10
The second line works because the target widens the computation. That is useful and it is also where people get caught, because the target only helps in a plain assignment. Where an expression sits in a self-determined context, its width comes from its operands alone:
wire [7:0] x, y;
wire ovf = (x + y) > 8'hFF; // x + y is evaluated at 8 bits; it can never exceed 8'hFF
wire [8:0] c = {x + y}; // inside braces the sum is 8 bits; the carry is gone
The comparison is the classic one: the operands of > are sized among themselves, the 1-bit result tells the tool nothing, and the overflow test never fires. The habit that avoids all of this is to extend the operands yourself:
wire [4:0] sum_5b = {1'b0, a} + {1'b0, b}; // 5-bit add, no doubt about it
wire ovf_ok = ({1'b0, x} + {1'b0, y}) > 9'd255;
It costs nothing in hardware, since the adder is the same, and it makes the intent visible to the reader and the tool.
Power
wire [31:0] squared = base ** 2; // fine: a constant exponent unrolls to multiplies
// wire [31:0] p = base ** exp; // not synthesisable with a variable exponent
localparam DEPTH = 2 ** ADDR_BITS; // the usual use
Multiplication
The product of an $N$-bit and an $M$-bit operand needs $N + M$ bits:
wire [7:0] a, b;
wire [15:0] full_product = a * b; // computed at 16 bits, nothing lost
wire [7:0] trunc_product = a * b; // the lower 8 bits only
Up to 8 bits a multiplier is cheap anywhere. Up to 18 bits it is one DSP block on an FPGA. Wider than that it is several blocks chained and worth a look at the timing report, or a pipelined multiplier from the vendor’s library.
Division
Avoid it in synthesisable code. A combinational divider of two $N$-bit numbers is of order $N^2$ gates with a critical path of order $N$. Nearly every real need has a cheaper form:
| Wanted | Write |
|---|---|
| $a / 2^k$ | a >> k |
| $a \bmod 2^k$ | a & ((1 << k) - 1) |
| $a / c$ for a small constant $c$ | multiply by a reciprocal and shift |
| general division | a sequential divider, one bit per cycle |
| sine, log, square root | CORDIC or a table |
wire [7:0] a = 8'd200;
wire [7:0] div4 = a >> 2; // 50; wires only
wire [7:0] mod8 = a & 8'h07; // 0; wires only
wire [7:0] div3 = ({8'd0, a} * 16'd85) >> 8; // 66; 85/256 is 1/3 to within 0.2 %
When a datapath really must divide, the answer is a multi-cycle or pipelined divider written as a state machine, or the vendor’s divider core. Do not write / and hope.
Signed arithmetic
Every signal is unsigned unless declared otherwise. Declaring signed does not change the bits; it changes how they are extended and compared:
wire signed [7:0] a = -8'sd5;
wire signed [7:0] b = 8'sd3;
wire signed [7:0] r = a + b; // -2
wire signed [3:0] narrow = 4'b1010; // -6
wire signed [7:0] wide = narrow; // sign-extended: 8'b1111_1010, still -6
wire [7:0] wide_u = narrow; // zero-extended: 8'b0000_1010, which is 10
$signed(x) treats an unsigned signal as signed for one expression, and $unsigned the reverse. The trap is mixing: if any operand in an expression is unsigned, the whole expression is unsigned and sign extension stops happening. In a signed datapath, declare every port and wire signed rather than sprinkling casts.
An arithmetic unit
`timescale 1ns / 1ps
`default_nettype none
module alu_arith #(
parameter WIDTH = 8
)(
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
input wire [1:0] op,
output wire [WIDTH-1:0] result,
output wire carry_out,
output wire zero
);
wire [WIDTH:0] sum = {1'b0, a} + {1'b0, b}; // one bit wider, carry kept
wire [WIDTH:0] diff = {1'b0, a} - {1'b0, b}; // top bit is the borrow
assign {carry_out, result} =
(op == 2'b00) ? sum : // add
(op == 2'b01) ? diff : // subtract
(op == 2'b10) ? {1'b0, a + 1'b1} : // increment
{1'b0, a}; // pass
assign zero = ~|result;
endmodule
In simulation
An x or z on any input bit makes the whole arithmetic result x, which is how an unreset register shows up. The simulator will not tell you about overflow; you have to check. And $display("%d", v) prints unsigned unless you write $display("%d", $signed(v)).
initial begin
a = 8'hFF; b = 8'h01;
#10;
if ((a + b) > 8'hFF) $display("overflow"); // never prints: 8-bit compare
if (({1'b0, a} + {1'b0, b}) > 9'd255) $display("overflow"); // prints
$display("8-bit sum = %0d", a + b); // 0
$display("9-bit sum = %0d", {1'b0, a} + {1'b0, b}); // 256
end
Practice Exercise
Write a saturating 8-bit adder: the result is 8'hFF whenever the true sum would exceed 255.
Solution
`timescale 1ns / 1ps
`default_nettype none
module sat_adder (
input wire [7:0] a,
input wire [7:0] b,
output wire [7:0] result
);
wire [8:0] full_sum = {1'b0, a} + {1'b0, b};
assign result = full_sum[8] ? 8'hFF : full_sum[7:0];
endmodule
Compute the sum one bit wider. Bit 8 is the carry out of the 8-bit add; if it is set the true sum was over 255 and the output saturates.
Next, the operators that produce a single bit: comparisons and logical conditions, and the difference between & and && that catches every programmer once.