Capstone: A Simple ALU

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:

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.

10 20 30 40 50 60 70 80 90 t (ns) zero result[7:0] 69 4B A 5F 55 0 0 1 negative carry a[7:0] 5A 33 F6 b[7:0] F 33 3 op[2:0] 0 1 2 3 4 5 6 7 1 7 i[31:0] 1 2 3 4 5 6 7 8 is_sub ovf slt b_eff[7:0] F F0 F F0 CC FC addsub[8:0] 69 14B 69 14B 100 1F3 ADD 0 AND_ 2 OR_ 3

Open this lesson in the playground →