Open up any explanation of how a computer works and the same four pieces keep showing up: the ALU, the control unit, registers, and buses. These aren’t arbitrary categories — they’re the fundamental building blocks that make it possible for a CPU to fetch an instruction, figure out what it means, do the actual work, and move on to the next one, millions or billions of times per second.
This article goes through each of these components individually, in enough depth to actually understand how they cooperate, and then ties them together into the bigger picture of how a CPU executes a single instruction from start to finish.
The Arithmetic Logic Unit (ALU)
The ALU is where actual computation happens. Every arithmetic operation (addition, subtraction, multiplication in some designs) and every logical operation (AND, OR, XOR, NOT, bit shifts) that a program performs ultimately runs through the ALU.
How the ALU Works
At its core, an ALU is built from combinational logic circuits — arrangements of logic gates that produce an output based purely on current input, with no memory of past state. A basic ALU takes two input operands (each a binary number, typically 32 or 64 bits wide in modern CPUs) and a control signal specifying which operation to perform, and it produces a result along with status flags.
Operand A (e.g., 32-bit) ---\
>---[ ALU ]---> Result
Operand B (e.g., 32-bit) ---/ |
v
Operation code ----------------> Status Flags
(Zero, Carry, Overflow, Negative)
Status Flags
Beyond the raw result, the ALU sets flags that later instructions can check — this is how conditional branches actually work under the hood. A “Zero” flag gets set if the result was zero (used for equality checks). A “Carry” flag gets set if an addition overflowed the available bits (used for multi-precision arithmetic and unsigned overflow detection). An “Overflow” flag flags signed arithmetic overflow. A “Negative” (or sign) flag reflects the sign bit of the result. When source code writes if (a == b), the compiled machine code typically performs a subtraction and then checks the Zero flag.
Why ALU Design Matters
Modern high-performance CPUs don’t have just one ALU — they have several, allowing multiple arithmetic or logic operations to execute in the same clock cycle (part of what makes superscalar execution possible). Specialized ALUs also exist for specific data types: floating-point units (FPUs) handle decimal/real-number math using entirely different circuitry than integer ALUs, and vector/SIMD units apply the same operation across multiple data elements simultaneously.
The Control Unit
If the ALU is the muscle, the control unit is the nervous system. Its job is to orchestrate everything: fetching instructions from memory, decoding what they mean, and generating the control signals that tell every other component what to do and when.
Fetch-Decode-Execute, Driven by the Control Unit
The control unit manages the classic instruction cycle. During fetch, it uses the program counter (a register holding the address of the next instruction) to retrieve an instruction from memory, and increments the program counter to point at the next one. During decode, it interprets the binary instruction — determining the operation, the operands, and the addressing mode — and translates that into control signals. During execute, it directs the ALU, registers, and memory system to actually carry out the operation, routing data along the correct paths at the correct time.
Hardwired vs. Microprogrammed Control
There are two classic approaches to building a control unit. A hardwired control unit uses fixed logic circuits to generate control signals directly from the instruction bits — fast, but inflexible, since any change requires redesigning the circuitry. A microprogrammed control unit uses a small internal program (microcode) stored in on-chip memory to interpret each instruction as a sequence of simpler micro-operations — more flexible and easier to update or patch (which is part of how modern x86 chips can receive microcode updates to fix bugs or security issues), at some cost to raw speed. Most modern high-performance CPUs use a hybrid: simple, common instructions are hardwired for speed, while complex or rare instructions fall back to microcode.
Registers
Registers are small storage locations built directly into the CPU, offering the fastest possible access to data — typically just a single clock cycle, compared to potentially hundreds of cycles to reach main memory.
Types of Registers
| Register Type | Purpose |
|---|---|
| General-purpose registers | Hold data and intermediate results during computation |
| Program Counter (PC) | Holds the address of the next instruction to fetch |
| Instruction Register (IR) | Holds the instruction currently being decoded/executed |
| Stack Pointer (SP) | Tracks the top of the call stack |
| Status/Flags Register | Holds condition flags (zero, carry, overflow, etc.) |
| Memory Address Register (MAR) | Holds the address for an upcoming memory access |
| Memory Data Register (MDR) | Holds data being transferred to/from memory |
Why So Few Registers?
Architecturally visible general-purpose registers are a scarce resource — a typical modern architecture might expose only 16 to 32 of them to software, even though the actual chip may internally have hundreds of physical registers (used behind the scenes for register renaming in out-of-order execution, discussed elsewhere). This scarcity is deliberate: encoding which register an instruction uses takes bits in the instruction format, and more visible registers means larger instructions or more complex encoding. Compilers spend significant effort on register allocation — deciding which program variables get to live in these precious few register slots at any given moment versus being “spilled” to slower memory.
Buses
Buses are the communication channels that let the CPU, memory, and I/O devices exchange data. Without buses, none of the other components could actually cooperate — they’d just be isolated islands.
The Three Bus Types
The address bus carries the memory address the CPU wants to read from or write to; its width determines the maximum addressable memory (a 32-bit address bus can address up to 4 GB, which is why 32-bit systems historically hit that memory ceiling). The data bus carries the actual data being transferred; its width affects how much data moves per transfer, with modern systems commonly using 64-bit-wide data buses. The control bus carries signals coordinating the transfer itself — read/write indicators, clock signals, interrupt requests, and bus-request/grant signals for arbitration when multiple devices might want to use the bus at once.
CPU Memory
|---- Address Bus --------->| (where to read/write)
|<--- Data Bus ------------>| (the actual data, bidirectional)
|---- Control Bus --------->| (read/write/clock signals)
Bus Bottlenecks
Because a bus is a shared resource, it’s a natural point of contention — this is the physical embodiment of the “von Neumann bottleneck” mentioned in broader architecture discussions. Modern systems mitigate this with techniques like multiple parallel buses for different purposes (separate paths for CPU-to-cache and CPU-to-memory traffic), wider buses, higher bus clock speeds, and interconnect technologies (like point-to-point links in modern multi-core and multi-socket systems) that reduce contention compared to older shared-bus designs.
Putting It All Together: Executing One Instruction
Consider a simple instruction: ADD R1, R2, R3 (add the values in registers R2 and R3, store the result in R1).
- The control unit uses the program counter to fetch the instruction from memory via the address and data buses, loading it into the instruction register.
- The control unit decodes the instruction, identifying it as an ADD operation with source registers R2, R3 and destination register R1.
- The control unit signals the register file to output the values of R2 and R3 onto internal data paths feeding the ALU.
- The ALU receives these two operands, performs the addition, and produces a result along with any relevant status flags (like a carry-out flag if the result overflowed).
- The control unit signals the register file to write the ALU’s result into R1.
- The program counter increments, and the cycle begins again for the next instruction.
This entire sequence, in a modern CPU, doesn’t happen for just one instruction at a time in isolation — it happens for many instructions simultaneously across different pipeline stages, as covered in dedicated discussions of CPU pipelining. But the fundamental roles of each component — ALU computing, control unit orchestrating, registers storing, and buses transporting — remain the same regardless of how many instructions are in flight at once.
Common Misconceptions
“The ALU does everything the CPU does.” The ALU only performs computation; fetching, decoding, sequencing, and moving data are all handled by the control unit and supporting infrastructure, not the ALU itself.
“More registers is always strictly better.” More architectural registers reduce memory traffic, but they also increase instruction encoding size and hardware complexity; ISA designers balance this trade-off carefully rather than maximizing register count blindly.
“Buses are just wires and don’t really affect performance.” Bus width, clock speed, and arbitration overhead can be significant real-world bottlenecks, especially in systems with many devices contending for shared memory bandwidth.
A Closer Look at the ALU’s Internal Logic
It’s worth understanding, at least conceptually, how an ALU actually performs addition, since it illustrates how simple logic gates combine into meaningful computation. A single-bit full adder takes two input bits plus a carry-in bit from the previous position, and produces a sum bit and a carry-out bit, using a small combination of XOR, AND, and OR gates. Chain enough of these full adders together, with each one’s carry-out feeding the next one’s carry-in, and the result is a ripple-carry adder capable of adding numbers of arbitrary width, one bit position at a time.
Bit 0: A0 + B0 + Cin(0) -> Sum0, Carry0
Bit 1: A1 + B1 + Carry0 -> Sum1, Carry1
Bit 2: A2 + B2 + Carry1 -> Sum2, Carry2
...and so on for every bit in the operand width
The obvious drawback of a ripple-carry adder is speed: the final bit’s result can’t be known until the carry has “rippled” through every preceding bit position, creating a delay that grows with operand width. Real ALUs use considerably more sophisticated designs — carry-lookahead adders, for instance, compute carry signals for multiple bit positions in parallel using additional logic, trading more circuitry for significantly reduced latency. This is a small but genuinely illustrative example of a pattern that recurs constantly in hardware design: a simple, elegant solution exists, but real, high-performance implementations pay a complexity cost specifically to eliminate a critical bottleneck.
Multiplication, Division, and Specialized Circuits
Addition and subtraction are relatively cheap for an ALU to implement efficiently, but multiplication and especially division are considerably more expensive in terms of the circuitry (and time) required. Integer multiplication is often implemented using a combination of shift-and-add techniques accelerated by dedicated multiplier circuits, since naive repeated addition would be far too slow for practical use. Division is typically the most expensive basic arithmetic operation a CPU performs, frequently taking many more cycles than addition, multiplication, or logical operations — a fact that shows up in real-world performance tuning, where replacing a division with a multiplication by a precomputed reciprocal, or with bit-shifting when dividing by a power of two, remains a genuinely useful optimization technique in performance-critical code, and one that compilers themselves apply automatically whenever they can safely determine the divisor at compile time.
Floating-point arithmetic, following the IEEE 754 standard used across virtually all modern hardware, requires an entirely separate functional unit (the FPU) with considerably more complex circuitry, since it has to handle a mantissa, an exponent, and various special cases (infinities, NaN — “not a number” — values, and denormalized numbers representing extremely small magnitudes) correctly. This complexity is exactly why floating-point operations have historically been, and in some cases remain, slower than equivalent integer operations, and why numerically sensitive code sometimes needs to be written with real awareness of floating-point precision and rounding behavior rather than treating it as equivalent to idealized real-number arithmetic.
The Control Unit’s Role in Handling Exceptions and Interrupts
Beyond the routine fetch-decode-execute cycle, the control unit is also responsible for handling interrupts (signals from external devices, like a network card indicating incoming data) and exceptions (conditions arising from the currently executing instruction itself, like an attempt to divide by zero or access invalid memory). When either occurs, the control unit has to suspend normal instruction fetching, save enough of the current processor state to resume correctly afterward, and transfer control to a predefined handler routine, typically part of the operating system kernel.
This mechanism is the foundation of nearly everything an operating system does at a low level: preemptive multitasking works because a timer interrupt periodically forces the control unit to hand control back to the OS scheduler; device drivers work because the control unit responds to interrupts signaling that a device needs attention; system calls work because a program can deliberately trigger a controlled exception to request a privileged operation from the kernel. None of this would be possible without the control unit’s ability to reliably detect these conditions and redirect execution in a controlled, recoverable way.
Register Windows and Specialized Register Techniques
While most architectures expose a fixed, small set of general-purpose registers, some ISAs have experimented with more elaborate register schemes. Older SPARC processors, for example, implemented register windows — a technique where a much larger physical register file is divided into overlapping “windows,” and each function call gets its own fresh window rather than needing to explicitly save and restore registers to memory on every call. This reduced memory traffic for function-call-heavy code at the cost of significant hardware complexity, and it illustrates that the “small, fixed register set” model, while dominant, isn’t the only possible design; different ISAs have made different trade-offs here based on the workloads and constraints they were designed around.
Modern out-of-order CPUs, as mentioned in broader pipeline discussions, use register renaming to map a small number of architectural registers onto a much larger pool of physical registers internally — in some sense a modern echo of the same underlying goal that register windows pursued decades earlier: giving the hardware more actual storage to work with than the ISA’s visible register count would suggest.
Bus Arbitration in Multi-Device Systems
When more than one device might need to use a shared bus at the same time — for instance, a DMA-capable disk controller wanting to write directly to memory while the CPU is also trying to access memory — some mechanism has to decide who gets priority. This is called bus arbitration. Simple schemes use a fixed priority ordering; more sophisticated schemes use round-robin or dynamic priority approaches to avoid any single device being permanently starved of access. Modern systems increasingly avoid this problem altogether by moving away from a single shared bus toward point-to-point interconnects (like PCIe or a CPU’s internal ring or mesh interconnect linking cores and cache), where each connection has dedicated bandwidth rather than being shared and arbitrated among many devices — directly reducing the kind of contention that plagued older, simpler shared-bus designs.
Conclusion
The ALU, control unit, registers, and buses form the essential machinery underneath every instruction a CPU ever executes. The ALU computes, the control unit coordinates, registers provide lightning-fast local storage, and buses move everything between components. Understanding how these four pieces interact — using the simple fetch-decode-execute walkthrough as an anchor — turns the CPU from an opaque black box into a system whose behavior can actually be reasoned about, which is exactly the kind of understanding that separates surface-level familiarity with computers from genuine technical depth.
