Why Cray threw away the cache: Flynn, SIMD and the vector idea

After this lesson you will place any machine in Flynn's taxonomy by counting its streams, show with numbers why a pipelined vector unit beats an array of processing elements, give the reason behind each design choice of a Cray-style vector machine, and say where 572 of the 578 instructions of scalar DAXPY disappear on VMIPS.

In January 1968 IBM announced the System/360 Model 85, the first commercially available computer with a cache memory. Eight years later Seymour Cray's company shipped the Cray-1. It was "10 times faster than competing machines" and the fastest computer in the world from 1976 to 1982, in the Computer History Museum's words — and it had no data cache at all. Its operands travelled between a main memory of sixteen banks and its registers, among them eight vector registers of 64 elements each.

Lessons 22–28 showed why a modern core starves without its caches. So why would the fastest machine of its decade leave the data cache out, and how could it still win?

A second number points to the answer. On VMIPS, the textbook machine built on the Cray-1's model, the loop Y[i] = a*X[i] + Y[i] over 64 elements takes 6 instructions. The scalar MIPS loop needs 578.

Past the walls of ILP: data-level parallelism

Lesson 8 ended at the limits of ILP, which the notes group as three walls (p. 387): control (a misprediction costs more the deeper the pipeline), memory (misses and pointer chasing starve the out-of-order window) and complexity/energy (renaming, reservation stations and reorder buffers grow faster than the IPC they buy: "a lot of energy is spent to squeeze out a few more IPCs"). The way past is another kind of parallelism: threads (TLP, lesson 29), independent requests (RLP), or data — this lesson and the next two.

Take the plainest loop there is, Z[i] = X[i] + Y[i] for i = 0 … 63. Iteration 17 never needs the result of iteration 16, so in principle all 64 additions could happen at the same moment. Change one index and the picture flips: in X[i] = X[i] + X[i-1], statement S1 of the July 2024 quiz on loop-carried dependences, every addition waits for the one before.

x₀y₀ x₁y₁ x₂y₂ x₃y₃ x₄y₄ x₅y₅ +++ +++ z₀z₁z₂ z₃z₄z₅ no element waits

Z[i] = X[i] + Y[i]: data-level parallelism

x₀x₁x₂ x₃x₄x₅ +++ +++ x₀′x₁′x₂′ x₃′x₄′x₅′ one at a time

X[i] = X[i] + X[i−1]: a loop-carried chain

Fig. 1. Left: every sum reads its own two elements and nothing connects the columns, so all six — or all 64 — can be computed at once. Right: the loop-carried dependence of the July 2024 quiz, where every addition needs the result of the one before it (orange arrows): no hardware can run these side by side.

Data-level parallelism (DLP) — the same operation applied independently to many data items, so that no element waits for the result of another: "same instruction, multiple data" (p. 387). It is a property of the program; SIMD and vector hardware are ways of exploiting it.

Why bother, when an out-of-order core would also find those 64 independent additions? Because of what finding them costs. A superscalar core fetches, decodes, renames, schedules and commits every single instruction, and that machinery, not the adder, is where most of its energy goes. A SIMD or vector machine "only needs to fetch one instruction per data operation" (p. 387): one instruction says "add these 64 pairs". Hence the claim this lesson has to justify: on such loops an in-order vector processor can match or beat a complex out-of-order core, at lower energy.

DLP is the same operation on many independent elements; a SIMD or vector machine pays for one instruction and gets many element operations, so the front-end cost per element almost vanishes.

Flynn counts streams, not width

Two machines each start four operations in the same clock cycle. The first is a 4-wide superscalar: it reads four instructions from one program counter — a load, a multiply, an add, an index update — and sends each to its own unit. The second is a SIMD array: it reads one instruction, ADD R1, R2, R3, and broadcasts it to four processing elements, each adding its own pair of registers. Both are parallel. Only one of them is SIMD.

PC LD F2, 0(R1) FMUL F6, F4, F0 FADD F8, F8, F10 ADDUI R2, R2, 8 Load unit Multiplier Adder Int ALU one PC: four different instructions one stream

4-wide superscalar: SISD

PC ADD R1, R2, R3 PE0 PE1 PE2 PE3 R2₀+R3₀ R2₁+R3₁ R2₂+R3₂ R2₃+R3₃ one PC: one instruction, four data one stream, four data

SIMD array of 4 PEs: SIMD

