Superscalar Execution

Imagine a factory line that can only assemble one car per minute. This holds no matter how many workers stand idle nearby. That is a scalar pipeline: one instruction fetched, decoded, executed, and retired per cycle. This happens even though the chip has plenty of spare transistors doing nothing. Superscalar execution asks a simple question. Why not build several assembly lines side by side? Cars could be routed to whichever line is free. In processors, this means fetching, decoding, and executing multiple instructions in the same clock cycle. This is done by replicating pipeline stages and functional units. This idea underlies almost every general-purpose CPU shipped since the early 1990s. The Intel Pentium, the MIPS R10000, the IBM POWER series, and every modern Arm and x86 core are superscalar. They issue two, four, six, or even eight instructions per cycle. Without superscalar execution, clock-speed scaling alone would have hit a wall decades ago. Single-threaded performance would look very different. Your phone, your laptop, and the servers behind every website you visit all depend on this mechanism. It turns transistor budgets into speed. The central engineering challenge is not replicating hardware — that is comparatively easy. Instead, it is deciding, every single cycle, which instructions are safe to run together. Two instructions cannot be issued in the same cycle under certain conditions. This happens if one needs a result the other hasn't produced yet. It also happens if both want to write the same register in an order that matters. This is where dependence checking, building on Instruction-Level Parallelism and Dependences, becomes the beating heart of the design. Superscalar hardware must detect data hazards, structural hazards, and control hazards across a group of instructions. It must do this in parallel, in real time, without slowing the clock down. This article builds a precise mental model of in-order superscalar issue. It covers how instructions are fetched in bundles. It explains how dependence checks are performed in parallel hardware. It shows how issue width interacts with hazards to bound speedup. Finally, it explains why real designs eventually needed to reorder instructions. This topic leads directly into Out-of-Order Execution and, later, into Simultaneous Multithreading.

Key takeaways
  • Superscalar processors fetch, decode, and execute multiple instructions per cycle using replicated pipeline stages and functional units.
  • The hard problem is dependence checking: detecting RAW, WAR, and WAW hazards within a fetched instruction group in a single cycle.
  • Issue width (instructions per cycle) is capped by available ILP, hazard density, and hardware complexity, which grows faster than linearly with width.
  • In-order superscalar designs stall the entire bundle when any instruction in it has an unresolved hazard, wasting issue slots.
  • Amdahl-style reasoning shows real speedup is far below the nominal issue width because of dependence stalls and limited ILP.
  • These limitations motivate reordering instructions dynamically, the subject of Out-of-Order Execution.
The core insight

Superscalar execution turns idle transistors into throughput by issuing several independent instructions per cycle, at the cost of hardware that must prove — every cycle — that those instructions really are independent.

The idea

A scalar pipeline advances at most one instruction through each pipeline stage per cycle: at best, one instruction completes per cycle in steady state (a throughput of Instructions Per Cycle, IPC, equal to 1). A superscalar pipeline widens every stage — fetch, decode, issue, execute, and often writeback — so that a bundle of ww instructions can move through together, where ww is the issue width (also called superscalar degree). An issue width of 4 means the machine can, in the best case, fetch four instructions, decode four instructions, and dispatch four instructions to execution units in one cycle, giving a theoretical peak IPC of 4. Two structural ingredients make this possible. First, replicated resources: multiple ALUs, multiple load/store ports, multiple decoders, and wide fetch/decode datapaths, so that ww instructions physically have somewhere to go simultaneously. Second, and far more subtle, parallel dependence checking: hardware logic that examines all (w2)\binom{w}{2} pairs of instructions in the current bundle (plus in-flight instructions from prior bundles) and determines which subset can legally issue together this cycle. Instructions with an unresolved hazard against an earlier instruction in program order must wait, and in a simple in-order superscalar design, if instruction ii in the bundle stalls, every instruction after it in program order stalls too — even if instruction i+1i{+}1 has no hazard at all. This is the crucial asymmetry that separates in-order superscalar from the out-of-order designs that came later. The three hazard types inherited from pipelining reappear here, but now checked across a group instead of just against the immediately preceding stage:

  • RAW (read-after-write / true dependence): instruction jj reads a register that instruction ii (earlier in program order, possibly in the same bundle) writes. jj cannot issue until ii's result is available (or forwarded).
  • WAW (write-after-write): two instructions in the bundle write the same destination register; the later one in program order must be the one whose write is architecturally visible last.
  • WAR (write-after-read): a later instruction writes a register an earlier instruction still needs to read; in-order pipelines with a single register-read stage rarely hit this in-bundle, but it matters once instructions are reordered, foreshadowing Out-of-Order Execution.
  • Structural hazards also multiply: if the bundle contains three loads but the machine has only two load/store units, one load must be held back regardless of data dependences.

