এই পর্বে যা শিখবেন
- লন্ড্রির দোকান আর গার্মেন্টস লাইনের উদাহরণে pipelining, আর latency বনাম throughput-এর পার্থক্য
- RISC-V-এর পাঁচ ধাপ — কোন instruction কোন ধাপে কী করে — আর pipeline register কীভাবে ডেটা ও control signal এগিয়ে নেয়
- Pipeline diagram আঁকা, (k + n − 1) সূত্র আর speedup-এর সীমা k-এর প্রমাণ
- অসম ধাপ আর register overhead-সহ পরীক্ষার ধাঁচের পূর্ণ numerical; কোন ধাপ ভাগ করলে লাভ
- কেন RISC ISA pipeline-বান্ধব, hazard-এর তিন ধরনের পরিচয়, আর pipeline কত গভীর হওয়া উচিত
১ · Pipelining-এর মূল ভাবনা — লন্ড্রির দোকান আর গার্মেন্টসের লাইন
পর্ব ৩২-এ (Single-cycle datapath) একটি প্রসেসর বানানো হয়েছে, যেখানে প্রতিটি instruction এক লম্বা clock cycle-এ শুরু থেকে শেষ হয়। পর্ব ৩৪-এ (Multi-cycle datapath) একই কাজ কয়েকটি ছোট cycle-এ ভাগ করা হয়েছে। দুই নকশাতেই একটি মিল আছে: এক সময়ে মাত্র একটি instruction. Instruction যখন ALU-তে, তখন instruction memory বসে থাকে; যখন memory-তে, তখন ALU বসে থাকে। এই পর্বের প্রশ্ন: সব অংশকে একসাথে ব্যস্ত রাখা যায় কীভাবে?
মহল্লার একটি লন্ড্রির দোকান ভাবুন। কাপড়ের প্রতিটি বস্তায় চারটি কাজ: ধোয়া (wash), শুকানো (dry), ইস্ত্রি (iron) আর ভাঁজ করে প্যাকেট (fold) — প্রতিটিতে ৩০ মিনিট, আর প্রতিটি কাজের আলাদা যন্ত্র বা আলাদা কর্মী। অলস দোকানি একটি বস্তার চারটি কাজ শেষ করে তবেই পরের বস্তা ধরেন। বুদ্ধিমান দোকানি প্রথম বস্তাটি washing machine থেকে dryer-এ দিয়েই দ্বিতীয় বস্তাটি washing machine-এ ঢোকান। গার্মেন্টস কারখানার sewing line-ও এভাবে চলে: একজন কলার লাগান, পাশের জন হাতা, তার পরের জন বোতাম — প্রত্যেকে প্রতি কয়েক মিনিটে একটি শার্টের নিজের অংশটুকু করে পাশে ঠেলে দেন। একটি শার্ট বানাতে মোট সময় কমেনি, কিন্তু লাইনের শেষ মাথা থেকে প্রতি কয়েক মিনিটে একটি করে শার্ট বেরোয়।
এটিই pipelining: কাজকে কয়েকটি ধাপে (stage) ভাগ করা, আর ভিন্ন ভিন্ন কাজের ভিন্ন ধাপ একই সময়ে চালানো। নিচের stepper-এ চারটি বস্তা (A, B, C, D) আর চার ধাপ — W = ধোয়া, D = শুকানো, I = ইস্ত্রি, F = ভাঁজ; প্রতিটি ঘর ৩০ মিনিটের একটি সময়-খণ্ড।
load 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 A W D I F . . . . . . . . . . . . one load: 4 slots x 30 min = 2 hours (latency)
load 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
A W D I F . . . . . . . . . . . .
B . . . . W D I F . . . . . . . .
C . . . . . . . . W D I F . . . .
D . . . . . . . . . . . . W D I F
4 loads x 2 hours = 16 slots = 8 hours
each machine is idle 3 slots out of every 4
load 1 2 3 A W D I B . W D C . . W D . . . slot 2: A in dryer, B in washer (2 machines busy) slot 3: A, B, C all moving (3 machines busy)
load 1 2 3 4 A W D I F B . W D I C . . W D D . . . W slot 4: all 4 machines busy, A finishes at the end of slot 4
load 1 2 3 4 5 6 7
A W D I F . . .
B . W D I F . .
C . . W D I F .
D . . . W D I F
total = 4 + 4 - 1 = 7 slots = 3.5 hours
speedup = 8 / 3.5 = 2.29 (not 4!)
each load still takes 2 hours; one load leaves every 30 min
- একটি বস্তা। চার ধাপ × ৩০ মিনিট = ২ ঘণ্টা। একটি বস্তা দোকানে ঢোকা থেকে প্যাকেট হয়ে বেরোনো পর্যন্ত এই সময়টিই তার latency. Pipeline করলেও এটি কমবে না।
- একটির পর একটি। চারটি বস্তা পরপর: ৪ × ২ = ৮ ঘণ্টা (১৬টি খণ্ড)। প্রতিটি যন্ত্র প্রতি চার খণ্ডের তিনটিতে বসে থাকে — washing machine চলে শুধু ১, ৫, ৯, ১৩ নম্বর খণ্ডে।
- Pipeline ভরে উঠছে। খণ্ড ১-এ শুধু A ধোয়া হয়; খণ্ড ২-এ A dryer-এ আর B washing machine-এ; খণ্ড ৩-এ তিনটি যন্ত্র ব্যস্ত। শুরুর এই সময়টিকে বলে pipeline ভরা (fill) — এখনো কোনো বস্তা শেষ হয়নি।
- Pipeline পূর্ণ। খণ্ড ৪-এ চারটি যন্ত্রই একসাথে চলছে — A ভাঁজ, B ইস্ত্রি, C শুকানো, D ধোয়া। খণ্ড ৪-এর শেষে প্রথম বস্তা A বেরোয়, তারপর প্রতি ৩০ মিনিটে একটি করে।
- ফল। শেষ বস্তা D বেরোয় খণ্ড ৭-এর শেষে: ৪ + ৪ − ১ = ৭ খণ্ড = ৩.৫ ঘণ্টা। Speedup = ৮ / ৩.৫ ≈ ২.২৯ — চার ধাপ থাকলেও ৪ গুণ নয়, কারণ শুরুতে ভরতে আর শেষে খালি হতে সময় লাগে। বস্তা অনেক বেশি হলে speedup ৪-এর কাছে যায় (অংশ ৫)।
Latency বনাম throughput
পর্ব ০৩-এ (পারফরম্যান্স মাপা) দুটি মাপ দেখেছেন। Latency — একটি কাজ শুরু থেকে শেষ হতে কত সময়। Throughput — প্রতি একক সময়ে কয়টি কাজ শেষ হয়। লন্ড্রিতে latency ২ ঘণ্টাই থেকে গেছে, কিন্তু throughput ২ ঘণ্টায় ১টি বস্তা থেকে বেড়ে ৩০ মিনিটে ১টি — অর্থাৎ ৪ গুণ (দীর্ঘ সময়ের হিসাবে)। প্রোগ্রাম কোটি কোটি instruction চালায়, আর প্রোগ্রামের মোট সময় ঠিক করে throughput, একটি instruction-এর latency নয়। তাই pipelining একটি instruction-কে দ্রুত না করেও পুরো প্রোগ্রামকে দ্রুত করে।
Pipelining একটি throughput কৌশল. এটি প্রতিটি instruction-এর latency কমায় না — বাস্তবে register-এর খরচে একটু বাড়ায় (অংশ ৬) — কিন্তু প্রতি cycle-এ একটি instruction শেষ করে। পর্ব ০৩-এর iron law, CPU time = IC × CPI × Tc-এ pipelining Tc-কে প্রায় ধাপ-সংখ্যা দিয়ে ভাগ করে, আর CPI-কে আদর্শ অবস্থায় ১-এ রাখে।
২ · পাঁচ ধাপের RISC-V pipeline — IF, ID, EX, MEM, WB
পর্ব ৩২-এর single-cycle datapath-কে স্বাভাবিকভাবেই পাঁচ টুকরো করা যায়, কারণ প্রতিটি instruction মোটামুটি একই পথে চলে: instruction আনা, বোঝা, হিসাব, memory, ফল লেখা। এই পাঁচ ধাপ (Patterson–Hennessy-র RISC-V সংস্করণের নিয়মে, যা এই কোর্সের মূল ধারা) হলো:
| ধাপ | সবার জন্য কাজ | add / sub | lw | sw | beq |
|---|---|---|---|---|---|
| IF — Instruction fetch | PC-র ঠিকানা থেকে instruction আনা, PC + 4 হিসাব | একই | একই | একই | একই |
| ID — Instruction decode | Field আলাদা করা, rs1/rs2 register পড়া, immediate তৈরি, control signal বানানো | rs1, rs2 পড়া | rs1 পড়া, imm (I-type) | rs1, rs2 পড়া, imm (S-type) | rs1, rs2 পড়া, imm (B-type) |
| EX — Execute | ALU-র কাজ | rs1 ± rs2 | ঠিকানা = rs1 + imm | ঠিকানা = rs1 + imm | rs1 − rs2 তুলনা; লক্ষ্য = PC + imm |
| MEM — Memory access | Data memory | কিছু না (পার হয়) | Memory থেকে পড়া | Memory-তে rs2 লেখা | (সরল নকশায়) PC বদলানো |
| WB — Write-back | Register file-এ লেখা | ফল → rd | পড়া মান → rd | কিছু না | কিছু না |
লক্ষ করুন দুটি বিষয়। প্রথমত, register file দুই ধাপে ব্যবহার হয় — ID-তে পড়া, WB-তে লেখা। এজন্য pipeline-এর ছবিতে register file-কে প্রায়ই ID-তে আঁকা হয়, আর WB থেকে একটি তার পেছনে ফিরে আসে। দ্বিতীয়ত, memory-ও দুই ধাপে লাগে — IF-এ instruction, MEM-এ data. একই cycle-এ একটি instruction IF-এ আর আরেকটি MEM-এ থাকতে পারে, তাই দুটি আলাদা memory পথ লাগে: আলাদা instruction cache আর data cache (অংশ ৮-এ এর কারণ, structural hazard).
কেন প্রতিটি instruction পাঁচ ধাপই পার হয়? add-এর MEM-এ কোনো কাজ নেই — তবু সে MEM ধাপে এক cycle কাটায়, তারপর WB-তে লেখে। কারণ নিয়ম সরল রাখা: প্রতিটি ধাপ প্রতি cycle-এ ঠিক একটি instruction নেয়। add যদি MEM বাদ দিয়ে চতুর্থ cycle-এই WB করত, আর তার আগের lw একই cycle-এ তার পঞ্চম cycle-এ WB করত, তবে দুজনে একই সাথে register file-এর একটিমাত্র write port চাইত — একটি সংঘাত। সবাইকে একই পাঁচ ধাপের ছন্দে রাখলে এমন সংঘাতই তৈরি হয় না।
Register file-এর ছোট কৌশল: একই cycle-এ একটি instruction WB-তে কোনো register লিখছে আর আরেকটি ID-তে সেটি পড়ছে — এমন হতে পারে। Patterson–Hennessy-র নকশায় register file cycle-এর প্রথম অর্ধেকে লেখে আর দ্বিতীয় অর্ধেকে পড়ে, তাই একই cycle-এ লেখা মানটিই পড়া যায়। পর্ব ৩৬-এ data hazard-এর হিসাবে এটি কাজে লাগবে।
৩ · Pipeline register — ধাপগুলোর মাঝের হাতবদল
লন্ড্রিতে বস্তা নিজেই পরের যন্ত্রে চলে যায়। Circuit-এ একটি instruction-এর “বস্তা” হলো তার সব তথ্য — instruction-এর বিট, register থেকে পড়া মান, immediate, ALU-র ফল, destination register-এর নম্বর আর control signal. দুই ধাপের মাঝে এগুলো ধরে রাখতে বসানো হয় pipeline register — পর্ব ১৮-এর (Timing আর clock) সাধারণ edge-triggered register-এর একটি চওড়া সারি। প্রতিটি clock edge-এ সব pipeline register একসাথে আগের ধাপের ফল তুলে নেয় আর পরের ধাপকে দেয়। নাম দেওয়া হয় দুই পাশের ধাপ দিয়ে: IF/ID, ID/EX, EX/MEM, MEM/WB. PC নিজেও একটি register, যা IF ধাপকে খাওয়ায়; WB-র পরে আলাদা কোনো pipeline register লাগে না, কারণ ফল সরাসরি register file-এ যায়।
Control signal তৈরি হয় ID-তে, চলে instruction-এর সাথে
পর্ব ৩৩-এ (Control unit) একটি টেবিল দেখেছেন: opcode দেখে control unit বলে দেয় ALUSrc, MemtoReg, RegWrite, MemRead, MemWrite, Branch আর ALUOp কোনটি কী হবে। Single-cycle নকশায় এই control signal-গুলো পুরো cycle ধরে স্থির থাকে, কারণ datapath-এ একটিই instruction. Pipeline-এ পাঁচটি instruction একসাথে — EX ধাপের ALU-কে তার নিজের instruction-এর ALUOp মানতে হবে, ID-তে যে নতুন instruction decode হচ্ছে তারটা নয়।
সমাধান সহজ আর সুন্দর: control unit ID ধাপে একবারই সব signal বানায়, তারপর সেগুলো instruction-এর ডেটার সাথে pipeline register-এ চড়ে এগোয়। তিন দলে ভাগ করা হয়: EX বিট (ALUOp, ALUSrc) — EX-এ ব্যবহার শেষ, তাই EX/MEM-এ আর যায় না; M বিট (MemRead, MemWrite, Branch) — MEM-এ শেষ; WB বিট (RegWrite, MemtoReg) — একেবারে শেষ পর্যন্ত যায়। তাই ছবিতে ID/EX-এ তিন দল, EX/MEM-এ দুই দল, MEM/WB-এ এক দল। একই যুক্তিতে rd-এর নম্বরও শেষ পর্যন্ত বয়ে নিতে হয় — WB ধাপে লেখার সময় IF/ID-তে ততক্ষণে অন্য instruction, তার rd ভিন্ন।
সাধারণ ভুল: “WB ধাপ register file-এ লেখে, তাই rd-এর নম্বর IF/ID থেকে নিলেই চলে” — না। পাঠ্যবইয়ের প্রথম সরল pipelined datapath-এর ছবিতে প্রায়ই ঠিক এই ভুলটি রেখে পরে ধরিয়ে দেওয়া হয়: WB-র সময় IF/ID-তে চার instruction পরের কেউ বসে আছে, ফলে ভুল register-এ লেখা হয়। নিয়ম: কোনো ধাপের যা লাগবে, তা হয় সেই ধাপের নিজের pipeline register থেকে আসবে, নয় সেখানে হিসাব হবে।
নিচের stepper-এ lw x5, 0(x10) পাঁচ cycle-এ চারটি pipeline register পেরিয়ে যায় (x10 = 100, memory[100] = 42). লক্ষ করুন, প্রতিটি register-এর পুরোনো জায়গায় সাথে সাথে পরের instruction বসে পড়ে — পাঁচটি instruction-এর প্রোগ্রামটি অংশ ৪-এর।
| Cycle · lw এখন কোন ধাপে | |
| IF/ID | |
| ID/EX | |
| EX/MEM | |
| MEM/WB | |
| Register file-এ লেখা |
- Cycle ১ — IF. PC = 0 থেকে
lw-এর instruction আনা হয়, PC + 4 = 4 হিসাব হয়। Cycle-এর শেষের edge-এ instruction-এর বিট আর PC IF/ID-তে ধরা পড়ে। - Cycle ২ — ID. Instruction ভেঙে rs1 = x10 পড়া হয় (১০০), imm = 0 তৈরি হয়, rd = 5 আলাদা করা হয়, আর control unit সব signal বানায়। এগুলো ID/EX-এ যায়। একই সময়ে IF/ID-তে উঠে বসেছে পরের instruction
addi. - Cycle ৩ — EX. ALU ঠিকানা হিসাব করে: ১০০ + ০ = ১০০. EX বিট (ALUSrc, ALUOp) এখানে খরচ হয়ে গেছে; EX/MEM-এ যায় শুধু ALU-র ফল, rd আর M ও WB বিট। ID/EX-এ এখন
addi, IF/ID-তেadd. - Cycle ৪ — MEM. MemRead = 1, তাই data memory-র ঠিকানা ১০০ থেকে ৪২ পড়া হয়। MEM/WB-এ যায় data = ৪২, rd = 5 আর শুধু WB বিট। পেছনের তিনটি register-এ এখন যথাক্রমে
sw,add,addi. - Cycle ৫ — WB. MemtoReg = 1 বলে multiplexer memory-র মান বেছে নেয়, RegWrite = 1 বলে register file-এ লেখা হয়: x5 ← ৪২. MEM/WB থেকে আসা rd = 5 ব্যবহার হয়েছে, IF/ID-র নয় — সেখানে এখন
sub, যার rd 9.
৪ · Pipeline diagram — instruction বনাম clock cycle
পরীক্ষায় আর নকশার আলোচনায় pipeline বোঝানোর সবচেয়ে প্রচলিত ছবি হলো pipeline diagram: সারিতে instruction, কলামে clock cycle, ঘরে ধাপের নাম। নিচের পাঁচটি RV32I instruction-এর মধ্যে কোনো নির্ভরতা নেই — কেউ আগের কারো লেখা register পড়ে না — তাই কোনো hazard নেই (hazard-এর কথা অংশ ৮-এ)।
| Instruction | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 |
|---|---|---|---|---|---|---|---|---|---|
lw x5, 0(x10) | IF | ID | EX | MEM | WB | ||||
addi x6, x0, 3 | IF | ID | EX | MEM | WB | ||||
add x7, x1, x2 | IF | ID | EX | MEM | WB | ||||
sw x8, 4(x10) | IF | ID | EX | MEM | WB | ||||
sub x9, x3, x4 | IF | ID | EX | MEM | WB |
টেবিলটি একটি সিঁড়ির মতো। প্রতিটি সারি আগেরটির চেয়ে এক ঘর ডানে সরে যায়, কারণ প্রতি cycle-এ একটি নতুন instruction IF-এ ঢোকে। কোনো কলাম ধরে নিচে পড়লে সেই cycle-এ pipeline-এর অবস্থা পাওয়া যায় — যেমন c5-এ পাঁচটি ধাপেই একজন করে: sub IF-এ, sw ID-তে, add EX-এ, addi MEM-এ, lw WB-তে। এটিই একমাত্র cycle, যখন pipeline সম্পূর্ণ পূর্ণ। নিচের stepper-এ একই জিনিস cycle ধরে ধরে দেখুন — এবার প্রতিটি সারি একটি ধাপ।
| Clock cycle | |
| IF | |
| ID | |
| EX | |
| MEM | |
| WB | |
| ব্যস্ত ধাপ | |
| শেষ হয়েছে |
- c1. শুধু
lwIF-এ। বাকি চারটি ধাপ খালি — pipeline ভরা শুরু হলো। - c2.
lwID-তে,addiIF-এ। দুটি ধাপ ব্যস্ত, এখনো কিছু শেষ হয়নি। - c3.
lwEX-এ ঠিকানা হিসাব করছে,addiID-তে,addIF-এ। - c4. চারটি ধাপ ব্যস্ত।
lwdata memory পড়ছে, আরswএকই cycle-এ instruction memory থেকে আসছে — আলাদা I-cache আর D-cache থাকায় সংঘাত নেই। - c5. পাঁচটি ধাপই ব্যস্ত — pipeline পূর্ণ।
lwWB-তে x5-এ লিখে শেষ: প্রথম ফল আসতে লাগল k = ৫ cycle. - c6. নতুন instruction নেই, তাই IF খালি — pipeline খালি হওয়া (drain) শুরু।
addiশেষ হলো; এখন থেকে প্রতি cycle-এ একটি করে শেষ হবে। - c7.
addশেষ। তিনটি ধাপ ব্যস্ত। - c8.
sw-এর WB ধাপে কোনো কাজ নেই (register-এ লেখে না), তবু সে ছন্দ মেনে এক cycle কাটিয়ে শেষ হয়। - c9.
subশেষ — মোট ৯ cycle = k + n − ১ = ৫ + ৫ − ১. Pipeline ছাড়া (প্রতিটি instruction পাঁচ ধাপ, একটির পর একটি) লাগত ৫ × ৫ = ২৫টি ধাপ-সময়।
এই diagram যেকোনো instruction-তালিকার জন্য বানানো যায়। নিচের generator নিয়মটি সরাসরি কাজে লাগায়: instruction i (০ থেকে গোনা) cycle i + ১-এ IF-এ ঢোকে, তাই cycle c-তে সে আছে ধাপ নম্বর c − ১ − i-তে। তালিকায় instruction যোগ বা বাদ দিয়ে Run চাপুন — মোট cycle কীভাবে বদলায় দেখুন।
// Pipeline diagram for a hazard-free instruction list (5 stages).
// Instruction i (0-based) enters IF in cycle i+1 and is in stage s
// during cycle i+1+s. Total cycles = k + n - 1.
const STAGES = ["IF", "ID", "EX", "MEM", "WB"];
const prog = [
"lw x5, 0(x10)",
"addi x6, x0, 3",
"add x7, x1, x2",
"sw x8, 4(x10)",
"sub x9, x3, x4",
];
const k = STAGES.length, n = prog.length, total = k + n - 1;
let head = "instruction".padEnd(17);
for (let c = 1; c <= total; c++) head += ("c" + c).padStart(5);
console.log(head);
prog.forEach((ins, i) => {
let row = ins.padEnd(17);
for (let c = 1; c <= total; c++) {
const s = c - 1 - i; // stage index in cycle c
row += (s >= 0 && s < k ? STAGES[s] : ".").padStart(5);
}
console.log(row);
});
// Which cycles is the pipeline completely full?
const full = [];
for (let c = 1; c <= total; c++) if (c >= k && c <= n) full.push(c);
console.log("\nk = " + k + ", n = " + n + " -> total cycles = k + n - 1 = " + total);
console.log("pipeline full (all 5 stages busy) in cycle(s): " + full.join(", "));
console.log("unpipelined would need n * k = " + n * k + " cycles");
console.log("cycles per finished instruction = " + (total / n).toFixed(2));আউটপুট:
instruction c1 c2 c3 c4 c5 c6 c7 c8 c9
lw x5, 0(x10) IF ID EX MEM WB . . . .
addi x6, x0, 3 . IF ID EX MEM WB . . .
add x7, x1, x2 . . IF ID EX MEM WB . .
sw x8, 4(x10) . . . IF ID EX MEM WB .
sub x9, x3, x4 . . . . IF ID EX MEM WB
k = 5, n = 5 -> total cycles = k + n - 1 = 9
pipeline full (all 5 stages busy) in cycle(s): 5
unpipelined would need n * k = 25 cycles
cycles per finished instruction = 1.80
ছাপা টেবিল ওপরের হাতে আঁকা diagram-এর সাথে ঘর-ঘর মেলে। মোট ৯ cycle, আর পূর্ণ cycle মাত্র একটি — c5, কারণ এখানে n = k = ৫. Pipeline ছাড়া ২৫টি ধাপ-সময় লাগত। শেষ লাইনটি গুরুত্বপূর্ণ: প্রতিটি শেষ হওয়া instruction-এর জন্য গড়ে ১.৮০ cycle — আদর্শ ১ নয়, কারণ ভরা আর খালি হওয়ার ৪টি cycle মাত্র ৫টি instruction-এর মধ্যে ভাগ হয়েছে। তালিকায় ১০০টি instruction দিলে হতো (৫ + ৯৯) / ১০০ = ১.০৪.
একটি ছোট pipelined machine — প্রতিটি register-এর ভেতরে উঁকি
Diagram বলে কে কোথায়; কিন্তু প্রতিটি pipeline register-এ আসলে কোন মান আছে? নিচের ছোট simulator একই পাঁচটি instruction আসলে চালায় (x1 = 10, x2 = 20, x3 = 50, x4 = 7, x8 = 99, x10 = 100, memory[100] = 42). এর মূল কৌশলটি hardware-এর মতোই: প্রতিটি cycle-এ প্রথমে WB, তারপর MEM, EX, ID, IF হিসাব হয় — প্রতিটি ধাপ আগের cycle-এর pipeline register পড়ে — আর শেষে চারটি register একসাথে বদলায়, ঠিক যেমন clock edge-এ সব flip-flop একসাথে মান নেয়।
// Tiny 5-stage RV32I pipeline (no hazards in this program, so no
// forwarding or stalls). Each cycle we update the stages from WB back
// to IF, so every stage reads the pipeline register of the LAST cycle.
const x = new Array(32).fill(0); Object.assign(x, {1: 10, 2: 20, 3: 50, 4: 7, 8: 99, 10: 100});
const mem = {100: 42};
const P = [ {t: "lw x5,0(x10)", op: "lw", rd: 5, a: 10, imm: 0},
{t: "addi x6,x0,3", op: "addi", rd: 6, a: 0, imm: 3},
{t: "add x7,x1,x2", op: "add", rd: 7, a: 1, b: 2},
{t: "sw x8,4(x10)", op: "sw", a: 10, b: 8, imm: 4},
{t: "sub x9,x3,x4", op: "sub", rd: 9, a: 3, b: 4} ];
let IFID = null, IDEX = null, EXMEM = null, MEMWB = null, pc = 0;
const name = r => (r ? r.i.t.split(" ")[0] : "-");
for (let c = 1; c <= P.length + 4; c++) {
let wb = "-"; // WB
if (MEMWB && MEMWB.i.rd) { x[MEMWB.i.rd] = MEMWB.v; wb = `x${MEMWB.i.rd}=${MEMWB.v}`; }
let nMW = null; // MEM
if (EXMEM) { const e = EXMEM;
if (e.i.op === "sw") mem[e.alu] = e.B;
nMW = {i: e.i, v: e.i.op === "lw" ? mem[e.alu] : e.alu}; }
let nEM = null; // EX
if (IDEX) { const d = IDEX, op = d.i.op;
const alu = op === "add" ? d.A + d.B : op === "sub" ? d.A - d.B : d.A + d.i.imm;
nEM = {i: d.i, alu, B: d.B}; }
const nIE = IFID ? {i: IFID.i, A: x[IFID.i.a], B: x[IFID.i.b ?? 0]} : null; // ID
const nIF = pc / 4 < P.length ? {i: P[pc / 4], pc} : null; // IF
if (nIF) pc += 4;
[IFID, IDEX, EXMEM, MEMWB] = [nIF, nIE, nEM, nMW];
console.log(`c${c} IF/ID[${IFID ? name(IFID) + " pc=" + IFID.pc : "-"}]`.padEnd(23) +
`ID/EX[${IDEX ? name(IDEX) + " A=" + IDEX.A + " B=" + IDEX.B : "-"}]`.padEnd(21) +
`EX/MEM[${EXMEM ? name(EXMEM) + " alu=" + EXMEM.alu : "-"}]`.padEnd(19) +
`MEM/WB[${MEMWB ? name(MEMWB) + " v=" + MEMWB.v : "-"}]`.padEnd(17) + "WB: " + wb);
}
console.log(`final: x5=${x[5]} x6=${x[6]} x7=${x[7]} x9=${x[9]} mem[104]=${mem[104]}`);আউটপুট:
c1 IF/ID[lw pc=0] ID/EX[-] EX/MEM[-] MEM/WB[-] WB: -
c2 IF/ID[addi pc=4] ID/EX[lw A=100 B=0] EX/MEM[-] MEM/WB[-] WB: -
c3 IF/ID[add pc=8] ID/EX[addi A=0 B=0] EX/MEM[lw alu=100] MEM/WB[-] WB: -
c4 IF/ID[sw pc=12] ID/EX[add A=10 B=20] EX/MEM[addi alu=3] MEM/WB[lw v=42] WB: -
c5 IF/ID[sub pc=16] ID/EX[sw A=100 B=99] EX/MEM[add alu=30] MEM/WB[addi v=3] WB: x5=42
c6 IF/ID[-] ID/EX[sub A=50 B=7] EX/MEM[sw alu=104] MEM/WB[add v=30] WB: x6=3
c7 IF/ID[-] ID/EX[-] EX/MEM[sub alu=43] MEM/WB[sw v=104] WB: x7=30
c8 IF/ID[-] ID/EX[-] EX/MEM[-] MEM/WB[sub v=43] WB: -
c9 IF/ID[-] ID/EX[-] EX/MEM[-] MEM/WB[-] WB: x9=43
final: x5=42 x6=3 x7=30 x9=43 mem[104]=99
প্রতিটি সারি একটি cycle-এর শেষের ছবি। কর্ণ বরাবর পড়ুন: lw c1-এ IF/ID-তে, c2-এ ID/EX-এ (A = 100), c3-এ EX/MEM-এ (alu = 100), c4-এ MEM/WB-এ (v = 42), আর c5-এ “WB: x5=42” — ঠিক stepper-এর মতো। sw c6-এ ঠিকানা ১০৪ হিসাব করে, c7-এ memory[104]-এ ৯৯ লেখে; তার MEM/WB-এর v = 104 কোনো কাজে লাগে না, তাই c8-এ “WB: -”. sub ৫০ − ৭ = ৪৩ হিসাব করে c9-এ x9-এ লেখে। শেষ লাইনের মানগুলো single-cycle machine-এ চালালে যা হতো, হুবহু তাই — pipelining ফল বদলায় না, শুধু সময় বদলায়। (এই প্রোগ্রামে কোনো instruction আগেরটির ফল পড়ে না; পড়লে কী হয়, পর্ব ৩৬-এর বিষয়।)
৫ · Speedup-এর হিসাব — k + n − ১ থেকে k পর্যন্ত
এবার সাধারণ সূত্র। ধরুন pipeline-এ k-টি ধাপ, প্রতিটির সময় সমান τ, আর n-টি instruction, কোনো hazard নেই।
মোট সময়ের প্রমাণ। প্রথম instruction সব k ধাপ পেরোতে k cycle নেয় — সেটি শেষ হয় cycle k-এর শেষে। এরপর প্রতি cycle-এ একটি করে instruction শেষ হয়, কারণ instruction i + ১ ঠিক এক cycle পরে instruction i-এর পেছনে পেছনে চলে। বাকি n − ১টি instruction তাই আরও n − ১ cycle নেয়। মোট:
মোট cycle = k + (n − ১) ⇒ Tpipe = (k + n − ১) · τ
বাড়তি k − ১ cycle হলো শুরুতে pipeline ভরা আর শেষে খালি হওয়ার খরচ — fill and drain; এই সময়ে সব ধাপ একসাথে কাজ করে না।
Pipeline ছাড়া — একই ধাপগুলো, কিন্তু এক instruction শেষ হলে তবেই পরেরটি — প্রতিটি instruction নেয় k·τ, তাই Tseq = n · k · τ. অতএব
Speedup S(n) = n · k / (k + n − ১)
সীমা। লব আর হরকে n দিয়ে ভাগ করুন: S(n) = k / (১ + (k − ১)/n). n → ∞ হলে (k − ১)/n → ০, তাই S → k. আর যেহেতু (k − ১)/n > ০ যেকোনো সসীম n আর k > ১-এর জন্য, S(n) সবসময় k-এর চেয়ে কম — k কখনো ছোঁয়া যায় না, শুধু কাছে যাওয়া যায়। আদর্শ speedup = ধাপের সংখ্যা, শর্ত তিনটি: (১) সব ধাপ সমান, (২) pipeline register-এর কোনো সময়-খরচ নেই, (৩) কোনো stall নেই। বাস্তবে তিনটিই ভাঙে — অংশ ৬ আর ৮-এ।
| n (instruction সংখ্যা) | |
| Pipeline-এ cycle (k + n − 1) | |
| Pipeline ছাড়া (n · k) | |
| Speedup S(n) | |
| k-এর কত ভাগ (S / k) |
- n = ১. একটি instruction: দুই পদ্ধতিতেই ৫ cycle — speedup ১. একা একটি কাজে pipeline-এর কোনো লাভ নেই, কারণ overlap করার মতো কেউ নেই।
- n = ২. ৬ বনাম ১০ cycle, speedup ১০ / ৬ ≈ ১.৬৭.
- n = ৫. অংশ ৪-এর প্রোগ্রাম: ৯ বনাম ২৫, speedup ≈ ২.৭৮ — আদর্শ ৫-এর অর্ধেকের একটু বেশি। ভরা-খালির ৪ cycle এখানে বড় বোঝা।
- n = ১০. ১৪ বনাম ৫০, speedup ≈ ৩.৫৭ — k-এর ৭১%.
- n = ১০০. ১০৪ বনাম ৫০০, speedup ≈ ৪.৮১. বাড়তি ৪ cycle এখন ১০৪-এর মাত্র ৪%.
- n = ১,০০০. speedup ≈ ৪.৯৮ — ৯৯.৬%.
- n = ১০৬. speedup ≈ ৪.৯৯৯৯৮ — ৫-এর এত কাছে যে পার্থক্য প্রায় অদৃশ্য, তবু ৫ নয়। আসল প্রোগ্রাম কোটি কোটি instruction চালায়, তাই ভরা-খালির খরচ সাধারণত নগণ্য — আসল ক্ষতি আসে অসম ধাপ, overhead আর stall থেকে।
আদর্শ CPI = ১, আর বাস্তবের CPI
পর্ব ০৩-এর ভাষায় CPI (cycles per instruction) দেখুন। n-টি instruction-এ (k + n − ১) cycle, তাই CPI = (k + n − ১) / n, যা n বড় হলে ১-এ নামে। অর্থাৎ আদর্শ pipeline-এর CPI = ১ — প্রতি cycle-এ একটি instruction শেষ। (Single-cycle-এর CPI-ও ১, কিন্তু তার cycle অনেক লম্বা; multi-cycle-এর cycle ছোট, কিন্তু CPI ৩–৫. Pipeline দুইয়ের ভালোটা একসাথে নেয়: ছোট cycle এবং CPI ≈ ১.)
Hazard থাকলে কিছু instruction-কে অপেক্ষা করতে হয় — stall, আর pipeline-এ ঢোকে একটি খালি ধাপ, bubble. তখন:
CPIpipeline = ১ + (প্রতি instruction-এ গড় stall cycle)
উদাহরণ। একটি প্রোগ্রামে ২৫% instruction lw, যার ৪০%-এর ঠিক পরেই একটি instruction তার ফল চায় আর ১ cycle stall হয়; ১৫% branch, যার ২০% ভুল অনুমান হয় আর প্রতিটিতে ২ cycle নষ্ট হয়। Stall = ০.২৫ × ০.৪ × ১ + ০.১৫ × ০.২ × ২ = ০.১০ + ০.০৬ = ০.১৬, তাই CPI = ১.১৬. অংশ ৬-এর Tstage = ২২০ ps নিলে প্রতি instruction-এ গড় সময় ১.১৬ × ২২০ = ২৫৫.২ ps, আর ৮২০ ps-এর single-cycle-এর তুলনায় speedup ৮২০ / ২৫৫.২ ≈ ৩.২১ (stall ছাড়া হতো ৩.৭৩)। এই stall কোথা থেকে আসে আর কীভাবে কমে — forwarding, load-use stall — পর্ব ৩৬-এ (Pipeline hazard), আর branch-এর অংশ পর্ব ৩৭-এ (Branch prediction).
৬ · অসম ধাপ আর register overhead — পরীক্ষার পূর্ণ numerical
বাস্তব ধাপগুলো সমান নয়। Memory পড়া আর ALU সাধারণত ধীর, register পড়া বা লেখা দ্রুত। কিন্তু pipeline-এর সব ধাপ একই clock শোনে — clock-কে সবচেয়ে ধীর ধাপের জন্য অপেক্ষা করতেই হবে। তার ওপর প্রতিটি pipeline register নিজেই সময় খায়: পর্ব ১৮-এর sequencing overhead, tpcq + tsetup (আর skew). তাই:
Tstage = max(ধাপের delay) + treg, যেখানে treg = tpcq + tsetup
ধীরতম ধাপটি pipeline-এর critical path — পর্ব ১৮-এর ভাষায়, যে দুই register-এর মাঝে সবচেয়ে বেশি logic.
উদাহরণ: ২০০/১০০/২০০/২০০/১০০ ps, register overhead ২০ ps
একটি প্রসেসরে IF = ২০০, ID = ১০০, EX = ২০০, MEM = ২০০, WB = ১০০ ps; প্রতিটি register-এর overhead ২০ ps. (ক) single-cycle আর pipelined নকশার clock, (খ) একটি instruction-এর latency, (গ) ১, ৫ আর ১০,০০,০০০ (১০৬) instruction-এর মোট সময়, throughput আর speedup বের করুন।
ধরে নেওয়া: single-cycle নকশাতেও cycle-এর শেষে একটি register (PC আর register file) আছে, তাই তারও একবার ২০ ps overhead লাগে। কিছু বইয়ের প্রশ্নে non-pipelined নকশার overhead ধরা হয় না — সেই হিসাব নিচে আলাদা দেখানো হলো। পরীক্ষায় প্রশ্নে যা বলা আছে, সেটিই মানবেন, আর নিজের ধারণাটি লিখে দেবেন।
- Single-cycle clock: সব ধাপের যোগফল + একবার overhead = ২০০ + ১০০ + ২০০ + ২০০ + ১০০ + ২০ = ৮২০ ps (≈ ১.২২ GHz).
- Pipelined clock: Tstage = max(২০০, ১০০, ২০০, ২০০, ১০০) + ২০ = ২২০ ps (≈ ৪.৫৫ GHz).
- Latency: single-cycle-এ ৮২০ ps; pipelined-এ ৫ × ২২০ = ১,১০০ ps — ২৮০ ps বেশি! কারণ ১০০ ps-এর ধাপগুলোকেও ২২০ ps দিতে হচ্ছে, আর overhead ১ বারের বদলে ৫ বার।
| n | Single-cycle সময় (n × ৮২০) | Pipelined সময় ((৫ + n − ১) × ২২০) | Speedup | Pipelined throughput |
|---|---|---|---|---|
| ১ | ৮২০ ps | ১,১০০ ps | ০.৭৪৫ (ধীর!) | ০.৯০৯ instr/ns |
| ৫ | ৪,১০০ ps | ৯ × ২২০ = ১,৯৮০ ps | ২.০৭১ | ২.৫২৫ instr/ns |
| ১০৬ | ৮২০,০০০,০০০ ps = ৮২০ µs | ১,০০০,০০৪ × ২২০ = ২২০,০০০,৮৮০ ps ≈ ২২০.০০১ µs | ৩.৭২৭ | ৪.৫৪৫ instr/ns |
| সীমা n → ∞ | ৮২০ / ২২০ | ৩.৭২৭ | ১ / ২২০ ps ≈ ৪.৫৫ × ১০৯ instr/s | |
Single-cycle-এর throughput সব n-এ একই: ১ / ৮২০ ps ≈ ১.২২ instr/ns. সংখ্যাগুলো থেকে তিনটি শিক্ষা। এক, একটি instruction-এর জন্য pipeline ধীর (speedup ০.৭৪৫ < ১)। দুই, n বড় হলে speedup সীমায় পৌঁছায়, কিন্তু সেই সীমা ৫ নয়, ৩.৭৩ — কারণ সীমাটি আসলে Tsingle / Tstage, k নয়। তিন, ৫ থেকে ৩.৭৩-এ নামার দুই কারণ আলাদা করে মাপা যায়: সমান ধাপ হলে (৮০০ / ৫ = ১৬০ ps প্রতিটি) Tstage হতো ১৮০ ps আর সীমা ৮২০ / ১৮০ ≈ ৪.৫৬ — অসমতার দাম; overhead শূন্য হলে আর সমান ধাপে সীমা হতো ৮০০ / ১৬০ = ঠিক ৫.
যদি single-cycle-এ overhead না ধরা হয় (T = ৮০০ ps): speedup n = ১-এ ০.৭২৭, n = ৫-এ ২.০২০, n = ১০৬-এ ৩.৬৩৬, আর সীমা ৮০০ / ২২০ ≈ ৩.৬৪.
কোন ধাপ ভাগ করলে লাভ?
Tstage ঠিক করে ধীরতম ধাপ। তাই স্বাভাবিক প্রশ্ন: ২০০ ps-এর একটি ধাপকে দুটি ১০০ ps-এর ধাপে ভাগ করলে? ফাঁদটি হলো, এখানে তিনটি ধাপ ২০০ ps (IF, EX, MEM). শুধু IF ভাগ করলে ধাপ ৬টি হয়, কিন্তু সবচেয়ে ধীর ধাপ এখনো ২০০ — Tstage ২২০-ই থাকে, আর latency বেড়ে ৬ × ২২০ = ১,৩২০ ps. লাভ শূন্য, ক্ষতি আছে। তিনটিকেই ভাগ করলে ৮টি ১০০ ps-এর ধাপ: Tstage = ১২০ ps, সীমা ৮২০ / ১২০ ≈ ৬.৮৩. আর ক্রমাগত ভাগ করলে? নিচের calculator দুটোই দেখায়।
// Pipeline timing: Tstage = max(stage delay) + register overhead.
// Single-cycle machine: Tc = sum(stage delays) + one register overhead.
const REG = 20; // ps (tpcq + tsetup)
function analyse(name, stages, ns) {
const k = stages.length;
const T = Math.max(...stages) + REG;
const single = stages.reduce((a, b) => a + b, 0) + REG;
console.log(`${name}: k=${k} Tstage=${T} ps latency=${k * T} ps single-cycle Tc=${single} ps`);
for (const n of ns) {
const tp = (k + n - 1) * T, ts = n * single;
console.log(" n=" + String(n).padEnd(8) + "pipelined " + String(tp).padStart(10) +
" ps single " + String(ts).padStart(10) + " ps speedup " + (ts / tp).toFixed(3) +
" throughput " + (n / tp * 1000).toFixed(3) + " instr/ns");
}
}
analyse("5-stage RISC-V", [200, 100, 200, 200, 100], [1, 5, 100, 1e6]);
analyse("split IF only ", [100, 100, 100, 200, 200, 100], [1e6]);
analyse("split IF,EX,MEM", [100, 100, 100, 100, 100, 100, 100, 100], [1e6]);
// Deeper and deeper: 800 ps of logic cut into k perfectly equal pieces.
console.log("\n k Tstage limit speedup speedup / k (overhead share)");
for (const k of [1, 2, 4, 8, 16, 32, 64]) {
const T = 800 / k + REG, sp = 820 / T;
console.log(String(k).padStart(3) + String(T).padStart(9) + sp.toFixed(2).padStart(16) +
(sp / k).toFixed(2).padStart(14) + ((REG / T) * 100).toFixed(0).padStart(12) + "%");
}আউটপুট:
5-stage RISC-V: k=5 Tstage=220 ps latency=1100 ps single-cycle Tc=820 ps
n=1 pipelined 1100 ps single 820 ps speedup 0.745 throughput 0.909 instr/ns
n=5 pipelined 1980 ps single 4100 ps speedup 2.071 throughput 2.525 instr/ns
n=100 pipelined 22880 ps single 82000 ps speedup 3.584 throughput 4.371 instr/ns
n=1000000 pipelined 220000880 ps single 820000000 ps speedup 3.727 throughput 4.545 instr/ns
split IF only : k=6 Tstage=220 ps latency=1320 ps single-cycle Tc=820 ps
n=1000000 pipelined 220001100 ps single 820000000 ps speedup 3.727 throughput 4.545 instr/ns
split IF,EX,MEM: k=8 Tstage=120 ps latency=960 ps single-cycle Tc=820 ps
n=1000000 pipelined 120000840 ps single 820000000 ps speedup 6.833 throughput 8.333 instr/ns
k Tstage limit speedup speedup / k (overhead share)
1 820 1.00 1.00 2%
2 420 1.95 0.98 5%
4 220 3.73 0.93 9%
8 120 6.83 0.85 17%
16 70 11.71 0.73 29%
32 45 18.22 0.57 44%
64 32.5 25.23 0.39 62%
প্রথম চার সারি ওপরের টেবিলের প্রতিটি সংখ্যা মেলায়, আর n = ১০০-এ speedup ৩.৫৮৪. “split IF only”-তে Tstage ২২০-ই রয়ে গেছে, latency ১,১০০ থেকে ১,৩২০, আর ১০৬ instruction-এর সময় সামান্য বেড়েছে (২২০,০০০,৮৮০ থেকে ২২০,০০১,১০০ ps) — একটি বাড়তি ধাপ ভরতে আরেক cycle. তিনটি ধাপ ভাগ করলে throughput ৪.৫৪৫ থেকে ৮.৩৩৩ instr/ns. শেষ টেবিলটি এই পর্বের সবচেয়ে গুরুত্বপূর্ণ ছবি: ৮০০ ps-এর logic-কে k-টি সমান টুকরো করলে k = ২-এ speedup ১.৯৫ (আদর্শের ৯৮%), কিন্তু k = ৬৪-এ মাত্র ২৫.২৩ (৩৯%) — কারণ ৩২.৫ ps-এর cycle-এর ৬২%-ই register-এর ২০ ps. প্রতিটি নতুন ধাপ আগেরটির চেয়ে কম লাভ দেয় — diminishing returns. লক্ষ করুন k = ৪-এর সমান টুকরো (২২০ ps) আর আমাদের অসম ৫-ধাপ (২২০ ps) একই গতির: অসম ধাপে একটি ধাপ বাড়তি থেকেও লাভ নেই।
৭ · কেন RISC ISA pipeline-বান্ধব — আর x86-এর চ্যালেঞ্জ
পর্ব ২৫-এ (ISA কী) RISC বনাম CISC-এর তর্ক দেখেছেন, আর পর্ব ২৭-এ (Instruction format আর encoding) RISC-V-এর বিট-বিন্যাস। এখন বোঝা যাবে কেন RISC-এর নকশাকারীরা এত সরলতার ওপর জোর দিয়েছিলেন — উদ্দেশ্য অনেকটাই pipeline. চারটি বৈশিষ্ট্য সরাসরি pipeline-কে সাহায্য করে:
১. সব instruction ৩২ বিট
নির্দিষ্ট দৈর্ঘ্য মানে IF ধাপ সবসময় ঠিক ৪ বাইট আনে আর PC + 4 করে — পরের instruction কোথায়, তা decode করার আগেই জানা। তাই প্রতি cycle-এ নির্দ্বিধায় একটি করে fetch চলে।
২. অল্প কয়েকটি format, field একই জায়গায়
RISC-V-এ rs1, rs2 আর rd প্রায় সব format-এ একই বিট-অবস্থানে। তাই ID ধাপ instruction-এর ধরন বোঝার আগেই register file পড়া শুরু করতে পারে — decode আর register পড়া একই ধাপে সমান্তরালে।
৩. Load/store নিয়ম
Load-store ISA-তে শুধু lw/sw memory ছোঁয়, আর প্রতিটি instruction সর্বোচ্চ একবার। তাই MEM ধাপ একটিই, আর ঠিকানা হিসাব (EX) হয় memory-র আগে — ঠিক পাঁচ ধাপের ক্রমে।
৪. Aligned memory access
Data যখন তার আকারের গুণিতক ঠিকানায় (alignment), একটি load একটি cache access-এ শেষ — দুই cache line-এ ছড়িয়ে দুবার যেতে হয় না। RISC-V misaligned access-কে ঐচ্ছিক বা ধীর পথে রাখে।
এবার x86-এর কথা ভাবুন। Instruction ১ থেকে ১৫ বাইট পর্যন্ত (variable length), prefix থাকতে পারে, আর একটি instruction-এর দৈর্ঘ্য জানতে তার বেশ কয়েকটি বাইট পড়তে হয় — তাই পরের instruction কোথায় শুরু, তা জানার আগেই IF আটকে যায়। add [rbx+8], eax-এর মতো একটি instruction memory পড়ে, যোগ করে, আবার memory-তে লেখে — পাঁচ ধাপের এক ছন্দে বসে না। কিছু instruction (যেমন string copy) অনেক cycle নেয়।
আধুনিক x86 প্রসেসর এটি সমাধান করে একটি চতুর অনুবাদ দিয়ে: ফ্রন্ট-এন্ডে decoder প্রতিটি x86 instruction-কে এক বা একাধিক RISC-ধাঁচের ছোট micro-op-এ ভাঙে, আর ভেতরের pipeline সেই micro-op-গুলো চালায়। Intel-এর Pentium Pro (১৯৯৫) এই পদ্ধতির পরিচিত শুরু, আর আজকের Intel ও AMD core-এ এটিই ভিত্তি; ঘন ঘন চলা অংশের decode-এর খরচ বাঁচাতে decode করা micro-op একটি ছোট cache-এ রাখা হয়। মূল্য: বড় ও শক্তিখেকো decoder, আর বাড়তি কয়েকটি pipeline ধাপ। অর্থাৎ শেষ পর্যন্ত x86-ও ভেতরে RISC-ধাঁচের pipeline চালায় — ISA-র জটিলতা গিলে ফেলা হয় ফ্রন্ট-এন্ডে (ভাবনার প্রশ্ন ০২)।
৮ · Hazard — যেখানে pipeline হোঁচট খায় (পরিচয়)
এতক্ষণ ধরে নেওয়া হয়েছে প্রতিটি instruction তার নির্ধারিত cycle-এ নিশ্চিন্তে চলতে পারে। যে পরিস্থিতিতে তা পারে না, তাকে বলে pipeline hazard. তিন ধরনের hazard আছে — এই পর্বে শুধু সংজ্ঞা আর একটি করে ছোট উদাহরণ; সমাধানের বিস্তারিত পরের দুই পর্বে।
১. Structural hazard — একই যন্ত্র, দুজন দাবিদার
Structural hazard হয়, যখন একই cycle-এ দুটি instruction একই hardware চায়। ক্লাসিক উদাহরণ: memory যদি একটিই হয় (instruction আর data একসাথে, von Neumann ধাঁচে), তবে অংশ ৪-এর c4-এ lw MEM-এ data পড়তে চায় আর sw IF-এ instruction আনতে চায় — একটি memory port দুটি কাজ একসাথে পারে না, তাই একজনকে অপেক্ষা করতে হয়। লন্ড্রিতে একটিই যন্ত্র যদি washer আর dryer দুটোই হয়, একই অবস্থা। সমাধান: বাড়তি hardware — আলাদা instruction cache আর data cache (Harvard ধাঁচ, অন্তত L1-এ), আর register file-এ আলাদা read ও write port. RISC-V-এর পাঁচ ধাপের নকশা এমনভাবে বানানো যাতে এই সমাধানগুলো থাকলে structural hazard না থাকে।
২. Data hazard — মান এখনো তৈরি হয়নি
Data hazard হয়, যখন একটি instruction এমন register পড়তে চায়, যা আগের instruction এখনো লেখেনি। সবচেয়ে সাধারণ ধরন RAW (read after write):
add x1, x2, x3 # x1 is written in WB (cycle 5)
sub x4, x1, x5 # but sub reads x1 in ID (cycle 3) -> old value!
add ফল হিসাব করে EX-এ (cycle ৩), কিন্তু register file-এ লেখে WB-তে (cycle ৫)। sub cycle ৩-এ ID-তে x1 পড়ে ফেলে — পুরোনো মান। দুই সমাধান: অপেক্ষা (stall, bubble ঢুকিয়ে), অথবা forwarding — EX/MEM register থেকে ফলটি সরাসরি পরের instruction-এর ALU-তে পৌঁছে দেওয়া। Forwarding-এর তার, load-এর পরে কেন তবু এক cycle stall লাগে (load-use), আর compiler কীভাবে instruction-এর ক্রম বদলে সাহায্য করে — সব পর্ব ৩৬-এ (Pipeline hazard — structural, data hazard, forwarding আর stall).
৩. Control hazard — কোন পথে যাব, এখনো অজানা
Control hazard হয় branch-এ। beq x1, x2, L নেওয়া হবে কি না, তা জানা যায় EX-এ তুলনার পর — কিন্তু ততক্ষণে IF আরও দুটি instruction এনে ফেলেছে, PC + 4-এর পথ ধরে। Branch নেওয়া হলে সেই দুটি ভুল, ফেলে দিতে হয়:
beq x1, x2, L # outcome known in EX (cycle 3)
addi x3, x0, 1 # already fetched in cycle 2 -> flush if taken
addi x4, x0, 2 # already fetched in cycle 3 -> flush if taken
L: sub x5, x6, x7
প্রতিটি নেওয়া branch-এ এভাবে কয়েকটি cycle নষ্ট হলে খরচ বিশাল, কারণ সাধারণ প্রোগ্রামে branch প্রায় প্রতি পাঁচ-ছয়টি instruction-এ একটি। সমাধান: branch-এর ফল আগে (ID-তে) বের করা, আর branch prediction — ফলটি অনুমান করে সেই পথে এগিয়ে যাওয়া, ভুল হলে ফিরে আসা (branch penalty). পুরোনো MIPS-এ একটি ভিন্ন কৌশল ছিল, branch delay slot; RISC-V সেটি নেয়নি। বিস্তারিত পর্ব ৩৭-এ (Control hazard আর branch prediction).
Structural = যন্ত্রের অভাব; data = মানের অপেক্ষা; control = পথের অনিশ্চয়তা। তিনটিই stall বা নষ্ট cycle হিসেবে CPI = ১ + stall-এর সূত্রে যোগ হয়। আর pipeline যত গভীর, data আর control hazard-এর খরচ সাধারণত তত বেশি — পরের অংশের মূল কথা।
৯ · Pipeline কত গভীর হবে — Pentium 4 থেকে আজকের core
অংশ ৬-এর শেষ টেবিল বলে, বেশি ধাপ মানে ছোট Tstage আর বেশি clock rate — যদিও ক্রমে কম লাভ। ধাপের সংখ্যাকে বলে pipeline depth, আর খুব বেশি ধাপের নকশাকে কখনো superpipelining বলা হয়। কিন্তু গভীরতার তিনটি দাম আছে:
- Register overhead জমে: প্রতিটি ধাপে ২০ ps-এর মতো স্থির খরচ, আর latency = k × Tstage বাড়তে থাকে।
- Hazard-এর খরচ বাড়ে: branch-এর ফল যত পরের ধাপে জানা যায়, ভুল অনুমানে তত বেশি ধাপের কাজ ফেলতে হয়; load-এর ফল আসতেও বেশি cycle লাগে।
- Power বাড়ে: বেশি register, বেশি clock load, আর উচ্চ frequency (পর্ব ০২-এর power wall).
ইতিহাসের সবচেয়ে বিখ্যাত পরীক্ষা। Intel-এর Pentium 4 (NetBurst, ২০০০) উচ্চ clock-এর জন্য প্রায় ২০ ধাপের pipeline নিয়ে আসে; ২০০৪ সালের Prescott সংস্করণে তা প্রায় ৩১ ধাপ বলে জানা যায়। Clock সত্যিই উঠেছিল — প্রায় ৩.৮ GHz পর্যন্ত — কিন্তু branch-এর ভুল অনুমানের খরচ আর তাপ এত বাড়ে যে পরের Core microarchitecture (২০০৬) প্রায় ১৪ ধাপের তুলনামূলক ছোট pipeline আর কম clock নিয়ে বেশি কাজ-প্রতি-cycle (IPC) দিয়ে তাকে ছাড়িয়ে যায় (পর্ব ০২-এ ইতিহাস আর প্রবণতা)। ধাপ-সংখ্যা গোনার পদ্ধতি নির্মাতা আর উৎসভেদে আলাদা, তাই এই সংখ্যাগুলো আনুমানিক।
সর্বোত্তম গভীরতা — একটি খেলনা মডেল
সহজ একটি মডেল বানানো যায়। মোট logic T, প্রতি ধাপে overhead d, ধাপ k. প্রতি instruction-এর মৌলিক সময় T/k + d. ধরুন h ভাগ instruction (যেমন ভুল অনুমান করা branch) pipeline খালি করে দেয় আর প্রায় k − ১ cycle নষ্ট করে। তাহলে
প্রতি instruction-এর সময় = (T/k + d) · (১ + h·(k − ১))
গুণ করে সাজালে: T(১ − h)/k + d·h·k + (ধ্রুবক)। প্রথম পদ k বাড়লে কমে (ছোট ধাপ), দ্বিতীয় পদ k বাড়লে বাড়ে (hazard আর overhead). k-এর সাপেক্ষে অন্তরকলন শূন্য ধরলে −T(১ − h)/k২ + d·h = ০, অর্থাৎ
kopt = √( T(১ − h) / (d·h) )
অর্থ পরিষ্কার: logic বেশি বা overhead কম হলে গভীর pipeline লাভজনক; hazard বেশি হলে অগভীর। ২০০২ সালের কাছাকাছি প্রকাশিত কয়েকটি গবেষণা (Intel আর IBM-এর গবেষকদের) আরও বিশদ মডেলে একই ধরনের যুক্তি দিয়েছিল, আর সাধারণভাবে বোঝা যায় যে সর্বোত্তম গভীরতা workload-নির্ভর। নিচের script এই খেলনা মডেলেই সব k চেষ্টা করে — এটি কোনো আসল CPU-র পরিমাপ নয়।
// TOY MODEL (not a real CPU): how deep should a pipeline be?
// T = 800 ps of logic, d = 20 ps register overhead per stage.
// A fraction h of instructions (e.g. mispredicted branches) flushes
// the pipeline and wastes (k - 1) extra cycles.
// time per instruction = (T/k + d) * (1 + h*(k - 1))
const T = 800, d = 20;
const tpi = (k, h) => (T / k + d) * (1 + h * (k - 1));
for (const h of [0, 0.02, 0.05, 0.1, 0.2]) {
let best = 1;
for (let k = 2; k <= 100; k++) if (tpi(k, h) < tpi(best, h)) best = k;
const formula = h > 0 ? Math.sqrt(T * (1 - h) / (d * h)).toFixed(1) : "inf";
console.log(`h=${h.toFixed(2)} best k=${String(best).padStart(3)}` +
` formula sqrt(T(1-h)/(d*h))=${formula.padStart(5)}` +
` time/instr=${tpi(best, h).toFixed(1).padStart(6)} ps` +
` (k=5: ${tpi(5, h).toFixed(1)} ps)`);
}
console.log("\nh = 0.10, time per instruction (ps) for some depths:");
console.log([1, 5, 10, 19, 30, 50].map(k => `k=${k}: ${tpi(k, 0.1).toFixed(1)}`).join(" "));আউটপুট:
h=0.00 best k=100 formula sqrt(T(1-h)/(d*h))= inf time/instr= 28.0 ps (k=5: 180.0 ps)
h=0.02 best k= 44 formula sqrt(T(1-h)/(d*h))= 44.3 time/instr= 71.0 ps (k=5: 194.4 ps)
h=0.05 best k= 28 formula sqrt(T(1-h)/(d*h))= 27.6 time/instr= 114.1 ps (k=5: 216.0 ps)
h=0.10 best k= 19 formula sqrt(T(1-h)/(d*h))= 19.0 time/instr= 173.9 ps (k=5: 252.0 ps)
h=0.20 best k= 13 formula sqrt(T(1-h)/(d*h))= 12.6 time/instr= 277.2 ps (k=5: 324.0 ps)
h = 0.10, time per instruction (ps) for some depths:
k=1: 820.0 k=5: 252.0 k=10: 190.0 k=19: 173.9 k=30: 182.0 k=50: 212.4
h = ০ (কোনো hazard নেই) হলে মডেল বলে “যত গভীর তত ভালো” — খোঁজার সীমা ১০০-তেই থামে, আর সূত্রে অসীম। Hazard একটু থাকলেই উত্তর বদলায়: h = ০.০২-এ সেরা k = ৪৪, h = ০.১০-এ ১৯, h = ০.২০-এ ১৩ — প্রতিবার সূত্রের মানের (৪৪.৩, ১৯.০, ১২.৬) কাছের পূর্ণসংখ্যা। শেষ লাইন দেখায় h = ০.১০-এ বক্ররেখাটি কতটা চ্যাপ্টা: k = ১০-এ ১৯০.০ ps, k = ১৯-এ ১৭৩.৯, k = ৩০-এ ১৮২.০ — সর্বোত্তমের আশপাশে বড় পরিসরে প্রায় একই ফল, তাই নকশাকারীরা power আর জটিলতা দেখে প্রায়ই সর্বোত্তমের চেয়ে কম ধাপ বেছে নেন। মডেলে power নেই — power ধরলে সর্বোত্তম আরও অগভীর হয়।
বাস্তব pipeline — আনুমানিক চিত্র
শেখার জন্য পাঁচ ধাপ, কিন্তু বাস্তব core-এ ধাপ সাধারণত বেশি, কারণ cache বড় আর ধীর, decode জটিল, আর clock উঁচু। সংখ্যাগুলো প্রকাশিত বিবরণ থেকে আনুমানিক, আর নির্মাতাভেদে গোনার নিয়ম আলাদা:
| Core-এর ধরন | আনুমানিক ধাপ | মন্তব্য |
|---|---|---|
| শিক্ষার RISC-V / ক্লাসিক MIPS R2000-ধাঁচ | ৫ | এই পর্বের মডেল |
| ছোট microcontroller core (যেমন কিছু ARM Cortex-M) | ২–৬-এর মতো | কম power, কম hazard-খরচ |
| ফোনের ছোট দক্ষ in-order core (যেমন ARM Cortex-A53) | প্রায় ৮ | In-order, দুটি instruction একসাথে |
| বড় out-of-order ARM ও x86 core | সাধারণত ১০–২০-এর মতো | Branch mispredict-এর খরচ প্রায় ১০–২০ cycle বলে উল্লেখিত |
| Pentium 4 Prescott (২০০৪) | প্রায় ৩১ | গভীর pipeline-এর চরম উদাহরণ |
আরও গভীরে: আধুনিক বড় core-এ “একটি pipeline” আসলে কয়েকটি: একটি ফ্রন্ট-এন্ড (fetch, branch prediction, decode), তারপর out-of-order অংশ, যেখানে বিভিন্ন execution unit-এর নিজস্ব দৈর্ঘ্যের pipeline — integer যোগ ১ cycle, গুণ ৩–৪, floating point আরও বেশি — আর শেষে instruction-গুলো প্রোগ্রামের ক্রমে চূড়ান্ত হয়। এরা প্রতি cycle-এ একাধিক instruction চালায় (superscalar, পর্ব ৩৯) আর ক্রম ভেঙে চালায় (out-of-order, পর্ব ৪০), ফলে CPI ১-এর নিচে নামতে পারে। এই পর্বের পাঁচ ধাপের in-order pipeline সেই সব কিছুর ভিত্তি।
১০ · AI-এর যুগে pipeline-এর হিসাব
AI টুল pipelining-এর ধারণা সুন্দর করে বোঝাতে পারে, কিন্তু numerical-এ চোখে পড়ার মতো ভুল করে। নিচের ভুলগুলো প্রায়ই দেখা যায়:
| AI-এর প্রচলিত ভুল | সঠিক কথা | কীভাবে ধরবেন |
|---|---|---|
| মোট cycle n × k বা n + k বলা | Hazard ছাড়া k + n − ১ | n = ১ বসিয়ে দেখুন — উত্তর k হতে হবে |
| Tstage = গড় ধাপ-delay | সবচেয়ে ধীর ধাপ + register overhead | প্রতিটি ধাপ কি এক clock-এ শেষ হচ্ছে, যাচাই করুন |
| “Pipelining latency কমায়” | Latency একই বা বেশি; throughput বাড়ে | k × Tstage আর single-cycle T তুলনা করুন |
| অসম ধাপেও speedup সীমা = k বলা | সীমা = Tsingle / Tstage | Script-এ n = ১০৬ চালিয়ে দেখুন |
| MIPS-এর branch delay slot বা ধাপের নিয়ম RISC-V-তে বসানো | RISC-V-এ delay slot নেই | RISC-V ISA manual মিলিয়ে দেখুন |
| পুরোনো CPU-র ধাপ-সংখ্যা নিশ্চিত সংখ্যা হিসেবে বলা | প্রকাশিত মান আনুমানিক, গোনার নিয়ম ভিন্ন | নির্মাতার optimization manual বা নির্ভরযোগ্য উৎস দেখুন |
AI-কে এমনভাবে প্রশ্ন করুন যাতে ধরে নেওয়া বিষয় আর প্রতিটি ধাপ লেখা থাকে:
AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- Pipelining কী, আর এটি latency না throughput বাড়ায়? — কাজকে ধাপে ভাগ করে ভিন্ন instruction-এর ভিন্ন ধাপ একসাথে চালানো; throughput বাড়ায়, latency কমায় না।
- k ধাপের pipeline-এ n instruction-এ কত cycle? — Hazard না থাকলে k + n − ১; speedup = n·k / (k + n − ১) → k.
- Pipeline register কেন লাগে? — প্রতিটি ধাপের ফল, rd আর বাকি control signal পরের ধাপের জন্য ধরে রাখতে, যাতে একাধিক instruction একে অপরের মান নষ্ট না করে।
- অসম ধাপে clock period কত? — সবচেয়ে ধীর ধাপ + register overhead; সেই ধাপ ভাগ না করলে বাকিগুলো দ্রুত করে লাভ নেই।
- তিন ধরনের hazard কী? — Structural (একই hardware), data (মান এখনো লেখা হয়নি, RAW), control (branch-এর ফল অজানা)।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Pipelining-এর ধারণা কম্পিউটারে এল কোথা থেকে, আর RISC-এর জন্মের সাথে এর সম্পর্ক কী?
ধারণাটি কারখানার। ১৯১৩ সালে Henry Ford-এর কারখানায় চলমান assembly line চালু হলে একটি গাড়ি বানানোর কাজ ছোট ছোট ধাপে ভাগ হয়ে যায়, আর প্রতিটি কর্মী একটি ধাপ করেন — একটি গাড়ির মোট সময় নয়, কারখানা থেকে গাড়ি বেরোনোর হার বিপুল বাড়ে। আমাদের গার্মেন্টস কারখানার sewing line আজও এই নীতিতে চলে।
কম্পিউটারে instruction-এর কাজ overlap করার চেষ্টা বেশ পুরোনো। ১৯৫০-৬০-এর দশকের কিছু বড় যন্ত্রে — যেমন IBM-এর Stretch প্রকল্পে — পরের instruction আগেভাগে আনার (“look-ahead”) ব্যবস্থা ছিল বলে ইতিহাসে উল্লেখ আছে, আর ১৯৬০-এর দশকের কিছু supercomputer-এ execution unit-গুলো ভেতরে pipelined ছিল। তবে তখন instruction set জটিল ছিল, তাই পুরো instruction-কে সমান ধাপে ভাগ করা কঠিন ছিল।
মোড় ঘোরে ১৯৭০-এর দশকের শেষ আর ১৯৮০-র দশকের শুরুতে। IBM-এ John Cocke-এর 801 প্রকল্প, Berkeley-তে David Patterson-এর RISC I আর Stanford-এ John Hennessy-র MIPS প্রকল্প প্রায় একই সিদ্ধান্তে পৌঁছায়: instruction set এমনভাবে বানাতে হবে, যাতে প্রতিটি instruction একই ছন্দের কয়েকটি ধাপে চলে, আর প্রায় প্রতি cycle-এ একটি instruction শেষ হয়। অংশ ৭-এর চার বৈশিষ্ট্য — নির্দিষ্ট দৈর্ঘ্য, অল্প format, load/store, aligned access — অনেকটাই এই লক্ষ্যে। MIPS প্রকল্প থেকে আসা R2000 প্রসেসর (১৯৮০-র দশকের মাঝামাঝি) পাঁচ ধাপের pipeline-এর ক্লাসিক উদাহরণ হয়ে ওঠে, আর Patterson ও Hennessy-র পাঠ্যবই এই মডেলকে প্রজন্মের পর প্রজন্ম শিক্ষার্থীর কাছে পৌঁছে দেয় — RISC-V সংস্করণে আজও একই কাঠামো।
CISC-ও পিছিয়ে থাকেনি: Intel-এর 80486 (১৯৮৯) integer অংশে পাঁচ ধাপের pipeline ব্যবহার করে বলে জানা যায়, আর পরে micro-op অনুবাদ জটিল x86-কেও গভীর pipeline-এ চালানো সম্ভব করে।
মূল উপলব্ধি: Pipelining কোনো একক আবিষ্কার নয়, বরং একটি পুরোনো কারখানা-নীতি; RISC-এর বড় অবদান ছিল instruction set-কে এমনভাবে নকশা করা যাতে এই নীতি সরল, দ্রুত আর সস্তা hardware-এ খাটে।
প্র ০২x86-এর instruction এত জটিল হলেও আধুনিক x86 প্রসেসর কীভাবে গভীর pipeline চালায়, আর তার দাম কী?
অংশ ৭-এ দেখেছেন x86-এর তিনটি সমস্যা: ১ থেকে ১৫ বাইটের পরিবর্তনশীল দৈর্ঘ্য, memory operand-সহ গণনা (একটি instruction-এ পড়া-হিসাব-লেখা), আর অনেক বিরল জটিল instruction. সরাসরি পাঁচ ধাপে এদের বসানো যায় না।
১৯৯০-এর দশকের মাঝামাঝি থেকে Intel আর AMD একটি অনুবাদ-স্তর যোগ করে। Intel-এর Pentium Pro (১৯৯৫) এর পরিচিত উদাহরণ: ফ্রন্ট-এন্ডের decoder প্রতিটি x86 instruction-কে এক বা একাধিক সরল micro-op-এ ভাঙে — যেমন add [rbx+8], eax হয়ে যায় একটি load, একটি যোগ আর একটি store-এর micro-op. ভেতরের pipeline শুধু এই RISC-ধাঁচের micro-op দেখে। বিরল জটিল instruction-এর জন্য একটি microcode ROM micro-op-এর পুরো ক্রম সরবরাহ করে — পর্ব ৩৪-এর microprogramming-এর আধুনিক উত্তরসূরি।
দৈর্ঘ্যের সমস্যা সামলাতে decoder আগে instruction-এর সীমানা খোঁজে — প্রায়ই অনুমান করে একাধিক বাইট-অবস্থান থেকে একসাথে decode শুরু করে, যা বেশ বড় circuit. এই খরচ কমাতে আধুনিক core-গুলো decode করা micro-op একটি ছোট cache-এ রাখে, যাতে ঘন ঘন চলা loop-এ decoder বিশ্রাম পায় আর power বাঁচে। উল্টো দিকে, পাশাপাশি দুটি instruction (যেমন compare আর branch) একটি micro-op-এ জুড়ে দেওয়ার কৌশলও (macro-op fusion) ব্যবহার হয়।
দাম কী? প্রথমত, বাড়তি ধাপ: decode-এর জন্য কয়েকটি ধাপ বেশি, তাই branch-এর ভুল অনুমানের খরচও বেশি। দ্বিতীয়ত, power আর chip-এর জায়গা: decoder আর micro-op cache-এর জন্য transistor. ছোট, কম-power-এর core-এ এই খরচ আনুপাতিকভাবে বড় — এটি একটি কারণ যে ফোনে ARM-এর আধিপত্য বলে সাধারণভাবে ব্যাখ্যা করা হয়, যদিও ব্যবসা, ইকোসিস্টেম আর নকশার আরও অনেক কারণও আছে। বড় সার্ভার core-এ, যেখানে cache আর execution unit বিশাল, decode-এর বাড়তি খরচ মোটের তুলনায় ছোট।
মূল উপলব্ধি: ISA আর microarchitecture আলাদা স্তর। x86 বাইরের দিকে CISC-ই রয়ে গেছে পুরোনো software চালানোর জন্য, কিন্তু ভেতরে RISC-ধাঁচের pipeline চালায় — জটিলতার দাম দেয় ফ্রন্ট-এন্ডে, ধাপ, transistor আর power দিয়ে।
প্র ০৩Pipeline-এ প্রতিটি instruction-এর latency বেড়ে যায় — তবু কেন এটি প্রায় সব প্রসেসরে? কোথায় latency-ই আসল?
অংশ ৬-এর উদাহরণে একটি instruction-এর latency ৮২০ থেকে ১,১০০ ps হয়েছে — ৩৪% বেশি। তবু প্রোগ্রাম প্রায় ৩.৭ গুণ দ্রুত। কারণ একটি প্রোগ্রাম কোটি কোটি instruction-এর স্রোত, আর মোট সময় = instruction-সংখ্যা ÷ throughput (মোটামুটি)। ঢাকার মেট্রো রেলের কথা ভাবুন: উত্তরা থেকে মতিঝিল একটি ট্রেনের যাত্রা-সময় নির্দিষ্ট, কিন্তু দিনে কত যাত্রী বহন হবে তা ঠিক করে কত ঘন ঘন ট্রেন ছাড়ে। প্রতি কয়েক মিনিটে ট্রেন ছাড়লে একটি ট্রেনের যাত্রা-সময় না কমিয়েও বহনক্ষমতা বিপুল বাড়ে।
তবে কিছু জায়গায় latency-ই আসল, আর সেখানে pipeline-এর খরচ সত্যিকারের। প্রথমত, নির্ভরশীল instruction-এর শিকল: যদি প্রতিটি instruction আগেরটির ফলের ওপর নির্ভর করে (যেমন linked list ধরে হাঁটা — একটি load-এর ফল পরের load-এর ঠিকানা), তবে overlap করার কিছু নেই; তখন প্রতিটি ধাপের latency সরাসরি জমে। এটিই data hazard-এর মূল, আর forwarding এই latency কমানোর উপায় (পর্ব ৩৬)।
দ্বিতীয়ত, branch-এর ফল: branch যত দেরিতে নিষ্পত্তি হয়, তত বেশি কাজ ঝুঁকিতে। Pipeline গভীর হলে branch থেকে fetch-এ ফিরে আসার latency বাড়ে — অংশ ৯-এর খেলনা মডেলের h·(k − ১) পদ।
তৃতীয়ত, real-time আর নিয়ন্ত্রণ ব্যবস্থা: গাড়ির ব্রেক-নিয়ন্ত্রক বা কারখানার মোটর-কন্ট্রোলারে একটি interrupt-এর জবাব কত দ্রুত আসে, তা গড় throughput-এর চেয়ে গুরুত্বপূর্ণ। এজন্য এসব microcontroller-এ প্রায়ই ছোট, অগভীর pipeline রাখা হয় — যাতে interrupt-এ সাড়া দিতে কম cycle লাগে আর সময় অনুমানযোগ্য থাকে (পর্ব ৩৮-এ exception আর interrupt).
আর একটি প্রায়ই-ভুলে-যাওয়া বিষয়: pipeline throughput বাড়ায় তখনই, যখন pipeline ভরা থাকে। অংশ ৫-এর stepper-এ n = ১-এ speedup ১, আর অংশ ৬-এ ০.৭৪৫ — ছোট, বারবার থেমে যাওয়া কাজে লাভ কম।
মূল উপলব্ধি: Pipelining একটি লেনদেন — একক কাজের latency দিয়ে কেনা হয় স্রোতের throughput. যেখানে কাজগুলো স্বাধীন আর অনেক, সেখানে এটি বিশাল জয়; যেখানে প্রতিটি কাজ আগেরটির অপেক্ষায়, সেখানে latency-ই রাজা।
প্র ০৪Structural hazard এড়াতে instruction আর data-র জন্য আলাদা cache কেন, অথচ main memory একটিই — এটি কি Harvard না von Neumann?
দুটোই — স্তরভেদে। ইতিহাসে Harvard Mark I (১৯৪৪) যন্ত্রে instruction আর data আলাদা মাধ্যমে রাখা হতো, সেখান থেকে “Harvard architecture” নাম। অন্যদিকে von Neumann-এর ১৯৪৫ সালের প্রতিবেদনে বর্ণিত stored-program ধারণায় instruction আর data একই memory-তে — যাতে প্রোগ্রামকেও ডেটার মতো লোড, বদল আর তৈরি করা যায় (compiler, loader, JIT সবই এর ওপর নির্ভর করে)।
Pipeline-এ এই দুই ধারণার টানাপোড়েন স্পষ্ট হয়। প্রতি cycle-এ IF ধাপ একটি instruction আনে, আর গড়ে প্রায় প্রতি তিন-চারটি instruction-এর একটি load বা store — সেটি একই cycle-এ MEM-এ memory চায়। Memory একটিই আর port একটিই হলে প্রায় প্রতিটি load/store-এ একটি cycle stall হতো: পর্ব ০৩-এর iron law-এ CPI প্রায় ১.৩-এর মতো হয়ে যেত, শুধু এই একটি কারণে।
সমাধান “modified Harvard”: CPU-র সবচেয়ে কাছে, L1 স্তরে, আলাদা instruction cache আর data cache — প্রতিটির নিজস্ব port, তাই IF আর MEM একই cycle-এ নির্বিঘ্নে চলে। কিন্তু L2, L3 আর DRAM unified — একটিই ঠিকানা-জগৎ, software-এর চোখে একটিই memory. তাই প্রোগ্রামার von Neumann মডেল দেখেন, আর pipeline পায় Harvard-এর গতি। আজকের প্রায় সব সাধারণ-কাজের CPU এভাবেই বানানো।
এর একটি সূক্ষ্ম পরিণতি আছে: কোনো প্রোগ্রাম যদি নিজেই নতুন code লেখে (যেমন JavaScript-এর JIT compiler), সেই code প্রথমে data cache-এ যায়, অথচ IF পড়ে instruction cache থেকে। তাই নতুন code চালানোর আগে দুই cache-কে মিলিয়ে নিতে হয়। RISC-V-এ এর জন্য fence.i নামের একটি instruction আছে; x86 এই সমন্বয় hardware-এ অনেকটাই নিজে করে, আর ARM-এ নির্দিষ্ট cache-maintenance instruction লাগে।
মূল উপলব্ধি: Structural hazard দূর করার সবচেয়ে সরল উপায় বাড়তি hardware. আলাদা L1 cache সেই বাড়তি hardware, যা software-এর সরল মডেল না ভেঙেই pipeline-কে প্রতি cycle-এ দুটি memory access দেয়।
প্র ০৫Pipeline-এর নীতি CPU-র বাইরে কোথায় কোথায় কাজ করে, আর সেখানেও কি একই সূত্র খাটে?
প্রায় সর্বত্র, আর সূত্রগুলো হুবহু একই — কারণ এগুলো কোনো circuit-এর নয়, যেকোনো ধাপে-ভাগ-করা কাজের সূত্র।
Unix pipe: ১৯৭০-এর দশকের শুরুতে Bell Labs-এ Doug McIlroy-র প্রস্তাবে Unix-এ pipe যোগ হয় — cat log | grep error | sort | uniq -c-এ চারটি প্রোগ্রাম একসাথে চলে, প্রতিটি আগেরটির output পড়তে পড়তে কাজ করে। মোট সময় ঠিক করে সবচেয়ে ধীর প্রোগ্রামটি — অংশ ৬-এর max(ধাপ) নিয়ম; আর sort-এর মতো যে ধাপ সব ইনপুট না পেয়ে output দিতে পারে না, সে pipeline-কে “ভেঙে” দেয় — একধরনের structural বাধা।
GPU-র graphics pipeline: vertex processing, rasterization, pixel shading, blending — প্রতি সেকেন্ডে লক্ষ লক্ষ ত্রিভুজ ও pixel এই ধাপগুলো দিয়ে স্রোতের মতো বয়ে যায় (পর্ব ৫৫)। এখানে latency (একটি frame কত দেরিতে পর্দায়) আর throughput (সেকেন্ডে কত frame) দুটোই মাপা হয়, আর গেমাররা এই পার্থক্য ভালো বোঝেন।
Network আর ব্যাংকের লেনদেন: একটি router-এর packet-processing ধাপগুলো (header পড়া, routing table খোঁজা, সারিতে রাখা) প্রায়ই hardware-এ pipelined. একটি কাল্পনিক মোবাইল ব্যাংকিং সেবার লেনদেন প্রক্রিয়াও একইভাবে ভাবা যায়: প্রতিটি অনুরোধ যাচাই, হিসাব-হালনাগাদ আর নিশ্চিতকরণ-বার্তা পাঠানোর ধাপে ভাগ হলে, একটি লেনদেন যখন বার্তার ধাপে, পরেরটি তখন হিসাবের ধাপে আর তার পরেরটি যাচাইয়ের ধাপে থাকতে পারে — আর সবচেয়ে ধীর ধাপটিই ঠিক করে সেকেন্ডে কতটি লেনদেন শেষ হবে।
Compiler আর build system: একটি বড় প্রকল্পে একটি ফাইল যখন compile হচ্ছে, আগেরটি তখন optimize বা link-এর ধাপে। আর একটি CPU-র ভেতরেও pipeline-এর ভেতরে pipeline — floating-point গুণের unit নিজেই কয়েক ধাপের।
সব জায়গায় একই তিন সমস্যা ফিরে আসে: অসম ধাপ (ধীরতম ধাপ গতি ঠিক করে), হাতবদলের খরচ (register overhead-এর মতো, যেমন process-এর মাঝে ডেটা কপি), আর নির্ভরতা (hazard).
মূল উপলব্ধি: k + n − ১, max(ধাপ) + overhead আর latency বনাম throughput — এই পর্বের তিনটি ধারণা একটি সাধারণ প্রকৌশল-ভাষা। CPU-তে শিখলে আপনি software, network আর কারখানার pipeline-ও একই চোখে মাপতে পারবেন।
অনুশীলন
পরীক্ষার ধাঁচে লেখা — আগে খাতায় সমাধান করুন, তারপর উত্তর মিলিয়ে নিন।
-
অসম ধাপে pipeline-এর সময় আর speedup (numerical): একটি পাঁচ ধাপের প্রসেসরে ধাপের delay IF = ২৫০, ID = ১৫০, EX = ৩০০, MEM = ২০০, WB = ১০০ ps; প্রতিটি register-এর overhead ২৫ ps (single-cycle নকশাতেও একবার)। Hazard নেই। (ক) দুই নকশার clock period, (খ) একটি instruction-এর latency, (গ) ১০০টি instruction-এর মোট সময় আর speedup, (ঘ) n → ∞-এ speedup-এর সীমা, (ঙ) pipelined নকশার সর্বোচ্চ throughput বের করুন।
- (ক) Single-cycle: ২৫০ + ১৫০ + ৩০০ + ২০০ + ১০০ + ২৫ = ১,০২৫ ps. Pipelined: max = ৩০০ (EX), তাই Tstage = ৩০০ + ২৫ = ৩২৫ ps.
- (খ) Single-cycle ১,০২৫ ps; pipelined ৫ × ৩২৫ = ১,৬২৫ ps (বেশি)।
- (গ) Single: ১০০ × ১,০২৫ = ১,০২,৫০০ ps. Pipelined: (৫ + ১০০ − ১) × ৩২৫ = ১০৪ × ৩২৫ = ৩৩,৮০০ ps. Speedup = ১,০২,৫০০ / ৩৩,৮০০ ≈ ৩.০৩.
- (ঘ) ১,০২৫ / ৩২৫ ≈ ৩.১৫ — ৫ নয়, কারণ EX বাকিদের চেয়ে অনেক ধীর আর overhead পাঁচবার।
- (ঙ) প্রতি ৩২৫ ps-এ একটি: ১ / ৩২৫ ps ≈ ৩.০৮ × ১০৯ instruction/s.
-
Pipeline diagram আঁকুন (trace by hand): পাঁচ ধাপের (IF, ID, EX, MEM, WB) pipeline-এ পরপর ৬টি স্বাধীন instruction I1–I6 (কোনো hazard নেই)। (ক) মোট কত cycle? (খ) cycle ৪-এ কোন ধাপে কোন instruction? (গ) কোন কোন cycle-এ pipeline পূর্ণ? (ঘ) I6 কোন cycle-এ WB করে?
c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 I1 IF ID EX MEM WB I2 IF ID EX MEM WB I3 IF ID EX MEM WB I4 IF ID EX MEM WB I5 IF ID EX MEM WB I6 IF ID EX MEM WB- (ক) k + n − ১ = ৫ + ৬ − ১ = ১০ cycle.
- (খ) c4: I4 IF-এ, I3 ID-তে, I2 EX-এ, I1 MEM-এ; WB খালি।
- (গ) সব ধাপ ব্যস্ত হয় যখন c ≥ k আর c ≤ n: c5 আর c6. (সাধারণভাবে পূর্ণ cycle-এর সংখ্যা n − k + ১, যদি n ≥ k.)
- (ঘ) I6 IF-এ ঢোকে c6-এ, তাই WB c6 + ৪ = c10.
-
কোন ধাপ ভাগ করবেন (design choice): অনুশীলন ১-এর প্রসেসরে (২৫০/১৫০/৩০০/২০০/১০০ ps, overhead ২৫ ps) (ক) ঠিক একটি ধাপকে দুই সমান ভাগে ভাগ করার অনুমতি পেলে কোনটি ভাগ করবেন, নতুন Tstage আর speedup-এর সীমা কত? (খ) দুটি ধাপ ভাগ করা গেলে? (গ) (খ)-এর নকশায় একটি instruction-এর latency কত?
- (ক) সবচেয়ে ধীর EX (৩০০) ভাগ করুন → ১৫০ + ১৫০. নতুন ধাপ: ২৫০, ১৫০, ১৫০, ১৫০, ২০০, ১০০; max = ২৫০ (IF) → Tstage = ২৭৫ ps, সীমা ১,০২৫ / ২৭৫ ≈ ৩.৭৩. অন্য কোনো ধাপ ভাগ করলে max ৩০০-ই থাকত — কোনো লাভ নেই।
- (খ) EX আর IF (২৫০ → ১২৫ + ১২৫) ভাগ করুন → ধাপ ১২৫, ১২৫, ১৫০, ১৫০, ১৫০, ২০০, ১০০; max = ২০০ (MEM) → Tstage = ২২৫ ps, সীমা ১,০২৫ / ২২৫ ≈ ৪.৫৬.
- (গ) ৭ ধাপ × ২২৫ = ১,৫৭৫ ps — মূল pipeline-এর ১,৬২৫ ps-এর চেয়ে একটু কম, কারণ Tstage অনেক কমেছে। তবে ধাপ ৫ থেকে ৭ হওয়ায় বাস্তবে hazard-এর খরচ বাড়বে (অংশ ৯)।
-
আউটপুট অনুমান করুন: অংশ ৬-এর calculator-এ (REG = ২০ ps) নিচের লাইনটি যোগ করলে কোন দুটি লাইন ছাপবে? Run না করে হিসাব করুন।
analyse("4-stage", [300, 250, 250, 200], [10]);4-stage: k=4 Tstage=320 ps latency=1280 ps single-cycle Tc=1020 ps n=10 pipelined 4160 ps single 10200 ps speedup 2.452 throughput 2.404 instr/nsTstage = ৩০০ + ২০ = ৩২০ ps; latency = ৪ × ৩২০ = ১,২৮০ ps; single-cycle = ১,০০০ + ২০ = ১,০২০ ps. n = ১০: pipelined (৪ + ১০ − ১) × ৩২০ = ১৩ × ৩২০ = ৪,১৬০ ps; single ১০ × ১,০২০ = ১০,২০০ ps; speedup = ১০,২০০ / ৪,১৬০ ≈ ২.৪৫২; throughput = ১০ / ৪,১৬০ ps ≈ ২.৪০৪ instruction প্রতি ns. সীমা হতো ১,০২০ / ৩২০ ≈ ৩.১৯ — n = ১০ এখনো ভরা-খালির খরচে অনেকটা পিছিয়ে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমপাঁচ ধাপের RISC-V pipeline-এ lw x5, 8(x2)-এর কাজগুলো যে ক্রমে ঘটে, সেই ক্রমে সাজান (▲▼ চেপে সরান)
- IF: PC-র ঠিকানা থেকে instruction আনা আর PC + 4 হিসাব
- ID: x2 পড়া, imm = 8 তৈরি আর control signal বানানো
- EX: ALU-তে ঠিকানা = x2 + 8 হিসাব
- MEM: data memory থেকে সেই ঠিকানার word পড়া
- WB: পড়া মান x5-এ লেখা
একটি পাঁচ ধাপের pipeline-এ ধাপের delay ১৫০, ২০০, ২৫০, ২০০ আর ১৫০ ps, আর প্রতিটি pipeline register-এর overhead ১০ ps. Clock period কত হবে?
- ২০০ ps
- ২৫০ ps
- ২৬০ ps
- ১,০০০ ps
পাঁচ ধাপের একটি pipeline-এ ২০টি পরস্পর-স্বাধীন instruction (কোনো hazard বা stall নেই) চালাতে মোট কত clock cycle লাগে?
- ১০০
- ২৪
- ২৫
- ২০
k-টি সমান ধাপের একটি pipeline-এ register overhead আর hazard নেই। Instruction-এর সংখ্যা n অসীমের দিকে গেলে single-cycle-এর তুলনায় speedup কোন মানের দিকে যায়?
- ১
- k − ১
- ২k
- k
একটি pipelined প্রসেসরে instruction আর data-র জন্য একটিমাত্র memory port আছে। একই cycle-এ একটি lw MEM ধাপে আর আরেকটি instruction IF ধাপে memory চায়। এটি কোন ধরনের hazard, আর প্রচলিত সমাধান কী?
- Structural hazard; আলাদা instruction cache আর data cache
- Data hazard; forwarding
- Control hazard; branch prediction
- কোনো hazard নয়; clock ধীর করলেই চলে
RISC-V-এর মতো ISA pipeline-এর জন্য x86-এর চেয়ে সহজ — এর একটি প্রধান কারণ কোনটি?
- RISC-V-এ instruction-এর মোট সংখ্যা খুব কম
- Instruction-এর দৈর্ঘ্য নির্দিষ্ট আর register-field একই জায়গায়, তাই fetch আর decode সরল
- RISC-V-এ register কম, তাই register file ছোট
- RISC-V প্রোগ্রামে কোনো hazard হয় না
একটি পাঁচ ধাপের pipeline-এর আদর্শ CPI ১. একটি প্রোগ্রামে ৩০% instruction load, আর তার এক-তৃতীয়াংশের পরে ১ cycle stall হয়; অন্য কোনো stall নেই। কার্যকর CPI কত?
- ১.০
- ১.৩
- ১.১
- ০.৯
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Pipelining = throughput: কাজকে ধাপে ভাগ করে ভিন্ন instruction-এর ভিন্ন ধাপ একসাথে; latency একই বা বেশি, কিন্তু আদর্শে প্রতি cycle-এ একটি instruction শেষ (CPI = ১)।
- পাঁচ ধাপ ও চার register: IF, ID, EX, MEM, WB; IF/ID, ID/EX, EX/MEM, MEM/WB ডেটা, rd আর control signal বয়ে নেয় — control তৈরি হয় ID-তে, প্রতিটি দল নিজের ধাপে খরচ হয়।
- সময়: (k + n − ১) cycle; S(n) = n·k / (k + n − ১) → k, শুধু সমান ধাপ, শূন্য overhead আর stall ছাড়া।
- বাস্তব clock: Tstage = max(ধাপ) + treg; সীমা Tsingle / Tstage. ২০০/১০০/২০০/২০০/১০০ + ২০ ps → ২২০ ps, latency ১,১০০ ps, সীমা ৩.৭৩. শুধু ধীরতম ধাপ(গুলো) ভাগ করলেই লাভ।
- Hazard ও গভীরতা: structural, data (RAW), control; CPI = ১ + stall. গভীর pipeline-এ clock দ্রুত কিন্তু overhead আর hazard-খরচ বেশি — সর্বোত্তম গভীরতা আছে (Pentium 4-এর প্রায় ৩১ ধাপ থেকে পরে প্রায় ১৪-তে ফেরা)।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩৬ · Pipeline hazard — structural, data hazard, forwarding আর stallপরবর্তী পর্বHazard-এর বিস্তারিত আর তাদের সমাধান।
- পর্ব ৩২ · Single-cycle datapath — একটি instruction-এর পুরো পথআগে পড়ুনযে datapath-কে এখানে পাঁচ টুকরো করা হয়েছে।
- পর্ব ১৮ · Timing আর clock — propagation delay, setup-hold, critical path আর metastabilityআগে পড়ুনRegister overhead (tpcq + tsetup) কোথা থেকে আসে।
- কম্পিউটার আর্কিটেকচার ও ডিজিটাল লজিক · কোর্স হোম৬২টি পর্বলজিক গেট থেকে pipeline আর cache — পুরো রোডম্যাপ।
- কম্পিউটার ফান্ডামেন্টালস · CPU কীভাবে কাজ করেপাশের ট্র্যাকশুরুর স্তরে fetch-decode-execute.
- C প্রোগ্রামিং · পারফরম্যান্স আর cacheপাশের ট্র্যাকProgrammer-এর চোখে CPU-র গতি।