An arithmetic logic unit is where the earlier lessons snap together. It
is, structurally, nothing new: a subtract-capable adder (lesson 6),
the gates (lesson 1) computing AND/OR/XOR in parallel, and a big
mux (lesson 2) — the case on op — choosing which result to
present. Registers around it (lessons 3–5) would make it a pipeline
stage; a state machine (lesson 8) sequencing operations over it would
make it a processor's execute unit. That's genuinely how CPUs begin.
The 8-bit ALU below implements eight operations:
000 ADD 001 SUB 010 AND 011 OR
100 XOR 101 SLL 110 SRL 111 SLT (set if less than, signed)
Details worth reading closely:
Subtraction reuses the adder: a + ~b + 1 — invert and add one is
two's complement in action, so ADD and SUB share hardware, selected by
one bit.
Flags: zero (result all zeros — one big NOR), carry (unsigned
overflow out of bit 8) and negative (MSB). Real ISAs add signed
overflow; try deriving it.
SLT peeks at the subtractor's sign — comparison is subtraction,
which is why CPUs implement < with the ALU they already have.
In the waveform, the testbench walks all eight ops on fixed operands
(a=0x5A, b=0x0F), then shows zero firing on SUB with equal inputs.
Predict each result before checking — by this lesson you can.
Experiment: add an opcode 111 -> MUL using lesson 7's * (mind the
width!), or registered outputs to make it a real pipeline stage.
The design
Verilog — design.v
// 8-bit ALU: 8 ops, shared adder/subtractor, flags.
module alu (
input wire [7:0] a, b,
input wire [2:0] op,
output reg [7:0] result,
output wire zero,
output wire carry, // unsigned carry/borrow out of add/sub
output wire negative
);
localparam [2:0] ADD = 3'd0, SUB = 3'd1, AND_ = 3'd2, OR_ = 3'd3,
XOR_ = 3'd4, SLL = 3'd5, SRL = 3'd6, SLT = 3'd7;
// One adder does both ADD and SUB: a + (~b) + 1 is a - b.
wire is_sub = (op == SUB) || (op == SLT);
wire [7:0] b_eff = is_sub ? ~b : b;
wire [8:0] addsub = {1'b0, a} + {1'b0, b_eff} + {8'd0, is_sub};
// Signed less-than from the subtraction's sign, fixed up for overflow.
wire ovf = (a[7] ^ b_eff[7] ^ 1'b1) & (a[7] ^ addsub[7]);
wire slt = addsub[7] ^ ovf;
always @* begin
case (op)
ADD, SUB: result = addsub[7:0];
AND_: result = a & b;
OR_: result = a | b;
XOR_: result = a ^ b;
SLL: result = a << b[2:0];
SRL: result = a >> b[2:0];
default: result = {7'd0, slt}; // SLT
endcase
end
assign zero = (result == 8'd0);
assign carry = addsub[8];
assign negative = result[7];
endmodule
Show the VHDL version
VHDL — design.vhd
-- 8-bit ALU: 8 ops, shared adder/subtractor, flags.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity alu is
port (
a, b : in std_logic_vector(7 downto 0);
op : in std_logic_vector(2 downto 0);
result : out std_logic_vector(7 downto 0);
zero : out std_logic;
carry : out std_logic;
negative : out std_logic
);
end entity;
architecture rtl of alu is
signal is_sub : std_logic;
signal b_eff : std_logic_vector(7 downto 0);
signal addsub : unsigned(8 downto 0);
signal ovf, slt : std_logic;
signal res : std_logic_vector(7 downto 0);
begin
is_sub <= '1' when op = "001" or op = "111" else '0';
b_eff <= not b when is_sub = '1' else b;
addsub <= resize(unsigned(a), 9) + resize(unsigned(b_eff), 9)
+ ("00000000" & is_sub);
ovf <= (not (a(7) xor b_eff(7))) and (a(7) xor addsub(7));
slt <= addsub(7) xor ovf;
process (all) begin
case op is
when "000" | "001" => res <= std_logic_vector(addsub(7 downto 0));
when "010" => res <= a and b;
when "011" => res <= a or b;
when "100" => res <= a xor b;
when "101" => res <= std_logic_vector(
shift_left(unsigned(a), to_integer(unsigned(b(2 downto 0)))));
when "110" => res <= std_logic_vector(
shift_right(unsigned(a), to_integer(unsigned(b(2 downto 0)))));
when others => res <= "0000000" & slt;
end case;
end process;
result <= res;
zero <= '1' when res = x"00" else '0';
carry <= addsub(8);
negative <= res(7);
end architecture;
The testbench
Verilog — tb.v
`timescale 1ns/1ns
module tb;
reg [7:0] a = 8'h5A, b = 8'h0F;
reg [2:0] op = 0;
wire [7:0] result;
wire zero, carry, negative;
alu dut (.a(a), .b(b), .op(op), .result(result),
.zero(zero), .carry(carry), .negative(negative));
integer i;
initial begin
$dumpfile("wave.vcd"); $dumpvars(0, tb);
// walk all 8 ops on 0x5A, 0x0F
for (i = 1; i < 8; i = i + 1)
#10 op = i[2:0];
// zero flag demo: 0x33 - 0x33
#10 begin a = 8'h33; b = 8'h33; op = 3'd1; end
// signed comparison demo: -10 < 3
#10 begin a = 8'hF6; b = 8'h03; op = 3'd7; end
#10 $finish;
end
endmodule
Simulated waveform
This trace was produced by actually simulating the code
above with Icarus Verilog.