5.3.13 · Hardware › Advanced Microarchitecture
Intuition Core Idea: Compiler Ko Kaam Karne Do
Traditional superscalar processors bahut saara die area aur power complex hardware logic mein lagaate hain taaki pata chale kaunsi instructions parallel chal sakti hain. VLIW (Very Long Instruction Word) isko ulta karta hai: compiler compile time pe dependencies analyze karta hai aur independent operations ko ek single wide instruction mein pack karta hai. Hardware simple ho jaata hai—woh bas execute karta hai jo compiler ne prepare kiya, koi runtime dependency checking nahi chahiye.
Isse meal prep ki tarah socho: ek chef (hardware) jo multiple cooks ko real-time mein frantically coordinate kare, uski jagah tum pehle se complete "meal kits" (VLIW instructions) prepare kar lete ho jisme sab kuch pre-sorted aur simultaneously cook karne ke liye ready hota hai.
Definition VLIW Architecture
Ek VLIW (Very Long Instruction Word) architecture fixed-length instruction words use karta hai jisme multiple independent operations hote hain jo multiple functional units pe parallel execution ke liye intended hote hain. Har instruction word explicitly specify karta hai ki ek given cycle mein har functional unit ko kya karna chahiye.
Key characteristics:
Static scheduling : Instruction parallelism hardware nahi, compiler determine karta hai
Explicit parallelism : Har instruction word mein different functional units ke liye operation slots hote hain
No interlocks : Hardware assume karta hai ki compiler ne dependencies sahi se handle ki hain
Lock-step execution : Saare functional units har cycle mein saath-saath aage badhte hain
Traditional superscalar processors complex hardware logic use karte hain:
Out-of-order execution engines jo runtime pe parallelism dhundhe
Register renaming logic jo false dependencies eliminate kare
Reservation stations aur reorder buffers jo in-flight instructions track kare
Wakeup/select logic jo significant power aur area consume kare
VLIW kehta hai: "Kya ho agar hum yeh saari complexity compiler mein shift kar dein?"
YEH trade-off kyun sense karta hai:
Compile-time analysis ke paas unlimited time aur global code visibility hoti hai
Hardware complexity issue width ke saath super-linearly badhti hai (bahut saari structures ke liye O(n²))
Dynamic scheduling hardware ki power consumption substantial hoti hai
Simple hardware → lower power, higher clock speeds, aasaan verification
Fixed slots kyun? Hardware operations ko functional units tak simple combinational logic se decode aur route kar sakta hai—koi complex dependency matrices ya dynamic dispatch logic ki zaroorat nahi.
Agar hamare paas ek VLIW machine hai jisme n operation slots per instruction hain:
Best case (full parallelism): n operations per instruction word
Worst case (no parallelism): 1 real operation + ( n − 1 ) NOPs per instruction word
Sequential RISC ke comparison mein code density efficiency :
Efficiency = Instruction bits transmitted Operations executed = n k × b VLIW b RISC
Jahan:
k = average operations per VLIW instruction (1 ≤ k ≤ n)
b RISC = bits per RISC instruction (~32 bits)
b VLIW = bits per VLIW instruction (n × b op , typically 128-512 bits)
Yeh kyun matter karta hai: Agar parallelism kam hai, toh tum NOPs ka code size cost toh pay karte ho lekin sequential performance milti hai. VLIW ka success poori tarah compiler ki parallelism dhundhne ki ability pe depend karta hai.
Worked example Example: Ek 4-Issue VLIW Machine (1 memory unit, 2 integer ALUs, 1 branch/FP)
Upar define ki gayi concrete 4-slot machine use karte hue: ek instruction mein zyada se zyada ek memory operation kyunki ek single load/store unit hai.
High-parallelism loop (array addition):
for ( int i = 0 ; i < 100 ; i ++ ) {
c [i] = a [i] + b [i];
}
Compiled VLIW code (steady state, software-pipelined across iterations):
Kyunki sirf EK memory op per cycle issue hota hai, loads aur stores multiple cycles mein spread hote hain. Do integer ALUs hamare liye address/add kaam overlap karne dete hain jabki single memory port data stream karta hai.
Slot layout: [ ALU1 | ALU2 | MEMORY (1 port) | BR/FP ]
Instr 1: [ NOP | NOP | LD r1,a[i] | NOP ]
Instr 2: [ NOP | NOP | LD r2,b[i] | NOP ]
Instr 3: [ ADD r5,r1,r2| NOP | LD r3,a[i+1] | NOP ]
Instr 4: [ NOP | NOP | ST c[i],r5 | NOP ]
Instr 5: [ NOP | NOP | LD r4,b[i+1] | NOP ]
Instr 6: [ ADD r6,r3,r4| NOP | LD r7,a[i+2] | NOP ]
Instr 7: [ NOP | NOP | ST c[i+1],r6 | NOP ]
Har step kyun:
Single memory port bottleneck hai: har load/store ko alag instruction mein jaana padta hai.
Do integer ALU slots compute ko memory stream ke saath overlap karne dete hain, isliye ADDs memory traffic ke "neeche chhupp" jaate hain.
Yeh example memory-bound hai: ALU slots mostly NOP hain kyunki memory operations ke relative itna compute fill karne ke liye simply nahi hai.
Lesson: VLIW hardware ke resource limits se zyada nahi ja sakta. Ek memory port ke saath, is loop ki throughput roughly ek memory op per cycle tak limited hai chahe kitne bhi ALU slots ho.
Low-parallelism code (dependent chain):
int fib = 1 , prev = 1 ;
for ( int i = 0 ; i < 10 ; i ++ ) {
int temp = fib;
fib = fib + prev;
prev = temp;
}
Compiled VLIW code (one iteration):
Instr 1: [ MOV r3,r1 | NOP | NOP | NOP ] // temp = fib
Instr 2: [ ADD r1,r1,r2 | NOP | NOP | NOP ] // fib = fib + prev (depends on r1)
Instr 3: [ MOV r2,r3 | NOP | NOP | NOP ] // prev = temp
Efficiency: 3 real operations / 12 slots = 25% slot utilization. Bahut bura!
Difference kyun? Doosre example mein true data dependencies hain jo parallel execution rokti hain. VLIW parallelism create nahi kar sakta; woh sirf jo exist karta hai use exploit kar sakta hai.
VLIW compiler ko sophisticated analysis perform karni padti hai:
Data dependencies:
RAW (Read After Write): True dependency, order preserve karna zaroori hai
WAR (Write After Read): Anti-dependency, register renaming se eliminate ho sakti hai
WAW (Write After Write): Output dependency, register renaming se eliminate ho sakti hai
Compiler yeh kyun handle karta hai: Superscalar processors mein, hardware register renaming WAR/WAW eliminate karta hai. VLIW mein, compiler ko scheduling ke dauran khud registers rename karne padte hain .
VLIW loop performance ke liye sabse powerful technique:
Traditional loop execution (sequential):
Iteration 1: [Load A] → [Compute] → [Store]
Iteration 2: [Load A] → [Compute] → [Store]
Iteration 3: [Load A] → ...
Software pipelined (overlapped):
Prologue: [Load A for iter 1]
Steady: [Load A for iter 2] [Compute iter 1] [Store iter 0]
[Load A for iter 3] [Compute iter 2] [Store iter 1]
[Load A for iter 4] [Compute iter 3] [Store iter 2]
Epilogue: [Compute iter n] [Store iter n-1]
[Store iter n]
Yeh kyun kaam karta hai: Alag iterations ek hi instruction cycle ke andar alag pipeline stages occupy karti hain, functional unit utilization maximize hoti hai.
Worked example Software Pipelining Example (1-memory-port, 4-slot machine ko respect karte hue)
Original loop:
for (i = 0 ; i < 100 ; i ++ ) {
x = a [i] * b [i]; // 4-cycle multiply on ALU2
c [i] = x + d [i]; // 1-cycle add on ALU1
}
II ke liye resource accounting: Per iteration hume 3 memory ops chahiye (LD a, LD b, LD d, ST c = actually 4 memory ops), 1 multiply, 1 add.
Per iteration memory ops needed: LD a, LD b, LD d, ST c = 4 memory ops , ek memory port → ResMII = ⌈ 4/1 ⌉ = 4 .
Koi loop-carried recurrence nahi hai (har c [ i ] independent hai), isliye RecMII = 1 .
Isliye II m i n = max ( 4 , 1 ) = 4 . Single memory port II = 4 force karta hai , II = 1 nahi.
Software-pipelined steady state (II = 4 cycles per iteration):
Slot layout: [ ALU1(add) | ALU2(mul) | MEMORY (1 port) | BR/FP ]
Cycle 1: [ ADD c-stage(i-1)| MUL x[i],a[i],b[i] | LD a[i+1] | NOP ]
Cycle 2: [ NOP | NOP | LD b[i+1] | NOP ]
Cycle 3: [ NOP | NOP | LD d[i+1] | NOP ]
Cycle 4: [ NOP | NOP | ST c[i-1] | NOP ]
Har iteration 4 cycles consume karti hai kyunki memory port (multiply latency nahi) binding constraint hai. 4-cycle multiply latency is 4-cycle window ke andar fully hide ho jaati hai.
Step-by-step kyun:
Cycle 1: Is iteration ka multiply shuru karo AUR pichli iteration ka add finish karo, jabki memory port agla a load karta hai.
Cycles 2–3: Memory port b aur d stream karta hai; ALUs idle hain kyunki multiply abhi bhi in flight hai (latency 4) aur koi add ready nahi hai.
Cycle 4: Memory port ek completed result store karta hai.
Note: kisi bhi slot mein kabhi do operations nahi hote , aur per cycle zyada se zyada ek memory op , defined issue width ko honor karte hue.
Total cycles (approx): prologue (~4) + 100 × 4 (steady) + epilogue (~4) ≈ 408 cycles.
Contrast — agar machine mein 2 memory ports hote (ek alag design), toh ResMII = ⌈ 4/2 ⌉ = 2 , loop time half ho jaata. Yeh dikhata hai ki VLIW performance hardware resource counts se dictate hoti hai, jise compiler ko exactly respect karna hota hai.
VLIW architectures branches eliminate karne ke liye heavily predication (conditional execution) use karte hain:
Iske bajaaye:
if (x > 0 )
a = b + c;
else
a = b - c;
VLIW with predication:
cmp.gt p1,p2 = x,0 // p1 = (x>0), p2 = !(x>0)
(p1) add a = b, c // execute if p1 is true
(p2) sub a = b, c // execute if p2 is true
Predication VLIW ke liye kyun critical hai:
Branches software pipelining rokti hain (conditional boundaries ke across iterations overlap nahi ho sakti)
Branch mispredictions expensive hain (koi speculative execution hardware nahi)
Predication control dependencies ko data dependencies mein convert karta hai, zyada parallelism enable karta hai
Aspect
VLIW
Superscalar
Parallelism discovery
Compile-time (static)
Run-time (dynamic)
Hardware complexity
Kam (simple dispatch)
Zyada (OoO engines)
Power efficiency
Better (no speculation)
Worse (speculation overhead)
Code portability
Poor (ISA tied to # units)
Good (HW adapts)
Code size
Bada (NOPs when low ILP)
Chhota (dense encoding)
Performance on irregular code
Poor
Better (dynamic adaptation)
Compiler burden
Extreme
Moderate
VLIW general-purpose CPUs mein kyun struggle kiya:
"VLIW dilemma ": Future performance ke liye zyada functional units add karne ke liye ISA change karna padta hai (zyada operation slots), backward compatibility break hoti hai. Intel Itanium ne explicit parallelism bits aur predication se isko solve karne ki koshish ki, lekin compiler technology general-purpose code mein consistently enough ILP nahi dhundh paaya.
VLIW kahaan succeed kiya:
DSPs (Texas Instruments C6x): Signal processing mein abundant data parallelism hoti hai
Embedded media processors : Jahan code ek specific processor ke liye ek baar compile hota hai
Kuch GPU shader cores (historical, jaise AMD TeraScale VLIW5/VLIW4) : In early GPUs ne genuinely VLIW bundles mein independent operations pack kiye. Note: modern GPUs largely VLIW se door ja chuke hain aur scalar SIMT designs mein shift ho gaye hain (neeche mistake dekho).
Common mistake Common Misconception: "VLIW Always Means More Performance"
Galat intuition: "Ek instruction mein zyada operations = faster programs!"
Kyun sahi lagta hai: Parallelism throughput toh increase karta hai jab exist karta hai.
Reality: VLIW sirf tab help karta hai jab compiler independent operations dhundh sake aur hardware ke paas unhe execute karne ke resources (ports, units) ho. Limited ILP wale code (linked lists, pointer chasing, recursion) ya resource bottlenecks (ek memory port) pe, VLIW instruction bits NOPs pe waste karta hai jabki near-sequentially perform karta hai.
Fix: VLIW ek domain-specific optimization hai. Yeh excel karta hai:
Predictable memory access wale regular loops (arrays, matrix operations)
High arithmetic intensity wale computations
Unpredictable branches ke bina code
Yeh struggle karta hai:
Pointer-heavy data structures
Bahut saare unpredictable branches wale code
Poor locality ya limited memory ports wale workloads
Quantitative example: Intel Itanium (IA-64) 6-wide VLIW tha lekin SPECint pe often <2 IPC achieve karta tha (irregular code wale integer benchmarks), jabki 4-wide superscalar processors dynamic scheduling ke through 2-3 IPC achieve karte the.
Common mistake Misconception: "Modern GPUs VLIW machines hain"
Galat idea: "GPUs wide instructions issue karte hain, toh woh Itanium ki tarah static scheduling ke saath VLIW hone chahiye."
Kyun sahi lagta hai: GPUs do massive parallelism exploit karte hain, aur kuch historical GPUs (AMD TeraScale VLIW5/VLIW4, ~2007–2012) genuinely VLIW bundles use karte the.
Reality: Contemporary GPUs (NVIDIA since Fermi, AMD GCN/RDNA) SIMT (Single Instruction, Multiple Threads) use karte hain hardware-driven, dynamic warp/wavefront scheduling ke saath. Parallelism bahut saare independent threads ko lock-step lanes mein run karne se aati hai, naki ek compiler se jo independent operations ko ek wide instruction word mein pack kare. Har cycle mein kaunsa warp chalta hai yeh ek runtime hardware decision hai.
Fix: Do alag ideas distinguish karo:
VLIW: compiler statically independent operations ko ek instruction mein pack karta hai; hardware scheduling trivial hai.
SIMT: hardware dynamically lanes mein same scalar instruction execute karne wale bahut saare threads schedule karta hai.
Yeh same mechanism nahi hain, chahe dono high parallelism achieve karte hain.
Common mistake Code Compatibility Ko Galat Samajhna
Galat idea: "Main purane VLIW binaries naye wider VLIW processor pe run kar sakta hoon."
Kyun plausible lagta hai: Hum x86 backward compatibility ke aadi hain jo decades span karta hai.
Problem: Ek 4-issue machine ke liye compile kiya gaya VLIW binary har instruction mein 4 operation slots rakhta hai. Agar tumhare paas naya 8-issue machine hai, tum simply "zyada slots use" nahi kar sakte—instruction format fundamentally alag hai.
Fix: Ya toh:
Sab kuch naye machine ke liye recompile karo (DSP approach)
Explicit parallelism encoding add karo jo functional unit assignment se alag ho (IA-64 approach with bundles aur templates)
"Compatibility mode" mein run karna accept karo sirf purani number of units use karte hue
Isliye VLIW ne general-purpose computing mein kabhi dominance nahi ki—ISA evolution problem unsolved hai.
Socho:
for (i = 0 ; i < N; i ++ ) {
a [i] = b [i] + c [i];
d [i] = e [i] * f [i];
}
Compiler do independent statements dekhta hai aur parallel execute karna chahta hai. Lekin kya ho agar a aur e memory mein overlap karein? a[i] ko store karna e[i] se load ko affect kar sakta hai.
Superscalar mein: Hardware runtime pe memory disambiguation perform karta hai, dynamically aliasing detect karta hai.
VLIW mein: Compiler ko ya toh:
Static analysis ke through independence prove karna hoga (pointers ke saath often impossible)
Runtime checks insert karne honge (simplicity advantage khatam)
Independence assume karni hogi aur incorrect execution ka risk lena hoga
Yeh VLIW ko kyun limit karta hai: Memory aliasing general mein undecidable hai. Conservative assumptions parallelism khatam karte hain; aggressive assumptions bugs ka risk rakhte hain.
Recall Feynman Explan
Multiple functional units
Simpler HW, lower power, higher clock