ALU: The Heart of Your Processor

An adder is great, but a processor needs more than just addition. It needs to compare values, perform bitwise logic, shift data, and subtract. All of these operations live in a single unit called the Arithmetic Logic Unit — the ALU.

From the outside, an ALU looks complicated. It takes two N-bit numbers and a control signal (the operation code, or opcode), and produces one N-bit result plus some status flags. But inside, the ALU follows the same principle you already learned: compute everything in parallel, then pick the right answer with a MUX.

Let's build a minimal 8-bit ALU that supports two operations: addition and bitwise AND. The opcode is a single bit: 0 means ADD, 1 means AND.

In Verilog:

module ALU8(
    input wire [7:0] a,
    input wire [7:0] b,
    input wire op_code,
    output wire [7:0] out,
    output wire carry_out
);
    wire [7:0] out_add;
    wire [7:0] out_and;
    
    Adder8 adder8(out_add, carry_out, a, b, 1'b0);
    
    and aa0(out_and[0], a[0], b[0]);
    and aa1(out_and[1], a[1], b[1]);
    and aa2(out_and[2], a[2], b[2]);
    and aa3(out_and[3], a[3], b[3]);
    and aa4(out_and[4], a[4], b[4]);
    and aa5(out_and[5], a[5], b[5]);
    and aa6(out_and[6], a[6], b[6]);
    and aa7(out_and[7], a[7], b[7]);
    
    Mux m0(out[0], out_add[0], out_and[0], op_code);
    Mux m1(out[1], out_add[1], out_and[1], op_code);
    Mux m2(out[2], out_add[2], out_and[2], op_code);
    Mux m3(out[3], out_add[3], out_and[3], op_code);
    Mux m4(out[4], out_add[4], out_and[4], op_code);
    Mux m5(out[5], out_add[5], out_and[5], op_code);
    Mux m6(out[6], out_add[6], out_and[6], op_code);
    Mux m7(out[7], out_add[7], out_and[7], op_code);
endmodule

Here's what happens at every moment in time. The Adder8 unit receives a and b and computes a + b as an 8-bit result on the wire out_add. Simultaneously, eight AND gates compute a & b, bit by bit, producing out_and. Both results are valid at the same instant. Then, for each of the 8 output bits, a 2-to-1 MUX selects between the adder's result and the AND result, using op_code as the selector. When op_code = 0, every MUX passes the adder's bit; when op_code = 1, every MUX passes the AND's bit. The output out is a clean, single-byte result.

To add subtraction, you'd build a block that computes two's complement of b (invert all bits and add 1), pass it through another MUX stage, and expand the opcode to 2 bits. The pattern scales: more operations → more parallel compute blocks → a bigger final MUX. This is why ALU design is sometimes called "the MUX at the end of the world."

The key insight is that the ALU doesn't decide what to compute. It computes everything, always, and uses the opcode only to select which result reaches the output. There's no if (op == ADD) anywhere in the hardware — that conditional lives in the programmer's mind. The silicon just runs all paths, all the time.

This design philosophy — brute-force parallelism followed by selection — is what makes processors fast. While a software interpreter would check the opcode and jump to the correct subroutine (a serial process), the ALU's Mux selects the answer in a single gate delay, often under a nanosecond.

Level 36 ("ALU8 in Verilog") is the final challenge of the Verilog series. You'll connect an Adder8, eight AND gates, and eight Mux instances into a working 2-operation ALU. When you toggle the opcode and watch the output change instantly between a + b and a & b, you'll be looking at the same fundamental architecture used in every processor from the 6502 to the Apple M4.