How it works

The superscalar front end must do three things every cycle: fetch a bundle, decode it, and determine an issue mask — which instructions in the bundle (and which carried-over instructions from a prior stalled bundle) actually proceed to execution this cycle. The algorithm below models a canonical in-order superscalar issue stage with width ww. ``` // Superscalar in-order issue, width w // pending[]: instructions already fetched/decoded, waiting to issue, // in strict program order (oldest first) // scoreboard: tracks which registers have results "not yet ready" // (i.e., produced by an in-flight, uncompleted instruction) function ISSUE_CYCLE(pending, scoreboard, functional_units): issued = [] blocked = false // once true, stop issuing (in-order) used_units = {} // structural resources claimed this cycle for k = 0 to w - 1: if blocked or pending is empty: break instr = pending[0] // oldest remaining instruction // 1. Structural hazard check unit = REQUIRED_UNIT(instr) if used_units.count(unit) >= AVAILABLE(unit): blocked = true break // 2. RAW hazard check against scoreboard (in-flight results) // and against instructions already issued THIS cycle for src in SOURCE_REGS(instr): if scoreboard[src].not_ready: blocked = true for prior in issued: if DEST_REG(prior) == src: blocked = true // same-cycle producer, not forwarded yet if blocked: break // 3. WAW hazard check: if a same-cycle earlier instruction in the // bundle writes the same destination, order must be preserved; // with in-order issue this is automatic since we issue oldest-first // and only the last write commits architecturally. // Passed all checks: commit to issuing this instruction issued.append(instr) used_units[unit] += 1 mark scoreboard[DEST_REG(instr)] as "not_ready" (result pending) pending.pop_front() return issued // 0 to w instructions issued this cycle ``` Three points about this pseudocode deserve emphasis because they are exactly where correctness lives: 1. Program order is preserved by construction. The loop always considers `pending[0]`, the oldest not-yet-issued instruction, first. The moment any instruction fails a check, `blocked` is set and the loop breaks — no younger instruction is allowed to "jump ahead," which is precisely the in-order-issue guarantee. 2. Same-cycle producers are not treated as forwarded for free. A same-cycle RAW dependence (instruction 2 in the bundle needs the result of instruction 1, also in the bundle) blocks issue unless the microarchitecture has an explicit same-cycle bypass path; the pseudocode is conservative and correct by default, and a real design that adds intra-bundle forwarding must justify it with an explicit bypass network. 3. Structural hazards are checked before data hazards, but the order doesn't matter for correctness — both are AND-ed together; what matters is that both are checked for every candidate instruction before it is allowed into `issued`.

Figure
In-order superscalar pipeline, issue width 3, with a bundle stall
Cycle-by-cycle issue, width = 3 Cycle 1 Cycle 2 Cycle 3 Cycle 4 Lane 0 Lane 1 Lane 2 I1: ADD r1 I2: ADD r2 I3: MUL r3 I4: SUB r1? stall stall I4: SUB r1 I5: LD r4 I6: ADD r5 I7: OR r6 I8: SW r6 (idle slot) Legend: issued this cycle blocked by RAW hazard stalled: in-order, behind a blocked instr. I4 (SUB) reads r1, written by I1 (ADD) — result not ready until end of cycle 1's execute stage. In-order issue: I5, I6 could issue independently, but must wait behind I4 in program order. Net effect over 4 cycles: 8 instructions issued in 4 cycles = IPC 2.0, despite peak width 3.

Complexity & correctness

Hardware complexity of dependence checking. For a bundle of width ww, every instruction's source registers must be compared against every other instruction's destination register to detect RAW hazards, and every destination must be compared against every other destination for WAW hazards. This requires O(w2)O(w^2) comparators in the checking network (each comparator being a small equality circuit on register-number bits). Because comparator delay and wiring fan-out grow with ww, the critical path through the issue logic — and hence achievable clock frequency — degrades roughly quadratically as width increases, which is why practical issue widths have historically topped out around 4–8 rather than growing to 32 or 64. This O(w2)O(w^2) scaling is the single most important complexity fact about superscalar hardware: doubling issue width more than doubles dependence-checking hardware, for a throughput gain that (as argued below) is typically much less than double.

