Loading header...

The ALU Is Just a Big Multiplexer

Last lesson ended with the control unit's decoder spitting out a handful of signals, one of which was a mysterious "ALU op-select" field. You've probably pictured the ALU as some kind of smart calculator that decides whether to add or subtract based on the instruction. That picture is wrong, and it's wrong in a way that's actually delightful once you see it: the ALU isn't deciding anything. It's computing every operation it knows how to do, all the time, in parallel — and the op-select bits are just choosing which of those already-computed answers gets allowed through to the output. The ALU is a calculator pretending to be a switchboard, or really, a switchboard pretending to be a calculator.


One Circuit, Many Results, Simultaneously

Inside a typical ALU, the same two input operands (call them A and B) feed several independent functional blocks at once:

flowchart LR classDef cpu fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a classDef bus fill:#f3e8ff,stroke:#9333ea,color:#581c87 classDef warn fill:#fee2e2,stroke:#dc2626,color:#7f1d1d A["Operand A"]:::cpu B["Operand B"]:::cpu ADD["Adder\n(A + B)"]:::warn SUB["Subtractor\n(A - B)"]:::warn AND["AND gate array\n(A & B)"]:::warn OR["OR gate array\n(A \| B)"]:::warn XOR["XOR gate array\n(A ^ B)"]:::warn SHL["Shift-left network"]:::warn SHR["Shift-right network"]:::warn A --> ADD B --> ADD A --> SUB B --> SUB A --> AND B --> AND A --> OR B --> OR A --> XOR B --> XOR A --> SHL A --> SHR MUX["Output MUX\n(function-select chooses winner)"]:::bus ADD --> MUX SUB --> MUX AND --> MUX OR --> MUX XOR --> MUX SHL --> MUX SHR --> MUX OUT["ALU result\n(only ONE path passes through)"]:::cpu MUX --> OUT

Every one of those blocks computes its result on every single clock cycle the ALU is active, regardless of whether the instruction actually wants that result. The adder computes A+B even when you asked for an AND. The shifter shifts even when you asked for subtraction. This sounds wasteful — and it costs a little extra silicon area and power — but it buys something enormously valuable: constant, predictable timing. There's no "decide which circuit to route to first" delay; everything is already computed by the time the multiplexer needs to choose, because they all started at the same instant.


Learning Objectives

By the end of this lesson, you should be able to:

  • Explain why many ALU subcircuits compute in parallel.
  • Describe how function-select bits choose one ALU result through a multiplexer.
  • Relate ALU delay to the CPU clock period and critical path.
  • Identify where status flags come from during arithmetic and logic operations.
  • Predict why adders and shifters often dominate ALU timing.

The Function-Select Field Is the Mux's Address Line

This is where lesson 2's decoder output reappears directly. The control unit's "ALU op-select" signal — typically 3 to 4 bits — is wired straight into the select lines of the output multiplexer. It is, quite literally, an address: just like the opcode addressed a row in the decoder's lookup table, the function-select field addresses one input line of the mux out of 2^n candidates.

Function-select bits Operation selected (typical RISC ALU)
000 ADD
001 SUB
010 AND
011 OR
100 XOR
101 Shift Left Logical
110 Shift Right Logical
111 Pass-through / compare

With 3 bits you get 8 possible operations — note the direct echo of lesson 2's "2^n combinations" idea, just applied one layer deeper in the datapath, inside a single functional unit instead of across the whole control unit.


Where Do the Select Bits Actually Come From?

Often, the function-select field isn't computed by anything fancy — it's lifted directly from a sub-field of the opcode itself, with at most a small amount of extra logic. Look at the AVR opcode map for arithmetic and logic instructions:

Instruction Opcode pattern ALU op-select asserted
ADD Rd, Rr 0000 11rd dddd rrrr ADD
SUB Rd, Rr 0001 10rd dddd rrrr SUB
AND Rd, Rr 0010 00rd dddd rrrr AND (bitwise)
OR Rd, Rr 0010 10rd dddd rrrr OR (bitwise)
EOR Rd, Rr 0010 01rd dddd rrrr XOR

Notice the top 6 bits change in a structured way across these — 0000 11, 0001 10, 0010 00, 0010 10, 0010 01. The decoder's match network (from lesson 2) recognizes each unique top-bit pattern and asserts a different, fixed function-select value into the ALU mux. There is no runtime "if opcode equals X then function equals Y" branch happening in software-like fashion — it's a hardwired translation, baked into how the decoder's outputs are physically connected to the mux's select inputs.

flowchart TD classDef cpu fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a classDef warn fill:#fee2e2,stroke:#dc2626,color:#7f1d1d classDef bus fill:#f3e8ff,stroke:#9333ea,color:#581c87 OP["Opcode top bits\n0001 10"]:::cpu DEC["Decoder match network\n(lesson 2)"]:::warn SEL["ALU function-select\n= SUB (001)"]:::bus MUX["ALU output mux\nroutes Subtractor's result"]:::cpu OP --> DEC --> SEL --> MUX

