The short answer: beyond adders, the key arithmetic circuits are comparators (is A greater than, equal to or less than B?), multipliers (from simple array multipliers to Booth-encoded and Wallace-tree designs), and the ALU, which combines arithmetic and logic operations behind an opcode and produces status flags. In RTL you usually write the behaviour (a * b, a < b) and let synthesis build the circuit, but you need to know the structures to reason about area and timing.
This post combines my older posts on comparators, multipliers and the ALU, since they are really one topic: the arithmetic heart of a processor.
- The building blocks
- Comparators
- Multipliers
- The ALU
- Area and timing trade-offs
Table of Contents
Part 1: The building blocks

Part 2: Comparators
An equality comparator XNORs each pair of bits and ANDs the results. A magnitude comparator also decides greater or less, starting from the most significant bit. In practice A < B is often computed as a subtraction and a look at the borrow (or the sign for signed numbers), which is how most ALUs do it.
Part 3: Multipliers
- Array multiplier: generate every partial product (A AND each bit of B), then add the rows. Easy to understand, but slow for wide operands.
- Booth encoding: recodes the multiplier so that about half as many partial products are needed, and handles signed (two’s complement) numbers naturally. Radix-4 Booth is common.
- Wallace and Dadda trees: add the partial products using a tree of carry-save adders (compressors), followed by one fast final adder. Much faster than adding row by row.
- Sequential multipliers: one add-and-shift per cycle. Very small but takes N cycles.
Part 4: The ALU
An ALU takes two operands and an operation code, and produces a result plus flags. A small ALU in Verilog:
module alu #(parameter N = 32) (
input wire [N-1:0] a, b,
input wire [2:0] op,
output reg [N-1:0] y,
output wire zero
);
always @(*) begin
case (op)
3'd0: y = a + b;
3'd1: y = a - b;
3'd2: y = a & b;
3'd3: y = a | b;
3'd4: y = a ^ b;
3'd5: y = {{(N-1){1'b0}}, ($signed(a) < $signed(b))}; // set-less-than
3'd6: y = a << b[4:0];
default: y = {N{1'b0}};
endcase
end
assign zero = (y == {N{1'b0}});
endmodule
Real processor ALUs add carry, overflow and negative flags, and more shift and compare operations. You can build a complete RISC-V ALU step by step in the CPU course linked below.
Part 5: Area and timing trade-offs
- Multipliers are large: a 32×32 multiplier can be bigger than the rest of a small ALU combined.
- Wide comparators and adders are often on the critical path; pipelining a multiplier is common.
- Sharing one adder for add, subtract and compare saves area.
Practise this on VLSI Forge
I built VLSI Forge so you can write RTL in your browser, run it on a real simulator and check every signal in the waveform. Free, nothing to install.
FAQ
What is Booth’s algorithm used for?
To reduce the number of partial products in a multiplier and to handle signed numbers efficiently.
What flags does an ALU produce?
Typically zero, carry, overflow and negative, which the processor uses for branches and comparisons.
How does an ALU compare signed numbers?
By subtracting and looking at the sign and overflow flags, or with a dedicated signed comparison as in the set-less-than above.
Should I write multipliers by hand in RTL?
Usually not. Write a * b and let synthesis or a library IP choose; only hand-design when you need a special structure.