Throughput bound. Let the dependence height of a straight-line code region be the length of the longest chain of true (RAW) dependences within it — this is exactly the notion introduced when discussing Instruction-Level Parallelism and Dependences. If a region of nn instructions has dependence height hh, then no schedule — no matter how wide the issue width — can execute it in fewer than hh cycles, because a dependence chain of length hh is a serial critical path. Superscalar width ww can shrink the number of cycles from nn (scalar) toward max⁡(h,⌈n/w⌉)\max(h, \lceil n/w \rceil) at best, but never below hh. This gives the honest complexity/performance statement:

cycles  ≥  max⁡ ⁣(h,  ⌈nw⌉)\text{cycles} \;\ge\; \max\!\left(h,\; \left\lceil \frac{n}{w} \right\rceil\right)

so IPC is bounded by

IPC  ≤  min⁡ ⁣(w,  nh).\text{IPC} \;\le\; \min\!\left(w,\; \frac{n}{h}\right).

The ratio n/hn/h is exactly the available instruction-level parallelism of the code. If a program has only 2-wide ILP on average, building a 6-wide superscalar machine wastes four issue slots per cycle on average — this is the concrete reason issue width alone does not guarantee proportional speedup, and it is the standard "Amdahl's-law-for-ILP" argument.

Correctness argument (in-order issue preserves sequential semantics). The claim to prove: the in-order superscalar issue algorithm produces results identical to running the same instructions one at a time in program order. The invariant maintained by `ISSUE_CYCLE` is: > At the start of every cycle, for every register rr, if some issued-but-not-yet-completed instruction will write rr, the scoreboard marks rr as not-ready, and no instruction reads a not-ready register in the same or an earlier cycle than its producer commits. This invariant is established by the RAW check (source registers are tested against the scoreboard and against same-cycle producers before an instruction is allowed into `issued`) and preserved because every newly issued instruction immediately marks its destination not-ready before the next candidate in the bundle is examined. WAW ordering is preserved automatically because instructions are only ever issued in increasing program-order index (`pending[0]` first, never skipped over), so if two instructions in flight write the same register, the one issued later in program order also completes its write later relative to program order — assuming a single, in-order writeback/commit stage, which real in-order superscalar pipelines provide. Combined, these guarantee that the final architectural state after all instructions retire is identical to sequential execution: the algorithm is a valid, hazard-safe parallelization of a sequential instruction stream, not merely a fast one.

Tradeoffs, stated honestly. In-order superscalar issue is simple to verify and cheap relative to out-of-order designs, but it pays for that simplicity with bundle-wide stalls: a single hazard anywhere in the bundle blocks everything behind it that cycle, even independent, ready-to-go instructions (visible in the worked example below). This wasted issue-slot problem is precisely what motivates dynamic instruction reordering, covered under Out-of-Order Execution.

Worked example

Consider this five-instruction sequence issued on a 3-wide in-order superscalar machine, with a one-cycle execute latency for ALU ops and a two-cycle latency for loads (result not ready until the end of the cycle after the load executes): ``` I1: ADD r1, r2, r3 ; r1 = r2 + r3 I2: LD r4, 0(r5) ; r4 = MEM[r5] (2-cycle latency) I3: ADD r6, r7, r8 ; r6 = r7 + r8 (independent of I1, I2) I4: SUB r9, r1, r6 ; needs r1 (I1) and r6 (I3) -> RAW I5: ADD r10, r4, r1 ; needs r4 (I2, 2-cyc) and r1 (I1) -> RAW ``` Trace, assuming full same-cycle forwarding is not available (a same-cycle producer cannot feed a same-cycle consumer, matching the conservative pseudocode above), and one load/store unit:

CycleLane 0Lane 1Lane 2Notes
1I1 issuesI2 issuesI3 issuesall mutually independent; r1, r4(pending), r6 marked not-ready
2I4: blocked——I4 needs r1 (ready end of cyc 1 ✓) and r6 (ready end of cyc 1 ✓) — wait, both ready! I4 issues.
2I4 issuesI5: blocked—I5 needs r4, still not ready (load latency 2 cyc, ready end of cyc 3) → whole rest of bundle stalls behind I5
3I5 issues——r4 now ready (end of cycle 2 completion + 1 more, per 2-cycle load); r1 already ready