Worked Example: Select Bits and Timing

Assume a teaching ALU has these worst-case delays:

Block Delay
AND/OR/XOR arrays 1 ns
Shifter 3 ns
Adder/subtractor 5 ns
Output mux 1 ns

Because all blocks compute in parallel, the selected operation does not change the clock budget. The ALU stage must allow the slowest candidate result to settle before the mux output is trusted:

  • Slowest function block: 5 ns.
  • Output mux delay: 1 ns.
  • ALU worst-case delay: 5 ns + 1 ns = 6 ns.

Even a simple AND instruction cannot use a 2 ns ALU budget if the same pipeline stage also supports ADD. Synchronous CPU timing is designed around the worst valid path, not the easiest instruction.


Why Design It This Way? Speed Over Elegance

A purist might ask: why not build a single reconfigurable circuit that "becomes" an adder or an AND gate on demand, saving silicon? Because reconfiguring a circuit takes time — and time is the one resource a clocked synchronous design cannot spend carelessly. Computing all results in parallel and selecting at the very last stage means the ALU's worst-case delay is the same no matter which operation you choose. That predictability is what lets a CPU designer say "the ALU stage takes exactly X nanoseconds" and build a clock period around that guarantee.

flowchart LR classDef clk fill:#fef9c3,stroke:#ca8a04,color:#713f12 classDef warn fill:#fee2e2,stroke:#dc2626,color:#7f1d1d A["All functional blocks\ncompute in parallel"]:::clk B["Propagation delay =\nslowest block's delay\n(usually the adder)"]:::warn C["Mux selection adds\nonly 1 extra gate delay"]:::clk D["Total ALU delay is\nconstant, regardless\nof which op was chosen"]:::warn A --> B --> C --> D

This constant-delay property is exactly what later allows pipelining to work cleanly — if the ALU stage's timing varied by instruction type, pipeline stage boundaries would become a nightmare to schedule. We'll touch this again in lesson 4 when comparing hardwired vs microcoded control.


Flags Are Just Another Output Tap

While we're inside the ALU, it's worth noting the status flags (Zero, Carry, Negative, Overflow on AVR's SREG, or N/Z/C/V on ARM's APSR) aren't computed by some separate "flag logic" — they're tapped directly off the selected result and its carry/borrow/sign bits, latched into the flag register on the same clock edge the ALU result is captured.

flowchart LR classDef cpu fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a classDef bus fill:#f3e8ff,stroke:#9333ea,color:#581c87 classDef clk fill:#fef9c3,stroke:#ca8a04,color:#713f12 MUX["ALU mux output"]:::bus R["Result → Register file"]:::cpu F["Carry-out, sign bit,\nzero-detect → SREG/APSR"]:::bus CLK["Same clock edge\nlatches both"]:::clk MUX --> R MUX --> F CLK -.-> R CLK -.-> F

Common Mistakes

  • Imagining the ALU reconfigures itself into only one circuit per instruction.
  • Forgetting that the clock period must cover the slowest legal ALU path.
  • Treating Carry and Overflow as the same flag; carry is unsigned arithmetic, overflow is signed range error.
  • Checking flags from an unselected internal result instead of the mux-selected ALU result.
  • Assuming a wider ALU is just "more bits"; wider adders increase carry propagation unless special adder structures are used.

Summary

The ALU doesn't "decide" what to compute — it computes everything, every cycle, and a few function-select bits (handed down directly from the decoder you met in lesson 2) simply choose which already-finished answer gets allowed out the door through a multiplexer. This parallel-then-select design trades a little silicon for guaranteed-constant timing, which is the quiet hero behind reliable clock periods. Now that you've seen how opcodes drive both the decoder and the ALU, the natural next question is: how does the chip generate the right control signals for instructions that need multiple clock cycles to finish?

Next: Microcode vs Hardwired Control

Further Reading

  • Harris and Harris, Digital Design and Computer Architecture, arithmetic logic unit and datapath chapters.
  • Patterson and Hennessy, Computer Organization and Design, single-cycle datapath sections.
  • Microchip, AVR Instruction Set Manual, arithmetic/logical instructions and SREG flag definitions.
  • Arm, Cortex-M Architecture Reference Manual, APSR condition flags.

Mind Map

mindmap root((ALU as Multiplexer)) Core idea Blocks compute in parallel Select bits choose result Output mux is final gate Operations Add A plus B Subtract A minus B AND OR XOR Shift left right Compare or pass through Timing Stage delay equals slowest block plus mux Adder often critical Clock period uses worst case Predictable delay helps pipelines Flags Zero from selected result Carry from add subtract Negative from sign bit Overflow from signed range Flags latch with result Practical checks Verify op select table Test edge values Check signed and unsigned cases Confirm flag update rules Common mistakes Carry equals overflow Ignoring critical path Reading unselected outputs Assuming ALU decides operation