Fig. 2. Both machines start four operations per cycle. The superscalar fetches four different instructions from one program counter: one instruction stream, parallelism across instructions. The SIMD array fetches one instruction and broadcasts it to four processing elements, each working on its own registers (the notes' example, p. 390): one instruction stream, four data streams.

In 1966 Michael Flynn classified computers by how many instruction streams and how many data streams they handle at once (p. 388):

Flynn's four classes with the notes' examples (pp. 388–389): rows count instruction streams, columns count data streams.
one data streammany data streams
one instruction streamSISD — a scalar RISC pipeline, an in-order MIPS, and also an out-of-order superscalar: one PC, one streamSIMD — vector processors (Cray, VMIPS), packed-SIMD extensions (MMX, SSE, AVX), GPUs in their warp execution
many instruction streamsMISD — essentially hypothetical; fault-tolerant redundant pipelines are sometimes citedMIMD — multicores, clusters, distributed-memory systems: one PC per thread

The rule that settles the confusing cases is the notes' own: "Flynn's classification is about streams, not width" (p. 388). A superscalar may issue four, six or eight instructions per cycle, but all come from one PC, each a different instruction on its own operands: parallelism across instructions, ILP, still SISD. A SIMD machine fetches one instruction and applies it to many elements, as ADDVV.D V3, V1, V2 computes V3[i] = V1[i] + V2[i] for every i: parallelism across data, DLP. Multithreaded and multicore processors run one stream per thread: MIMD, TLP. Vector processors are the textbook SIMD machine.

The SIMD machine: one PC, many processing elements

A SIMD machine has a single control unit with one program counter: each instruction is fetched once and broadcast to all the processing elements (PEs), which execute it in lockstep, each on its own local data (p. 390). The notes' picture is a classroom: one teacher says "add 5 to your number", and 32 students do it at once, each in their own notebook. A PE is much simpler than a core — a few registers, an ALU or FPU, sometimes a slice of local memory — and has no PC, no branch predictor and no cache: it relies on the controller.

One PC buys three things (p. 391): energy, with one fetch and decode per operation instead of one per PE; compact code, written once for all PEs; and simplicity, because PEs stepping in lockstep never need to synchronise. The price is lockstep: all PEs execute the same instruction, or some are masked off (lesson 31). A MIMD multicore makes the opposite trade: every core has its own PC and can run anything, and synchronisation — locks, barriers — becomes the programmer's problem.

Real machines mix the two (p. 391): a scalar unit runs ordinary SISD code and the SIMD instructions go to the PE array — MIPS plus the vector unit of VMIPS, scalar x86 plus AVX-512. GPUs, with their thousands of threads, are "essentially SIMD under the hood": a warp of threads executes one instruction at a time in lockstep. The notes' Figure 30 (p. 392) draws a vector processor next to a GPU's SIMD processor, which Hennessy and Patterson call a multithreaded SIMD processor; lesson 32 takes the pair apart.

Count instruction streams and data streams, never instructions per cycle: superscalar = SISD (ILP), vector unit and GPU warp = SIMD (DLP), multicore = MIMD (TLP).

Space or time: arrays of PEs and pipelined vector units

"Not all SIMD is created equal" (p. 394). The first way to build it is the SIMD array — Illiac IV, the Connection Machine: the programmer sees an array of PEs with local memories, and each broadcast instruction runs on all of them at once. Such arrays are awkward to program (you think in PEs, local memories and communication) and handle branches badly: they are "of historical interest". The second way is the vector processor of Cray and VMIPS: the programmer sees vector registers, writes one instruction for a whole array, and the hardware streams the elements through deeply pipelined functional units — "classical SIMD, just presented in a more programmer-friendly way" (p. 392).

The difference shows in time. Suppose the add takes 6 cycles. In the array each PE executes the ADD "exactly like a tiny CPU would" and stays "frozen" until it finishes, so "latency is exposed": every operation pays all of it (p. 395). In the vector unit the add is a pipeline: in cycle 1 the first pair of elements enters the adder, in cycle 2 the second pair enters while the first is still inside, and so on. After the 6-cycle start-up one result leaves every cycle: 64 elements take 6 + 64 = 70 cycles, on a "conveyor belt" where "latency is only paid once".

cycle 10 e5 e6 e7 e8 PE0 PE1 PE2 PE3 written: e1–e4 · waiting: e9 to e64 4 results every 6 cycles

In space: 4 PEs, each frozen for 6 cycles

cycle 10 123 456 e10e9e8 e7e6e5 one adder in 6 stages written: e1–e4 next: e5 in cycle 11, e6 in cycle 12 … one result every cycle

In time: 1 adder, 6 elements in flight

Fig. 3. The same 64-element add at cycle 10, with a 6-cycle latency. Left: four unpipelined PEs, each frozen for 6 cycles on its element, deliver 4 results every 6 cycles. Right: one adder cut into six stages holds six elements at once, each a stage further on; after the first 6 cycles it delivers one result every cycle, with a quarter of the hardware. Both have written e1–e4 by now; the array's next four arrive together in cycle 13, and from cycle 15 on the pipeline stays ahead.

Count both machines with the rule of the Tomasulo tables: a result is written \(L\) cycles after its operation starts (\(L\) the latency of the adder, \(n\) the number of elements).

  1. Pipelined unit. Element \(k\) enters the adder in cycle \(k\) and is written \(L\) cycles later (WRITE RESULT = START EXEC + latency):

    \[ t_{\text{write}}(k) = k + L \]
  2. The last element, \(k = n\), is written in cycle \(n + L\):

    \[ T_{\text{vector}} = L + n \]

    With \(L = 6\) and \(n = 64\): 70 cycles, the notes' count.

  3. Array of \(N\) unpipelined PEs. \(N\) elements start together and keep their PEs frozen for \(L\) cycles; the next \(N\) start \(L\) cycles later. Round \(r\) starts and writes in

    \[ t_{\text{start}}(r) = (r-1)L + 1 \] \[ t_{\text{write}}(r) = rL + 1 \]
  4. \(n\) elements need \(\lceil n/N \rceil\) rounds, so the last results are written in cycle

    \[ T_{\text{array}} = L \left\lceil \frac{n}{N} \right\rceil + 1 \]

    With \(L = 6\), \(n = 64\) and \(N = 8\): 6 · 8 + 1 = 49 cycles. With \(N = 1\), a single unpipelined adder: 385. The final 1 is the write cycle that the pipeline's \(L + n\) already contains.

  5. Divide the results by the hardware: the pipelined adder alone delivers 64/70 = 0.91 results per cycle, each of the 8 array adders 64/(8 · 49) = 0.16, about one every \(L\) cycles:

    \[ \frac{n}{L+n} \quad \text{vs} \quad \frac{1}{L} \]

    The pipeline wins on hardware, not on latency: one \(L\)-stage pipelined unit does the work of about \(L\) unpipelined ones, once the vector is long enough to keep it full.

Why can a vector unit be pipelined so deeply? Because of element independence: the element entering the adder never needs the result of an element still inside it, since V3[17] = V1[17] + V2[17] does not read V3[16]. No hazard check is needed between consecutive elements, so the unit can be cut into many short stages and clocked fast (p. 397).

The widget adds a third machine: a scalar loop doing the same additions, the body of the papers' strip-mining quiz, Y[i] = X[i] + Y[i]:

LOOP: LD    F2, 0(R1)      ; X[i]
      LD    F4, 0(R2)      ; Y[i]
      FADD  F4, F4, F2     ; X[i] + Y[i]
      SD    F4, 0(R2)      ; Y[i] ← F4
      ADDUI R1, R1, 8
      ADDUI R2, R2, 8
      BNE   R1, R3, LOOP   ; R3 = end of X

Counted optimistically — one cycle for each of the six other instructions, \(L\) cycles for the FADD because the SD waits for its result, no other stall — every element costs \(L + 6\) cycles. The scalar loop also loads and stores each element, which a single ADDVV.D does not: the fair whole-loop comparison is lesson 31's. Watch how often each machine pays the latency.

The element streamer

Top: the pipelined adder and the array of PEs at the cursor cycle t — each number is the element inside a stage of the adder or inside a PE. Bottom: how many results each machine has written by every cycle. The one thing to see: the pipelined unit pays the latency once per vector and then streams one result per cycle; the array pays it once per round, the scalar loop once per element.

  1. Keep L = 6 and n = 64. Before you look at the readouts: in which cycle does the pipelined adder write its last result, and how many elements are inside it at cycle 30? Then check with the cursor.
    What you should see

    Cycle 70 (6 + 64). At cycle 30 the belt holds elements 25 to 30, one per stage — six in flight — and 24 results are already written. From cycle 7 to cycle 70 the pipeline's curve climbs by exactly one result per cycle.

  2. How many PEs must the array have to keep up with the single pipelined adder? Predict, then find the smallest N with the slider.
    What you should see

    N = 6: 11 rounds, 6 × 11 + 1 = 67 cycles, just under 70; with N = 5 it needs 13 rounds, 79 cycles. Six adders to match one: the readout "results per cycle per adder" shows 0.91 for the pipeline against about 0.16 ≈ 1/6 for an array adder — less when N does not divide 64, because some PEs sit idle in the last round. Pipelining is parallelism in time.

  3. Set N = 1 and tick the scalar loop. How many times does each machine pay the 6-cycle latency, and how long does each take?
    What you should see

    The pipeline once (70 cycles); the one-PE array 64 times, 6 × 64 + 1 = 385 cycles — the notes' "latency × vector length", true only for N = 1; the scalar loop 64 times as well, plus six instructions per element: 64 × 12 = 768 cycles. The plot has to zoom out to show it.

  4. Make the vector short: n = 4, with N = 8. Which machine wins now, and why?
    What you should see

    The array: 7 cycles against 10. The pipeline spends 6 of its 10 cycles filling up and delivers 4 results in 10 cycles, 0.40 per cycle, against 0.91 at n = 64. A pipeline pays off only on long vectors — one reason vector registers are long, and the reason lesson 31's chime count can ignore the start-up.

"A vector unit does its 64 additions at once." It does one per cycle per unit: six or so are in flight at any moment, each at a different stage. Doing 64 at once takes 64 adders; vector machines add parallel copies of a unit as lanes — their PEs — without changing a single instruction (lesson 31).

An array of \(N\) PEs pays the latency once per round of \(N\) elements; a pipelined vector unit pays it once per vector and then streams one result per cycle: one \(L\)-stage pipelined adder does the work of about \(L\) unpipelined ones — on long vectors.

Cray's choices: where the transistors went

Seymour Cray designed the CDC 6600 (1964), then founded Cray Research, whose Cray-1 (1976) was the first commercially successful vector supercomputer (p. 396). Its designers tied its speed to its size: "a cylindrical shape was chosen for the CRAY-1 in order to keep wiring distances small", and smallness "allows a 12.5 nanosecond clock period" (Russell, 1978). The padded bench around the base, which earned it the name "the world's most expensive love-seat", hid the power supplies and some of the Freon plumbing.

When people say "vector processor" they usually mean this Cray-style design, and VMIPS is a simplified version of it (p. 396). Its philosophy, in the notes' words: "don't waste transistors on speculation or caches; spend them on wide datapaths and memory bandwidth" (p. 398). The target is scientific code with long arrays — matrix operations, PDE solvers, simulations — and with no giant instruction window and no speculative logic the result is "much higher throughput per watt than OoO ILP" (p. 398). Five choices follow, each with a reason and a price:

The five design choices of a classic vector machine (pp. 396–398), with the reason each one fits data-level parallelism and what it costs.
ChoiceWhy it fits DLPWhat it costs
1. One instruction, many elements — the vector ISAOne fetch and decode for 64 element operations; no loop overhead, no loop branches; dependences checked once per vector, not per elementHelps only loops with independent iterations; scalar code runs at scalar speed
2. Register-to-register — vector registersEach element is loaded once and reused by several instructions; the pipelines stream from registers, one element per cycle in order, instead of stalling on memory, unlike the memory-to-memory vector machines before the Cray-1 (CDC STAR-100)A large register file with many ports (VMIPS: 4 KB, 16 read and 8 write ports)
3. Deeply pipelined functional unitsElement independence allows deep pipelines with no hazard checks between elements: one result per cycle, start-up amortised over the vectorEvery instruction pays a start-up latency; short vectors waste most of it
4. Interleaved (banked) main memoryConsecutive words sit in consecutive banks, so one word per cycle streams in although each bank is busy for several cycles (Cray-1: 16 banks, 4 clock periods per access)Many banks; strides that keep hitting the same bank collide (lesson 31)
5. Little or no data cacheA vector load states exactly which words it needs, and a stream uses each of them once: a cache would add latency and area for no reuse, while the banks give the bandwidth directlyScalar and irregular code get no cache help; at most a scalar cache serves the scalar side, which the vector side bypasses (p. 397)

Three supporting pieces complete the machine (p. 397), all put to work in lesson 31: vector mask registers, which switch off elements so that loops with an if stay vectorisable; multiple lanes, parallel copies of the units (4 lanes, 4 elements per cycle); and a scalar unit for control flow and scalar data.

A Cray-style machine bets on regularity: one instruction for many elements, vector registers, deep pipelines, interleaved banks — and no data cache, because a stream has nothing to reuse.

VMIPS, and DAXPY in six instructions

VMIPS is not a commercial computer but Hennessy and Patterson's teaching model: the MIPS you know plus "a Cray-style vector coprocessor", simple enough to count cycles by hand, faithful enough that the counts mean something, and a bridge to the SIMD units of today's processors (p. 399). The February 2025 paper prints its block diagram (notes p. 578), redrawn here.

main memory in banks 0123 4567 one word per cycle vector load/store unit vector registers 8 × 64 × 64 bit V0V1V2V3 V4V5V6V7 ………… ………… FP add/sub FP multiply FP divide integer logical all pipelined scalar registers F0F2… R1R2 address scalar operand crossbar 16 read · 8 write ports
Fig. 4. VMIPS, after Hennessy and Patterson's figure reprinted in the February 2025 paper. Eight vector registers of 64 elements of 64 bits feed five fully pipelined functional units through a crossbar with 16 read and 8 write ports (arrows to a unit: operands read; arrows back: results written). One vector load/store unit streams whole vectors between the registers and the interleaved memory banks, one word per cycle after its latency. The scalar registers supply addresses and scalar operands such as the a of DAXPY.

Its three pillars, plus the scalar side (pp. 399–401):

The instructions, in the papers' spelling (Hennessy and Patterson write LV and SV for L.V and S.V):

The VMIPS instructions a lesson or an exam uses. The papers print L.V, S.V, ADDVV.D, ADDVS.D and MULVS.D, with the scalar operand in an F or S register (F1, S1).
InstructionWhat it does
L.V V1, R1 · S.V V1, R1load / store the whole vector V1 at the address in R1 (unit stride)
LVWS V1, (R1, R2) · SVWS (R1, R2), V1load / store with stride R2: every R2-th word
LVI V1, (R1+V2) · SVI (R1+V2), V1gather / scatter: element i at address R1 + V2[i]
ADDVV.D V1, V2, V3 · ADDVS.D V1, V2, F0V1[i] = V2[i] + V3[i] · V1[i] = V2[i] + F0 (vector-vector, vector-scalar)
SUBVV.D SUBVS.D SUBSV.D · MULVV.D MULVS.D · DIVVV.D DIVVS.D DIVSV.Dthe same for subtract, multiply (vector-vector also written MULV.D) and divide; SV puts the scalar first
SNEVS.D V1, F0 and the other compares (EQ, GT, LT, GE, LE; VV or VS)compare element by element and set the vector mask register
MTC1 VLR, R1 · MFC1 R1, VLRwrite / read the vector-length register (lesson 31)

The exam's machines are variations on VMIPS: "32 vectors of 16 elements" in July 2024 and June 2026, of 8 elements in June 2025. Always read the vector length from the question.

DAXPY, instruction by instruction

DAXPY, "Double-precision A·X Plus Y", is the loop of the opening puzzle (p. 402):

\[ Y[i] = a \cdot X[i] + Y[i] \]

A BLAS kernel and the inner loop of the Linpack benchmark that ranks supercomputers, it does one multiply and one add per element, with no dependence between elements, so it isolates the effect of vectorisation. The scalar MIPS loop for 64 elements, in the papers' mnemonics:

        LD    F0, 0(R3)     ; F0 ← a
        ADDUI R4, R1, 512   ; R4 = end of X
LOOP:   LD    F2, 0(R1)     ; X[i]
        FMUL  F2, F2, F0    ; a * X[i]
        LD    F4, 0(R2)     ; Y[i]
        FADD  F4, F4, F2    ; a * X[i] + Y[i]
        SD    F4, 0(R2)     ; Y[i] ← F4
        ADDUI R1, R1, 8     ; next X
        ADDUI R2, R2, 8     ; next Y
        SUB   R5, R4, R1    ; elements left?
        BNE   R5, R0, LOOP  ; R0 is always 0

And on VMIPS, the same work:

        LD      F0, 0(R3)   ; F0 ← a
        L.V     V1, R1      ; V1 ← X[0..63]
        MULVS.D V2, V1, F0  ; V2 ← a * X
        L.V     V3, R2      ; V3 ← Y[0..63]
        ADDVV.D V4, V2, V3  ; V4 ← a * X + Y
        S.V     V4, R2      ; Y ← V4
  1. Set-up, executed once: LD F0 (the scalar a) and ADDUI R4 (the end of X), 2 instructions.

  2. Loop body, executed once per element: 9 instructions. Five do the work — two loads, the multiply, the add, the store — and four only run the loop: two pointer updates, the bound test and the branch.

  3. For 64 elements:

    \[ 2 + 9 \times 64 = 578 \]

    dynamic instructions, the notes' "≈ 578".

  4. VMIPS: the scalar LD F0 plus one vector instruction per operation of the body; no loop, so no pointers, no bound, no branch:

    \[ 578 - 6 = 572 \]
  5. Where the 572 went: the 320 element instructions (5 × 64) became 5 vector instructions, 315 fewer; the 3 × 64 = 192 pointer updates and bound tests vanished, and so did the 64 branches and the bound set-up:

    \[ 315 + 192 + 64 + 1 = 572 \]

Fetch and decode are not all: the scalar pipeline also resolves the four dependences that carry the data through the body — LD to FMUL, FMUL to FADD, LD to FADD, FADD to SD — once per element, 256 times; VMIPS resolves each of them once. The widget counts both.

Where the 572 instructions go

Each row is one instruction of the scalar loop body; its bar grows by one cell per element as the loop runs, 9 rows by 64 elements. Under it, the whole VMIPS program at the same scale. The one thing to see: the scalar machine fetches, decodes and checks one instruction per element operation, the vector machine one per data operation.

  1. Before you press "Run the loop": how many instructions will MIPS fetch and decode for the 64 elements, and how many VMIPS?
    What you should see

    2 + 9 × 64 = 578 for MIPS, 6 for VMIPS: 96 times fewer. The counter climbs by 9 per iteration while the VMIPS row never grows.

  2. Step ten iterations with "One iteration". What share of the fetched instructions only runs the loop? Does the share change as the loop goes on?
    What you should see

    41 of 92, 45 %; at the end 257 of 578, 44 %. The share hardly moves, because the overhead is paid per element: four of every nine instructions do no arithmetic and move no data.

  3. Let the loop finish. Of the 572 saved instructions, is the bigger part the loop overhead or the element instructions? Could complete unrolling give you the same saving?
    What you should see

    The element instructions: 315, against 192 + 64 + 1 = 257 of overhead. Complete unrolling removes the 257 at most and leaves 1 + 5 × 64 = 321 instructions; only a vector instruction does 64 element operations with one fetch and one decode.

  4. How many times does the scalar pipeline resolve the dependence of FADD on FMUL, and how many times does VMIPS resolve that of ADDVV.D on MULVS.D?
    What you should see

    64 against 1: once per element against once per vector. Over the four dependences of the body the readout ends at 256 against 4 — a scalar pipeline checks, and may stall, on every element.

How long the six instructions take is the subject of lesson 31.

The opening puzzle, solved

Back to 1976. A cache pays off when data are reused or when the hardware has to guess what comes next, and a vector loop offers neither: a vector load states exactly which 64 words it wants, and DAXPY uses each once. A stream needs bandwidth and a way to pay latency once, and that is where the Cray-1's transistors went: sixteen banks, each busy for 4 clock periods, delivering one word per clock period (Russell, 1978); pipelined units that pay their latency once per 64 elements; and 4 KB of vector registers, the buffer a cache would have been, filled by the compiler, so nothing misses. The 578 against 6 is the same decision seen from the instruction side.

VMIPS is MIPS plus 8 vector registers of 64 × 64-bit elements (16 read, 8 write ports), five pipelined units and one pipelined load/store unit over interleaved banks; on it, DAXPY's 578 scalar instructions become 6.

Exam-style questions

The February 2025 paper asked an open question on the VMIPS figure of section 5: present its main concepts, then the advantages of vector over scalar execution, each with the feature that provides it. Its model answer is in lesson 31, once convoys and chaining are on the table.

Explain the main concepts and benefits of each one of the following topics of SIMD and vector architectures: 1. Data-level parallelism; 2. Flynn's taxonomy and the class of a superscalar processor; 3. SIMD execution with a single program counter; 4. Array processors vs pipelined vector processors; 5. Vector register file; 6. Deeply pipelined vector functional units; 7. Interleaved memory banks; 8. Little or no data cache.

Show model answer

1. Data-level parallelism. The same operation applied independently to many data items, as in Z[i] = X[i] + Y[i]: no element waits for another. It is a property of the program, exploited by SIMD and vector hardware with one instruction per data operation instead of one per element; the fetch, decode and issue cost per element is divided by the vector length, so it costs far less energy than extracting the same parallelism as ILP.

2. Flynn's taxonomy and the superscalar. Flynn classifies machines by the number of instruction streams and data streams. A superscalar fetches several different instructions per cycle from one program counter: one instruction stream, each instruction on its own operands, so it is SISD and exploits ILP. The benefit of the classification is to separate ILP (more instructions per cycle from one stream), DLP (one instruction on many data, SIMD) and TLP (many streams, MIMD): Flynn counts streams, not width.

3. SIMD with a single PC. One control unit fetches each instruction once and broadcasts it to many processing elements, which execute it in lockstep on their own data. Benefits: one fetch and decode per operation instead of per PE (energy), compact code, no synchronisation between PEs. Drawback: divergent control flow must be handled by masking off elements.

4. Array vs vector processors. An array processor has N processing elements working side by side; an operation with latency L keeps them frozen, so it takes L × ⌈n/N⌉ cycles and the latency is exposed on every round, and the programmer thinks in PEs and local memories. A vector processor pipelines its units: the latency is paid once and then one result per cycle, L + n cycles (6 + 64 = 70), and the programmer thinks in vector registers. On long vectors one L-stage pipelined unit does the work of about L unpipelined ones.

5. Vector register file. Registers holding whole vectors (VMIPS: 8 registers of 64 × 64-bit elements, 16 read and 8 write ports through a crossbar), used register-to-register: each element is loaded once and reused by several instructions, and the pipelines are fed from registers without stalling on memory. It is compiler-controlled, unlike a cache: no tags, no misses.

6. Deeply pipelined functional units. Each unit accepts a new element every cycle. Element independence means no hazard checks between elements, so the pipelines can be deep and fast, and the start-up latency is amortised over the whole vector: about one result per cycle per unit. Cost: every instruction pays its start-up, so short vectors are inefficient.

7. Interleaved memory banks. Consecutive words are placed in consecutive banks; each bank is busy for several cycles after an access, but together they deliver one word per cycle to the vector load/store unit (Cray-1: 16 banks, 4-cycle bank busy time). Benefit: the bandwidth needed by streaming loads and stores; cost: many banks, and conflicts when a stride keeps hitting the same bank.

8. Little or no data cache. Vector accesses are predictable — the instruction states which words it needs — and streaming kernels use each element once, so a cache would add latency and area for little reuse. Bandwidth comes from the banks, latency is amortised by pipelining and buffered in the vector registers; a small scalar cache may serve the scalar side, which the vector side bypasses.

The 22 June 2026 paper asks its 8-point question in exactly this form, there on Tomasulo-style techniques, with five lines per item. For each item write the mechanism in one sentence, then the benefit, then a cost or a condition. Items 2 and 4 carry the two traps of the topic: "superscalar = SIMD" and the notes' "latency × vector length" for arrays.

Let's consider Flynn's taxonomy. In which class is an out-of-order superscalar processor that issues up to 4 instructions per clock cycle from a single thread? (Single answer.)

  • SISD One program counter, one instruction stream: the four instructions of a cycle are different instructions of the same stream. Parallelism across instructions is ILP, not a second stream.
  • SIMD SIMD is one instruction applied to many data elements; the superscalar issues several different instructions, each on its own operands.
  • MISD MISD would need several instruction streams on a single data stream; it is essentially hypothetical.
  • MIMD MIMD needs several instruction streams, one PC per thread or core; a single core running one thread has one.
Show model answer

Answer 1: SISD. Flynn's classification counts instruction and data streams, not issue width: a superscalar fetches all its instructions from one program counter.

The notes file it under "Practical clarification" (p. 388) because it is the classic confusion. A good motivation line: "streams, not width; superscalar = ILP within one stream".

True or false: in a vector processor with fully pipelined functional units, the latency of a functional unit is paid once for each element of the vector.

  • True That describes a single unpipelined unit (the array with N = 1) or a scalar loop: L × n. In a pipelined unit the elements overlap, L + n cycles.
  • False The latency (start-up) is paid once per vector instruction; after it, one element completes every cycle: 6 + 64 = 70 cycles for 64 elements with a 6-cycle adder.
Show model answer

False. The latency is paid once per vector instruction (the start-up); then the pipeline delivers one result per clock cycle, so n elements take about L + n cycles, not L × n.

The True/False form of the June 2025 paper's GPU quiz, on the idea behind every vector timing in lesson 31. If asked to motivate, give both counts with numbers.

Which features allow a classic (Cray-style) vector processor to keep its functional units busy with little or no data cache? (Multiple answers.)

  • Interleaved memory banks that together deliver one word per clock cycle Each bank is busy for several cycles, but consecutive words sit in different banks: bandwidth without a cache.
  • Vector registers loaded and reused under compiler control They buffer whole vectors next to the units; the compiler decides what is loaded, so nothing misses.
  • Pipelined load/store and arithmetic units that pay the latency once per vector Memory latency is paid once per vector load, then one word per cycle: it is amortised, not hidden by a cache.
  • A large branch target buffer that predicts the next memory address A BTB predicts branch targets, not data addresses; a vector loop has no per-element branches to predict anyway.
  • The high temporal reuse of the vector elements Streaming kernels use each element once; it is the lack of reuse that makes a cache useless here.
Show model answer

Answers 1, 2 and 3. Vector accesses are predictable and stream each element once: interleaved banks supply the bandwidth, pipelined units pay the latency once per vector, and the vector registers act as a compiler-managed buffer.

The last option is the trap: caches live on reuse, and the reason vector machines drop them is that streams have none. Choosing it means confusing locality with data-level parallelism — the slip of the notes on p. 387.

Let's consider a vector processor whose FP add unit is fully pipelined with a start-up latency of 6 clock cycles. How many clock cycles does ADDVV.D V3, V1, V2 take on vectors of 64 elements, counting the start-up and then one result per cycle?

Show model answer
\[ T = \text{start-up} + n = 6 + 64 = 70 \ \text{cycles} \]

For comparison: one unpipelined adder would need 6 × 64 = 384 cycles of computation; an array of 64 unpipelined PEs about 6, with 64 adders instead of one.

The papers' convoy quizzes ignore the start-up (cycles = convoys × vector length, lesson 31); a question about one instruction with a given start-up counts it. Read which of the two the question wants.

Let's consider DAXPY on 64 elements. The scalar MIPS code has 2 set-up instructions and a loop body of 9 instructions executed once per element; the VMIPS code has 6 instructions. By what factor does vectorisation reduce the number of executed instructions?

Show model answer
\[ IC_{\text{MIPS}} = 2 + 9 \times 64 = 578 \] \[ \frac{IC_{\text{MIPS}}}{IC_{\text{VMIPS}}} = \frac{578}{6} = 96.3 \]

Write the instruction count before dividing: 9 × 64 = 576 forgets the set-up. If the question asks where the difference goes, 572 = 315 element instructions + 192 pointer and bound updates + 64 branches + 1 set-up.

Check yourself

Which of these are SIMD in Flynn's sense? Select all that apply.

  • A vector unit executing ADDVV.D V3, V1, V2 on 64 elements One instruction, 64 element operations: one instruction stream, many data streams.
  • A GPU warp of 32 threads executing one instruction in lockstep Within a warp one instruction is broadcast to all lanes: "essentially SIMD under the hood" (p. 391).
  • A 4-issue out-of-order superscalar running one program Four different instructions per cycle from one PC: one stream, ILP — SISD.
  • A quad-core processor running four threads Four PCs, four instruction streams: MIMD.

Ask two questions: how many PCs, and does one instruction touch one element or many?

An array of 64 unpipelined PEs with a 6-cycle add finishes a 64-element add in 7 cycles; a single 6-stage pipelined adder needs 70. Why did vector machines choose the pipeline?

  • The pipelined adder keeps all its stages busy and delivers about one result per cycle, while each array adder delivers one result every 6 or 7 cycles and waits in between Per adder: 0.91 results per cycle against 0.14. On long vectors one pipelined adder does what the array needs about six adders for.
  • The array cannot add 64 numbers in parallel It can: that is exactly what its 64 PEs do, in 7 cycles.
  • The pipelined adder has a shorter latency Both need 6 cycles for the first result; the pipeline wins on throughput per unit of hardware, not on latency.
  • A pipelined adder needs no vector registers It needs them most of all: the elements stream out of and into vector registers.

Why can a vector add unit be cut into many short pipeline stages without the hazard-detection logic of a scalar pipeline?

  • The element entering the unit never needs the result of an element still inside it Element independence: V3[17] does not depend on V3[16], so there is nothing to check between consecutive elements.
  • The compiler inserts NOPs between the elements There are no instructions between elements: one instruction covers all of them.
  • Vector registers have more ports than scalar registers Ports let several units stream at once; they do not remove dependences.
  • The unit stalls for one cycle after every element Then it would deliver one result every two cycles; a vector unit delivers one per cycle.

VMIPS keeps 4 KB of vector registers next to its functional units. What makes them different from a 4 KB data cache?

  • The compiler decides what goes into them and when, with explicit L.V and S.V; there are no tags and no misses A compiler-controlled buffer (p. 400): like scalar registers, only 64 elements wide.
  • Nothing but the name: both are filled by hardware on demand A cache is filled by hardware when an access misses; a vector register only by an explicit vector load.
  • They keep the most recently used data and replace the rest by LRU There is no replacement policy: a register keeps its contents until an instruction overwrites it.
  • They are slower than a cache but hold more data They are faster — a register file next to the units — and here the same size.

An out-of-order superscalar and a vector processor both perform DAXPY's 64 multiplications and 64 additions, with floating-point units of similar cost. Where does the vector machine save most of its energy?

  • In the front end and the scheduling logic: one fetch, decode and dependence check per vector instead of per element, and no renaming or reorder buffer The arithmetic is the same; what disappears is the per-instruction machinery: "one instruction controls many ALUs, so fetch/decode overhead is amortized" (p. 387).
  • In the adders and multipliers, because vector units are smaller They do the same arithmetic; a pipelined vector unit is not cheaper per operation.
  • In the data cache, because the vector machine hits more often A classic vector machine has little or no data cache to hit in; the saving is in the front end.
  • In memory traffic, because a vector load reads fewer words Both read every X[i] and Y[i] once: the same words, only delivered as a stream.

Why does the VMIPS vector register file have 16 read ports and 8 write ports?

  • So that several functional units and the load/store unit can stream elements out of and into different registers in the same cycle Five units reading two elements and writing one, plus the load/store unit: up to 11 reads and 6 writes per cycle.
  • One port for every four elements of a 64-element register Ports serve units, not elements: a unit streams its elements through its port one per cycle.
  • To replace the data cache Ports give bandwidth between registers and units; what replaces the cache is the vector registers as a buffer plus the memory banks.
  • Because VMIPS issues 16 instructions per cycle VMIPS issues at most one vector instruction per cycle; the ports serve the units of the instructions already running.

Key take-aways

What to watch and read