Corrected cycle-by-cycle result: I1, I2, I3 issue in cycle 1 (3 instructions). I4's operands (r1 from I1, r6 from I3) are both ready at the start of cycle 2, so I4 issues alone in cycle 2 — but I5 cannot join it, because I5 needs r4 from the load I2, which has 2-cycle latency and is not ready until cycle 3. Because issue is in-order, I5 must wait even though lanes 1 and 2 are completely idle in cycle 2. In cycle 3, I5 finally issues. Total: 5 instructions in 3 cycles → IPC = 1.67, well under the peak width of 3, purely because of one RAW hazard on a load result and the in-order rule that idle lanes cannot be filled by instructions that come later in program order. This is exactly the throughput bound derived above: the dependence chain I2 → I5 has height 2 (in cycles, given the load latency), while n/w=5/3≈1.67n/w = 5/3 \approx 1.67 cycles-per-group if perfectly packed — the load latency, not width, is the binding constraint here.

Common pitfalls & traps

PitfallWhy it happensFix / correct mental model
Assuming IPC = issue widthConfusing peak issue width with sustained throughputIPC is bounded by min⁡(w,n/h)\min(w, n/h) — always check dependence height and hazard density
Forgetting WAW hazards because "the bundle looks like RAW-only"Two instructions writing the same register is easy to miss when scanning for readsAlways check destination-destination pairs, not just source-destination pairs
Assuming same-cycle forwarding is freeTreating a same-cycle producer/consumer pair as automatically bypassedSame-cycle bypass requires an explicit forwarding path; without it, such pairs must stall exactly like a pipelined RAW hazard
Ignoring structural hazards on shared unitsFocusing only on register dependences and forgetting the load/store port, divider, etc. are finiteCount required-unit usage per bundle and cap issue at available unit count
Treating in-order stall as "only that instruction waits"Intuition carries over incorrectly from superscalar's appearance of independenceIn-order issue means one stalled instruction blocks every later instruction in the same bundle, even independent ones

Pioneers & history

Superscalar execution grew out of a lineage of research into pipelining and instruction-level parallelism at IBM, CDC, and several university groups exploring how to exceed one-instruction-per-cycle throughput without changing the instruction set architecture visible to software.

Origins · 1 minJohn Cocke and how this was discovered
JC
John Cocke
1925–2002 · IBM Research, architect of RISC and superscalar concepts

John Cocke led foundational work at IBM on compiler optimization and reduced-instruction-set computing that directly informed the IBM RS/6000 (POWER) architecture, one of the earliest commercially successful superscalar designs. His insight that simpler, more regular instructions made aggressive parallel issue and pipelining tractable in hardware helped establish the RISC philosophy that superscalar and, later, out-of-order machines were built upon.

History
1965
— IBM System/360 Model 91 (Tomasulo's algorithm precursor work) explores multiple execution units and dynamic scheduling ideas.
1985
— Early superscalar research prototypes and IBM's "Cheetah/America" project explore multi-issue RISC pipelines.
1990
— IBM RS/6000 (POWER1) ships as one of the first mainstream 2- to 4-issue superscalar processors.
1993
— Intel Pentium introduces dual-issue (2-wide) superscalar execution to the mass x86 market.
1995
— Intel Pentium Pro adds out-of-order execution atop superscalar issue, foreshadowing the shift the field would take next.

Where this matters

Every mainstream general-purpose processor today — Arm Cortex and Apple Silicon cores in phones and laptops, AMD Zen and Intel Core in desktops and servers, IBM POWER and RISC-V high-performance implementations — is superscalar, typically issuing between 4 and 10 instructions per cycle in its front end. Compilers exploit this by scheduling independent instructions adjacent to each other (instruction scheduling, software pipelining) specifically to keep multiple issue lanes busy, directly leveraging the ILP-and-dependence reasoning from Instruction-Level Parallelism and Dependences. Superscalar issue logic is also the direct ancestor of the dispatch stage in Out-of-Order Execution machines, where the dependence-checking problem becomes even harder because instructions may issue in an order different from how they were fetched. It further sets the stage for Simultaneous Multithreading, where a superscalar machine's spare issue slots — the very slots wasted by hazard stalls in the worked example above — are filled with instructions from an entirely different thread, squeezing out throughput that single-thread ILP alone cannot supply. GPUs and DSPs use related but distinct multi-issue and VLIW ideas, where the compiler rather than hardware guarantees independence, trading dependence-checking hardware for compiler complexity — a design point directly informed by the O(w2)O(w^2) hardware cost argument above.

Ask the tutor: How does adding out-of-order issue change the dependence-checking hardware compared to in-order superscalar issue?

How is superscalar different from a simple deeper pipeline?

A deeper pipeline (more stages) increases clock frequency by shortening each stage's work, but still completes at most one instruction per cycle in steady state — it reduces latency per instruction, not throughput per cycle. Superscalar execution instead widens each stage to handle multiple instructions per cycle, directly raising peak IPC above 1. The two techniques are complementary: real processors are both deeply pipelined and superscalar, combining fast clocks with multi-instruction issue. Pipelining alone cannot exceed IPC = 1 no matter how many stages are added, because only one instruction enters the pipeline per cycle; superscalar removes exactly that ceiling by fetching and issuing a bundle of ww instructions together.

Why did early superscalar processors stay at 2-wide or 4-wide instead of going much wider?

Two multiplying constraints hold width down. First, dependence-checking hardware scales roughly O(w2)O(w^2), so an 8-wide checker needs about four times the comparator hardware of a 4-wide one, straining both die area and the critical path that sets clock frequency. Second, real programs simply don't offer unlimited ILP: the average dependence height between nearby instructions means that beyond a modest width, additional issue lanes sit idle most cycles because there's nothing independent left to fill them. Diminishing returns from limited ILP, combined with superlinear hardware cost, created a practical sweet spot historically around 3–6-wide issue for in-order designs, though modern out-of-order machines push wider using extra techniques.

Does superscalar execution require changes to the instruction set architecture (ISA)?

No — this is one of superscalar's most attractive properties. The ISA still describes a sequential stream of instructions with no explicit parallelism markers; the hardware alone decides, at runtime, which instructions can issue together. This contrasts with VLIW (Very Long Instruction Word) designs, where the compiler explicitly packs independent operations into wide instruction words and the hardware performs no dependence checking at all. Superscalar's ISA-transparency let processor vendors deliver large speedups to existing compiled binaries without recompilation, which was commercially decisive in the 1990s when large software ecosystems already existed for architectures like x86.

How does superscalar issue relate to out-of-order execution?

In-order superscalar issue examines instructions strictly in program order and stalls the whole bundle behind any hazard, which wastes issue slots whenever an early instruction in the bundle is blocked but later ones are ready. Out-of-order execution generalizes the dependence-checking machinery introduced here — the same RAW/WAW/WAR logic — but allows instructions to issue whenever their operands are ready, regardless of fetch order, using structures like reservation stations and reorder buffers to track this safely. Superscalar issue width is a prerequisite concept for out-of-order machines: you cannot usefully reorder instructions unless the underlying hardware can already issue and execute several of them per cycle.

What is the difference between issue width and IPC?

Issue width ww is a fixed hardware property: the maximum number of instructions the machine's pipeline stages are physically built to handle per cycle. IPC (instructions per cycle) is a measured, workload-dependent outcome: the average number of instructions that actually complete per cycle over some execution window. IPC can equal issue width only in the ideal case of infinite independent work with no hazards; in practice IPC is always less than or equal to ww, often substantially so, because of data hazards, structural hazards, and branch mispredictions. The gap between peak issue width and achieved IPC is precisely the quantity architects work hardest to close.

Can a superscalar processor issue instructions out of program order within the same bundle?

In a strictly in-order superscalar design, no — instructions are considered for issue in program order, and a stalled instruction blocks everyone behind it in the same cycle, as shown in the pseudocode and the worked example above. Some in-order designs add limited exceptions, such as allowing an independent load or store to bypass a stalled ALU instruction if the microarchitecture explicitly supports "limited out-of-order issue" for specific hazard classes, but this requires additional hazard-checking logic beyond the basic scheme. Full, general reordering across arbitrary instruction types is the defining feature of out-of-order execution, a distinct and more complex mechanism built on top of these same dependence-detection principles.

Why does WAW hazard checking matter if instructions issue in program order?

If issue and completion both strictly preserve program order, WAW is automatically safe, which is why the pseudocode's in-order guarantee handles it "for free." But real high-performance pipelines have varying execution latencies (a fast ADD versus a slow multi-cycle divide), so instructions can finish out of the order they were issued in, even under in-order issue. If a fast instruction is issued after a slow one but finishes writing its destination register first, and both target the same register, the final value could be wrong unless the hardware tracks completion order for writeback separately from issue order. This is why even in-order superscalar designs need explicit WAW bookkeeping at the writeback stage, not just at issue.

Before this

Where this leads