Fetch-Decode-Execute Cycle — a = b + c সিলিকনে ঠিক কী ঘটায়
Fetch-Decode-Execute Cycle
`a = b + c` লিখলে compiler চারটা RV32I instruction বানায় (দুইটা lw, একটা add, একটা sw)। প্রতিটা instruction CPU-তে একই পাঁচ ধাপ পার হয় — FETCH (PC থেকে instruction memory-তে পড়া, PC+4), DECODE (opcode থেকে control signal, register file read), EXECUTE (ALU-তে গণনা — ঠিকানা অথবা প্রকৃত যোগফল), MEMORY (load/store হলে data memory), WRITEBACK (register file-এ ফলাফল)। এই চক্র প্রতি clock cycle-এ পুনরাবৃত্ত হয় — এটাই Level ২-র toy CPU-র হুবহু সেই datapath+control, শুধু এবার একটা real ISA চালাচ্ছে।
আগে এটা বুঝি
এই পুরো মডিউলের driving question ছিল একটা লাইন কোড:
তিনটা অক্ষর আর দুইটা অপারেটর — অথচ এই প্রশ্নটাই এই মডিউলের প্রতিটা
আগের লেসনকে ন্যায্যতা দিয়েছে। ISA কী তা না জানলে বুঝতাম না b, c,
a কোথায় থাকে (registers-pc-flags লেসন)। Instruction encoding না
জানলে বুঝতাম না add t3, t1, t2 আসলে 0x00730E33 কেন (গত-আগের
লেসন)। Addressing mode না জানলে বুঝতাম না -8(s0)-এর মানে কী (গত
লেসন)। আর Level ২-র পুরো digital-logic মডিউল না থাকলে জানতাম না ALU,
register file, memory — এসবের ভেতরে আসলে কী আছে।
আজ, শেষ পর্যন্ত, আমরা এই সবকিছু জোড়া লাগিয়ে সম্পূর্ণ উত্তরটা দেব। এক লাইন কোড থেকে শুরু করে, প্রতিটা স্তর নেমে, একদম silicon পর্যন্ত।
মূল ধারণা
ধাপ ১ — C থেকে assembly
int a, b, c;
b = 7;
c = 35;
a = b + c;b, c, a — তিনটা স্থানীয় ভেরিয়েবল, একটা ফাংশনের স্ট্যাক
ফ্রেমে থাকে (addressing-modes লেসনের base+displacement আলোচনা মনে
করুন)। ধরুন compiler frame pointer s0-এর সাপেক্ষে এদের বসিয়েছে:
b → s0-8, c → s0-12, a → s0-4। GCC-স্টাইল -O0 (কোনো
optimization ছাড়া, প্রতিটা ভেরিয়েবল সত্যিই memory-তে যায়, register-এ
থাকে না) কম্পাইল করলে a = b + c লাইনটার জন্য এই assembly বেরোয়:
lw t1, -8(s0) # t1 = b
lw t2, -12(s0) # t2 = c
add t3, t1, t2 # t3 = b + c
sw t3, -4(s0) # a = t3চারটা instruction — দুইটা load (মেমোরি থেকে register-এ), একটা add (register-register), একটা store (register থেকে মেমোরিতে)। প্রতিটাই গত দুই লেসনে বিস্তারিত আলোচিত প্যাটার্ন — এখানে নতুন কিছু নেই, শুধু একসাথে বসানো।
ধাপ ২ — assembly থেকে machine code
Assembler প্রতিটা mnemonic-কে গত দুই লেসনে শেখা নিয়মে বিটে এনকোড করে। (হাতে-করা derivation-এর জন্য instruction-encoding লেসন দেখুন — এখানে শুধু ফলাফল, verify-করা।)
| Assembly | Format | Hex |
|---|---|---|
lw t1, -8(s0) | I-type | 0xFF842303 |
lw t2, -12(s0) | I-type | 0xFF442383 |
add t3, t1, t2 | R-type | 0x00730E33 |
sw t3, -4(s0) | S-type | 0xFFC42E23 |
এই চারটা ৩২-বিট সংখ্যা — শুধু এই চারটা সংখ্যা — এখন থেকে instruction
memory-তে পরপর চারটা ঠিকানায় বসে থাকবে। ধরুন a=b+c লাইনের প্রথম
instruction ঠিকানা 0x1000-এ শুরু হয়:
| ঠিকানা | Machine code | Instruction |
|---|---|---|
0x1000 | 0xFF842303 | lw t1, -8(s0) |
0x1004 | 0xFF442383 | lw t2, -12(s0) |
0x1008 | 0x00730E33 | add t3, t1, t2 |
0x100C | 0xFFC42E23 | sw t3, -4(s0) |
লক্ষ্য করুন প্রতিটা পরের ঠিকানা আগেরটার থেকে ঠিক 4 বেশি — RV32I-এর
ফিক্সড ৩২-বিট (৪ byte) encoding-এর সরাসরি ফল, instruction-encoding
লেসনের একটা মূল পয়েন্টের বাস্তব প্রকাশ।
ধাপ ৩ — এখন CPU-র কাজ শুরু
এই চারটা সংখ্যা এখন instruction memory-তে বসে আছে, নিষ্ক্রিয় বিট মাত্র। CPU-র কাজ হলো এই বিটগুলোকে একটার পর একটা পড়া, বোঝা, আর কার্যকর করা — আর ঠিক এই কাজটাই বর্ণনা করে fetch-decode-execute cycle:
পাঁচটা ধাপ, একটা loop-এ। প্রতিটা instruction এই পুরো loop একবার পার
হয়। যখন a = b + c-এর চারটা instruction শেষ হবে, এই loop ঠিক চারবার
ঘুরবে — প্রতিবার একটা ভিন্ন instruction নিয়ে।
পুরো ট্রেস, উপর থেকে নিচে
- a = b + cC সোর্স কোডে একটা লাইন — মানুষের পড়ার জন্য লেখা
- কম্পাইলার চারটা RV32I instruction জেনারেট করেlw t1,-8(s0); lw t2,-12(s0); add t3,t1,t2; sw t3,-4(s0)
- অ্যাসেম্বলার প্রতিটা mnemonic-কে ৩২-বিট এনকোড করে0xFF842303, 0xFF442383, 0x00730E33, 0xFFC42E23 — instruction-encoding লেসনের নিয়মে
- বিটগুলো instruction memory-তে পরপর বসে0x1000-0x100C, প্রতিটা ৪ byte দূরে — Level ২-র memory-sram-dram লেসনের addressable array
- FETCH — PC instruction memory-কে address করেবিট বেরিয়ে আসে, IR-এ latch হয়, PC ← PC+4
- DECODE — control unit opcode/funct দেখে signal বানায়RegWrite, MemRead/Write, ALUSrc, ALUOp — একই সাথে register file rs1/rs2 read শুরু হয়
- EXECUTE — ALU গণনা করেlw/sw-এ effective address (base+offset), add-এ প্রকৃত b+c যোগফল — Level ২-র alu-design লেসনের সেই ALU
- MEMORY — শুধু lw/sw-এ data memory accesslw পড়ে, sw লেখে; add-এ এই স্টেজ কিছুই করে না
- WRITEBACK — ফলাফল register file-এ ফেরেlw-তে memory-থেকে-পড়া মান, add-এ ALU-র ফলাফল — MemToReg mux বেছে নেয়
- PC এখন পরের instruction নির্দেশ করছে — cycle আবার শুরুএই লুপ চারবার ঘুরবে চারটা instruction-এর জন্য, তারপর প্রোগ্রামের পরের লাইনে
এই একটা ডায়াগ্রামই এই মডিউলের driving question-এর সম্পূর্ণ উত্তর। বাকি লেসন এই প্রতিটা ধাপকে আলাদা আলাদা করে, গভীরভাবে ব্যাখ্যা করবে।
ভেতরে কী ঘটছে
FETCH — PC থেকে IR পর্যন্ত
PC (Program Counter)-কে registers-pc-flags লেসনে ISA-স্তরে পরিচয় করানো হয়েছিল, আর Level ২-র registers-and-register-files লেসনে স্পষ্ট করা হয়েছিল — PC সাধারণত register file-এর বাইরে, একটা dedicated register। FETCH স্টেজে এই fact-টাই কাজে লাগে:
১. PC-র বর্তমান মান (আমাদের উদাহরণে, প্রথমবার 0x1000) instruction
memory-র address input-এ পাঠানো হয়।
2. Instruction memory — গঠনগতভাবে হুবহু Level ২-র memory-sram-dram
লেসনের array (row decoder + column mux দিয়ে addressed SRAM/DRAM,
কিন্তু এখানে instruction সংরক্ষণ করছে, ডেটা না) — সেই address-এ যা
আছে তা output করে: 0xFF842303।
3. এই ৩২ বিট Instruction Register (IR)-এ latch হয় — ঠিক Level
২-র toy CPU-তে যা “বাইরে থেকে এসে হাজির হয়” ধরে নেওয়া হয়েছিল,
এখন সেই “বাইরে থেকে আসা”-র প্রকৃত উৎস ব্যাখ্যা করা হলো।
4. PC ← PC + 4। পরের instruction-এর ঠিকানা প্রস্তুত হয়ে যায়,
পরের FETCH-এর জন্য।
┌─────────┐
│ PC │──────────────┐
│(register)│ │ address
└────┬────┘ ▼
│ ┌──────────────────┐
│ │ Instruction │
│ │ Memory │
│ │ (SRAM/DRAM array) │
│ └─────────┬──────────┘
│ │ data out (32 bit)
│ ▼
│ ┌──────────┐
│ │ IR │
│ └──────────┘
▼
┌─────────┐
│ + 4 │ (ALU বা dedicated adder)
└────┬────┘
│
▼
নতুন PC মান (পরের clock edge-এ latch হবে)DECODE — বিট থেকে অর্থ, আর সমান্তরাল register read
IR-এ এখন 0xFF842303 (আমাদের প্রথম instruction, lw t1, -8(s0))।
DECODE স্টেজে দুইটা জিনিস সমান্তরালে ঘটে — instruction-encoding
লেসনের সবচেয়ে গুরুত্বপূর্ণ insight-এর সরাসরি প্রয়োগ:
পথ ১ — Control unit। opcode = IR[6:0] = 0000011 পড়ে বোঝে এটা
একটা LOAD (I-type)। funct3 = IR[14:12] = 010 পড়ে বোঝে এটা LW
(word-size load, LB/LH না)। এই তথ্য থেকে control signal তৈরি হয়:
| Signal | মান | মানে |
|---|---|---|
RegWrite | 1 | লোড শেষে register file-এ লেখা হবে |
MemRead | 1 | MEMORY স্টেজে data memory পড়তে হবে |
MemWrite | 0 | কোনো memory write না |
ALUSrc | 1 | ALU-র দ্বিতীয় ইনপুট immediate থেকে আসবে, register থেকে না |
MemToReg | 1 | writeback-এ memory-র ফলাফল যাবে, ALU-র না |
ALUOp | ADD | effective address গণনায় ADD দরকার |
পথ ২ — Register file read, একই সাথে। rs1 = IR[19:15] = 01000 (x8=s0) — instruction-encoding লেসনের ফিক্সড-পজিশন যুক্তি অনুযায়ী,
এই read register file-এ তাৎক্ষণিকভাবে পাঠানো যায়, control unit
এখনো opcode বিশ্লেষণ শেষ করার জন্য অপেক্ষা না করেই — কারণ rs1
সবসময় [19:15]-এ, format যাই হোক। Register file s0-এর মান
আউটপুট করে দেয়।
Immediate generation — একটা তৃতীয়, ছোট সমান্তরাল কাজ। IR-এর
[31:20] বিট (I-type immediate) sign-extend হয়ে -8 তৈরি হয়। এই
ছোট circuit-টাই instruction-encoding লেসনের B/J-type immediate
“স্ক্র্যাম্বলিং” আলোচনার কারণ ছিল — format ভেদে কোন বিট থেকে
immediate বানাতে হবে তা এই circuit-ই ঠিক করে (এখানে সহজ, কারণ
I-type-এ immediate একটানা এক জায়গায়)।
EXECUTE — যেখানে ALU আসল কাজ করে
এখন ALU (Level ২-র alu-design লেসনের সেই ALU, হুবহু একই সার্কিট,
কোনো পরিবর্তন ছাড়া) কাজ করে। ইনপুট: A = register file-থেকে-পড়া rs1-এর মান, B = ALUSrc mux-এর আউটপুট (register rs2 অথবা sign-extended immediate), ALUOp = control unit-থেকে।
Instruction ১ (lw t1, -8(s0)): A = s0-এর মান, B = -8
(immediate, ALUSrc=1), ALUOp = ADD। ALU গণনা করে effective address
= s0 - 8 — এইটাই addressing-modes লেসনের base+displacement গণনা,
এখন সত্যিই একটা ALU-তে ঘটছে।
Instruction ৩ (add t3, t1, t2): A = t1-এর মান (= 7, ধরুন
আগের দুই load-এর পর), B = t2-এর মান (= 35, ALUSrc=0, register
থেকে সরাসরি), ALUOp = ADD। ALU গণনা করে 7 + 35 = 42।
এই মুহূর্তটাই এই পুরো মডিউলের payoff — b + c-এর প্রকৃত যোগফল,
42, এই ALU-র ভেতরে, সেই একই ripple-carry adder circuit-এ (Level ২-র
combinational-adders লেসন) গণনা হচ্ছে যা আমরা transistor থেকে শুরু
করে হাতে বানিয়েছিলাম।
MEMORY — শুধু যখন প্রয়োজন
Instruction ১, ২ (lw): MemRead=1। ALU-র গণনা করা ঠিকানা
(s0-8, তারপর s0-12) data memory-র address input-এ যায় — ঠিক
Level ২-র memory-sram-dram লেসনের সেই একই মেকানিজম (address → row
decoder → column mux → data out), শুধু এবার instruction memory না,
data memory। আউটপুট MDR (Memory Data Register)-এ latch হয়:
MDR ← 7 (instruction ১), তারপর MDR ← 35 (instruction ২)।
Instruction ৩ (add): MemRead=0, MemWrite=0 — এই স্টেজে
data memory-র সাথে কোনো যোগাযোগ হয় না। ALU-র ফলাফল (42) শুধু
পরের স্টেজে এগিয়ে যায়।
Instruction ৪ (sw): MemWrite=1। ALU-র গণনা করা ঠিকানা
(s0-4) data memory-র address-এ যায়, আর t3-এর মান (42, রেজিস্টার
file থেকে সরাসরি পড়া, দ্বিতীয় read port দিয়ে) সেই ঠিকানায় লেখা হয়।
WRITEBACK — ফলাফল ফিরে আসে register file-এ
Instruction ১, ২: RegWrite=1, MemToReg=1 — MDR-এর মান (memory
থেকে পড়া) register file-এর write port দিয়ে লেখা হয়: t1 ← 7,
তারপর t2 ← 35। Write address আসে IR[11:7]-থেকে (rd ফিল্ড) —
আবার সেই ফিক্সড-পজিশন সুবিধা, যেকোনো instruction format-এই rd
একই জায়গায়।
Instruction ৩: RegWrite=1, MemToReg=0 — এবার MDR না, বরং
ALU-র ফলাফল সরাসরি (42) লেখা হয়: t3 ← 42।
Instruction ৪ (sw): RegWrite=0 — S-type instruction-এর কোনো
destination register নেই (instruction-encoding লেসনের S-type
ফরম্যাট মনে করুন — rd ফিল্ডই নেই সেখানে), তাই writeback স্টেজে
কিছুই লেখা হয় না register file-এ। (Data memory-তে লেখা ইতিমধ্যে
MEMORY স্টেজেই ঘটে গেছে।)
PC ──► Instruction Memory ──► IR = 0x00730E33
│
┌───────────────┼────────────────┐
▼ ▼ ▼
opcode[6:0] rs1[19:15]=t1(6) rs2[24:20]=t2(7)
│ │ │
▼ ▼ ▼
Control Unit Register File Register File
RegWrite=1 read t1 read t2
MemRead=0,Write=0 (=7) (=35)
ALUSrc=0, MemToReg=0 │ │
│ └────────┬────────┘
│ ▼
│ ┌───────────────┐
│ │ ALU │ ◄── ALUOp=ADD
│ │ 7 + 35 = 42 │ (alu-design লেসন)
│ └───────┬───────────┘
│ │
│ MEMORY স্টেজ — কিছুই হয় না
│ │
│ ▼
│ MemToReg mux → ALU result (42)
│ │
└────────────► rd = IR[11:7] = t3(28)
│
▼
Register File write port: t3 ← 42Cycle পুনরাবৃত্তি — CPU-র heartbeat
Instruction ৪-এর WRITEBACK (বা MEMORY, যেহেতু store-এ writeback নেই)
শেষ হলে, পরের clock edge-এ PC (যা FETCH স্টেজেই ইতিমধ্যে 0x1010-এ
আপডেট হয়ে গেছে, প্রতিটা instruction fetch-এর সাথে সাথেই) instruction
memory-কে আবার address করে — এবার a=b+c-এর পরের লাইনের instruction।
এই FETCH → DECODE → EXECUTE → MEMORY → WRITEBACK → FETCH → … লুপ
কখনো থামে না, যতক্ষণ প্রোগ্রাম চলছে (বা halt/ecall exit-এর
মতো বিশেষ instruction না আসছে)।
যদি পরের instruction একটা branch হতো — PCSrc mux আর Zero flag
আমাদের চার-instruction sequence-এ কোনো branch নেই, কিন্তু ধরুন
a = b + c-এর ঠিক পরে সোর্স কোডে থাকত if (a == 0) goto skip; —
compiler এটাকে (addressing-modes লেসনের PC-relative আলোচনার সেই
পরিচিত instruction-টাই) এভাবে এনকোড করত:
beq t3, zero, offset # 0x00730463-এর মতোই একটা B-type, t3-কে zero-এর সাথে তুলনাএই instruction একই পাঁচ-স্টেজ কাঠামো পার হয়, শুধু EXECUTE আর তার পরের অংশ একটু ভিন্নভাবে কাজ করে:
EXECUTE-এ দুইটা জিনিস একসাথে ঘটে। ALU দুইটা ভিন্ন গণনা করে সমান্তরালে (Level ২-র alu-design লেসনে “সবগুলো sub-unit সবসময় চলে” নীতির আরেকটা প্রয়োগ) —
১. t3 - zero (বিয়োগ, শুধু Zero flag বের করার জন্য — ফলাফল
নিজে ফেলে দেওয়া হয়) — t3 == 0 কি না জানতে alu-design লেসনের
সেই flag generation সার্কিট ব্যবহার হয়, কোনো আলাদা “compare
circuit” ছাড়াই।
2. PC + offset — effective address, addressing-modes লেসনের
PC-relative গণনা, প্রতিটা branch-এ সবসময় গণনা হয়ে যায়,
শর্ত সত্য হোক বা না হোক (আগের লেসনের Callout মনে করুন)।
একটা নতুন control signal — PCSrc। DECODE স্টেজে control unit
opcode=1100011 (BRANCH) দেখে একটা নতুন সংকেত সক্রিয় করে যা বলে
“এই instruction-এর ফলাফল অনুযায়ী PC-এর পরবর্তী মান নির্ভর করবে
Zero flag-এর উপর।” WRITEBACK স্টেজের বদলে (branch-এ কোনো register
লেখা হয় না, RegWrite=0), একটা ছোট MUX পরের PC নির্বাচন করে:
PCSrc = Branch_signal AND Zero_flag
পরের PC = PCSrc ? (PC + offset) : (PC + 4) PC + 4 (স্বাভাবিক পরের ঠিকানা)
│
▼
┌─────────┐
│ MUX │◄── select = PCSrc = (Branch signal) AND (Zero flag)
└────┬────┘
│
PC + offset (branch taken)
│
▼
পরের clock edge-এ PC-তে latch হবেসম্পূর্ণ single-cycle datapath — সব টুকরো এক ডায়াগ্রামে
এতক্ষণ প্রতিটা স্টেজ আলাদা করে দেখানো হয়েছে। এখানে সবগুলো একসাথে —
এই লেসনের চার-instruction sequence-এর যেকোনো একটা (lw, add, বা
sw) এই একটামাত্র datapath দিয়েই চলে, শুধু control signal ভিন্ন
হয়। এটাই Level ২-র datapath-and-control-unit লেসনের toy datapath-এর
হুবহু বড়-ভাই সংস্করণ — একই “component-গুলো bus দিয়ে জোড়া, control
unit উপরে বসে সিদ্ধান্ত নেয়” নীতি, শুধু toy ৩-instruction ISA-র
বদলে RV32I।
┌─────────┐
┌────►│ +4 │──┐
│ └─────────┘ │ ┌────────┐
┌─────┴───┐ ├─────►│ MUX │──┐
│ PC │ │ │(PCSrc) │ │
└─────┬───┘ │ └────────┘ │
│ ┌────────┘ ▲ │
│ │ PC + branch offset │ │
▼ │ (addressing-modes │ │
┌──────────────┐ │ লেসন, PC-relative) │ │
│ Instruction │ │ │ │
│ Memory │ │ │ │
└──────┬───────┘ │ │ │
│ IR (32 bit) │ │ │
┌───────────────────┼──────────────┴─────────────┐ │ │
▼ ▼ ▼ │ │
opcode[6:0] rs1[19:15] rs2[24:20] │ │
funct3[14:12] │ │ │ │
funct7[31:25] ▼ ▼ │ │
│ ┌──────────────────────────────────────┐ │ │
▼ │ Register File (rs1,rs2 read) │ │ │
┌──────────┐ │ rd = IR[11:7] (write) │ │ │
│ Control │ └───┬────────────────────────┬──────────┘ │ │
│ Unit │ │ rs1_val │ rs2_val │ │
│(RegWrite, │ │ │ │ │
│ MemRead, │ │ ┌─────┴─────┐ │ │
│ MemWrite, │ │ │ MUX │◄── ALUSrc │ │
│ ALUSrc, │ │ │(imm বনাম │ │ │
│ MemToReg, │ │ │ rs2_val) │ │ │
│ ALUOp, │ │ └─────┬─────┘ │ │
│ Branch) │ │ │ Imm Generator │ │
└───┬──────┘ ▼ ▼ (instruction- │ │
│ ┌─────────────────────────────────┐ encoding লেসন) │ │
│ │ ALU │◄──────────────┘ │
│ │ (alu-design লেসন — Zero flag │ │
│ │ সহ, Branch AND Zero = PCSrc) │ │
│ └───────┬───────────────────┬────────┘ │
│ │ result │ Zero flag ──────────────────┘
│ ▼ │ (branch-এ ব্যবহৃত)
│ ┌───────────────┐ │
│ │ Data Memory │◄────────────┘ address
│ │ (memory-sram- │
│ │ dram লেসন) │
│ └───────┬───────┘
│ │ MemRead হলে data out
│ ▼
│ ┌───────────────┐
│ │ MUX │◄── MemToReg (ALU result বনাম memory data)
│ └───────┬───────┘
│ │
└──────────────────────┴─────────► Register File write port (RegWrite হলে)Single-cycle বনাম multi-cycle — এই ট্রেস কোনটা
এই লেসনের বর্ণনা প্রতিটা instruction-কে পাঁচটা যৌক্তিক ধাপে ভাগ করেছে, কিন্তু এটা বলেনি প্রতিটা ধাপ কয়টা clock cycle নেয়। দুইটা বৈধ বাস্তবায়ন সম্ভব — Level ২-র datapath-and-control-unit লেসনের সেই আলোচনার সরাসরি সম্প্রসারণ:
Single-cycle। প্রতিটা instruction ঠিক এক clock cycle-এ, সব পাঁচটা
স্টেজ combinationally, sequential logic ছাড়াই। Clock period-কে
সবচেয়ে ধীর instruction (এখানে lw/sw, যাদের memory access আছে)
অনুযায়ী যথেষ্ট লম্বা রাখতে হয়।
Multi-cycle। প্রতিটা স্টেজ নিজের clock cycle নেয় — একটা add-এর
লাগে তিন cycle (FETCH, DECODE+EXECUTE, WRITEBACK — MEMORY বাদ, কারণ
দরকার নেই), একটা lw/sw-এর লাগে চার-পাঁচ cycle। এটাই Level ২-র
toy CPU যা করেছিল — শুধু এবার একটা real ISA দিয়ে।
নিচের “example” অংশের cycle-বাই-cycle ট্রেসটা multi-cycle assumption ব্যবহার করছে (Level ২-র toy CPU-র সাথে সরাসরি তুলনীয় রাখতে)। তৃতীয় একটা কৌশল — pipelining — যেখানে ভিন্ন instruction ভিন্ন স্টেজে একই সাথে থাকে (instruction ২ DECODE হচ্ছে যখন instruction ১ EXECUTE হচ্ছে) — Level ৩-র পরের লেসনগুলোর মূল বিষয়, এখানে শুধু নাম উল্লেখ করাই যথেষ্ট।
Critical path — single-cycle হলে clock period কত হতো
Level ২-র alu-design লেসনে আমরা দেখেছিলাম একটা ALU-র critical path কীভাবে তার সবচেয়ে ধীর sub-unit (adder) দিয়ে নির্ধারিত হয়। একটা single-cycle CPU-তে এই একই যুক্তি পুরো datapath জুড়ে বিস্তৃত হয় — পুরো instruction cycle-টাই একটা মাত্র বিশাল combinational circuit, আর তার critical path হলো সবচেয়ে দীর্ঘ instruction-এর সম্পূর্ণ পথ।
| স্টেজ | আনুমানিক gate-delay অবদান (আপেক্ষিক একক) |
|---|---|
| FETCH (instruction memory read) | ~৫ |
| DECODE (control logic + register file read) | ~২ |
| EXECUTE (ALU — সবচেয়ে ধীর অংশ, alu-design লেসনের ~২৩-গেট adder) | ~৬ |
| MEMORY (data memory read/write, শুধু lw/sw-এ) | ~৫ |
| WRITEBACK (MemToReg mux + register file write) | ~২ |
lw/sw-এর জন্য পুরো পথ: 5+2+6+5+2 = 20 একক। add-এর জন্য (MEMORY
স্টেজ বাদ, কারণ ব্যবহৃত হয় না, কিন্তু single-cycle-এ এড়ানো যায় না
— MUX-কে এখনো সিদ্ধান্ত নিতে হয় কোন পথের ফলাফল ব্যবহার হবে, তাই
delay-টা থেকেই যায়): একই 20 একক।
উদাহরণ
সম্পূর্ণ cycle-বাই-cycle ট্রেস — b=7, c=35
Level ২-র toy CPU লেসনের ঠিক সেই একই সংখ্যা (7, 35, ফলাফল 42)
ব্যবহার করছি ইচ্ছাকৃতভাবে — এটা দেখানোর জন্য যে এই লেসনের real RV32I
ট্রেস আর সেই লেসনের toy ৩-instruction ট্রেস আসলে একই গণনার
দুইটা ভিন্ন বাস্তবায়ন।
ধরুন শুরুতে Memory[s0-8] = 7, Memory[s0-12] = 35, PC = 0x1000।
| Cycle | Instruction | স্টেজ | সক্রিয় signal | কী ঘটে |
|---|---|---|---|---|
| 1 | lw t1,-8(s0) | FETCH | — | IR ← 0xFF842303, PC ← 0x1004 |
| 2 | DECODE | MemRead=1, ALUSrc=1 | opcode→LOAD; rs1=s0 পড়া শুরু; imm=-8 sign-extend | |
| 3 | EXECUTE | ALUOp=ADD | ALU: s0 + (-8) → effective address | |
| 4 | MEMORY | MemRead=1 | Memory[addr] পড়ে MDR ← 7 | |
| 5 | WRITEBACK | RegWrite=1, MemToReg=1 | t1 ← MDR = 7 | |
| 6 | lw t2,-12(s0) | FETCH | — | IR ← 0xFF442383, PC ← 0x1008 |
| 7 | DECODE | MemRead=1, ALUSrc=1 | rs1=s0; imm=-12 | |
| 8 | EXECUTE | ALUOp=ADD | ALU: s0 + (-12) | |
| 9 | MEMORY | MemRead=1 | Memory[addr] পড়ে MDR ← 35 | |
| 10 | WRITEBACK | RegWrite=1, MemToReg=1 | t2 ← MDR = 35 | |
| 11 | add t3,t1,t2 | FETCH | — | IR ← 0x00730E33, PC ← 0x100C |
| 12 | DECODE | ALUSrc=0 | rs1=t1 (=7), rs2=t2 (=35) দুইটাই read | |
| 13 | EXECUTE | ALUOp=ADD | ALU: 7 + 35 = 42 | |
| 14 | WRITEBACK | RegWrite=1, MemToReg=0 | t3 ← ALU result = 42 (MEMORY স্টেজ স্কিপ হলো) | |
| 15 | sw t3,-4(s0) | FETCH | — | IR ← 0xFFC42E23, PC ← 0x1010 |
| 16 | DECODE | MemWrite=1, ALUSrc=1 | rs1=s0, rs2=t3 (=42), imm=-4 | |
| 17 | EXECUTE | ALUOp=ADD | ALU: s0 + (-4) → effective address | |
| 18 | MEMORY | MemWrite=1 | Memory[addr] ← t3 = 42 (কোনো WRITEBACK স্টেজ নেই) |
মোট ১৮ cycle, চারটা instruction, একটা সম্পূর্ণ a = b + c গণনা।
শেষে Memory[s0-4] = 42 — অর্থাৎ a = 42 = 7 + 35। ঠিক Level ২-র
toy CPU লেসনের সেই একই ফলাফল, একই সংখ্যা, কিন্তু এবার একটা প্রকৃত,
সম্পূর্ণ ISA-র ভেতর দিয়ে।
নিজে চালিয়ে দেখুন
Ripes-এ এই ঠিক চারটা instruction single-step করুন
১. Ripes ইনস্টল করুন (বা ripes.me ব্রাউজারে খুলুন), processor
model হিসেবে Single-cycle (বা Level ২-র toy CPU-র সাথে
তুলনা করতে চাইলে Ripes-এর 5-stage মডেলও চেষ্টা করা যায়, তবে এই
লেসনের multi-cycle ট্রেসের সাথে single-cycle ধারণাগতভাবে সবচেয়ে
কাছের) বেছে নিন।
2. একটা নতুন asm ফাইলে লিখুন:
.data
b: .word 7
c: .word 35
a: .word 0
.text
main:
la t0, b
lw t1, 0(t0)
la t0, c
lw t2, 0(t0)
add t3, t1, t2
la t0, a
sw t3, 0(t0)(এখানে la/absolute label ব্যবহার করা হয়েছে সরলতার জন্য — এই
লেসনের মূল ট্রেস স্ট্যাক-ভিত্তিক -8(s0) ব্যবহার করেছিল, দুটোই
base+displacement addressing-এর প্রয়োগ, শুধু base ভিন্ন।)
- Assemble করে single-step (F11 বা equivalent) দিয়ে প্রতিটা instruction চালান।
- প্রতিটা ধাপে register view-তে
t1,t2,t3-এর মান লক্ষ্য করুন —t1=7হওয়ার পরইt2পড়া শুরু হয়, ইত্যাদি। - Pipeline/stage view (যদি available) থেকে দেখুন কোন cycle-এ কোন instruction কোন স্টেজে আছে।
- শেষে memory view-তে
a-র ঠিকানায় গিয়ে42আছে কি না যাচাই করুন।
এই লেসনের হাতে-করা ১৮-cycle ট্রেস কোনো কাল্পনিক গল্প না — একটা বাস্তব RV32I simulator-এ ধাপে ধাপে চালালে PC, IR, register, memory ঠিক এই ট্রেস অনুযায়ীই বদলায়।
আসল C প্রোগ্রামে gdb দিয়ে instruction-level single-step
// addbc.c
int main() {
volatile int b = 7, c = 35;
volatile int a = b + c;
return a;
}gcc -O0 -g addbc.c -o addbc
gdb ./addbc
(gdb) break main
(gdb) run
(gdb) stepi # instruction-level single step, লাইন-level না
(gdb) info registers # প্রতিটা stepi-র পর register দেখুন
(gdb) x/4i $pc # পরের চারটা instruction disassemble করে দেখুন(volatile ব্যবহার করা হয়েছে যাতে compiler optimization ভেরিয়েবলগুলো
memory-তেই রাখে, register-এ propagate করে না — এই লেসনের lw/sw
প্যাটার্ন সংরক্ষিত থাকে।) প্রতিটা stepi-র পর PC কীভাবে বদলাচ্ছে,
আর info registers-এ কোন register কখন বদলাচ্ছে লক্ষ্য করুন — এটাই
এই লেসনের FETCH-এর “PC advance” আর WRITEBACK-এর “register বদলানো”-র
বাস্তব, চোখে-দেখা প্রমাণ।
এই লেসনের ট্রেস কোনো টয় উদাহরণ না — একটা প্রকৃত কম্পাইল-করা বাইনারিতেও ঠিক এই একই ধরনের instruction sequence, ঠিক এই একই ধাপে চলে।
নিজে বানান
একটা মিনি RV32I fetch-decode-execute simulator — CPU Emulator প্রজেক্টের বীজ
- instruction-encoding লেসনের decoder ফাংশন পুনর্ব্যবহার করুন (opcode/rd/funct3/rs1/rs2/funct7/immediate বের করা)
- ৩২টা register (list, index 0-31), একটা PC ভেরিয়েবল, আর একটা memory dict (ঠিকানা→byte বা word) বানান — x0 সবসময় 0 থাকতে হবে, লেখা ignore করুন
- একটা fetch(pc, instr_mem) ফাংশন লিখুন — PC থেকে instruction memory-তে ৪ byte পড়ে ৩২-বিট integer রিটার্ন করুন
- একটা decode(word) ফাংশন লিখুন (আগের লেসনের কাজ পুনর্ব্যবহার) — opcode অনুযায়ী একটা control-signal dict রিটার্ন করুন (RegWrite, MemRead, MemWrite, ALUSrc, MemToReg, ALUOp)
- একটা execute(control, rs1_val, rs2_val, imm) ফাংশন লিখুন — ALUSrc অনুযায়ী দ্বিতীয় operand বাছুন, ALUOp অনুযায়ী গণনা করুন
- MEMORY স্টেজ লিখুন — MemRead হলে পড়ুন, MemWrite হলে লিখুন, নাহলে ALU result-ই এগিয়ে যাক
- WRITEBACK স্টেজ লিখুন — RegWrite হলে, MemToReg অনুযায়ী MDR বা ALU result রেজিস্টার ফাইলে লিখুন
- একটা মূল লুপ লিখুন যা FETCH→DECODE→EXECUTE→MEMORY→WRITEBACK বারবার চালায়, PC প্রতিবার +4 করে, আর প্রতি ধাপে register/memory state print করে
- এই লেসনের চারটা instruction (0xFF842303, 0xFF442383, 0x00730E33, 0xFFC42E23) instruction memory-তে বসিয়ে চালান — শেষে t3=42 আর memory-তে a=42 আছে কি না যাচাই করুন
এটাই এই platform-এর “CPU Emulator” প্রজেক্টের প্রথম কার্যকরী সংস্করণ — একটা সত্যিকারের, যদিও ছোট, RV32I interpreter। আপনি যা বানালেন তা আসলে Ripes/QEMU/spike-এর মতো টুলের একটা মিনিয়েচার সংস্করণ, একই মূল লুপ কাঠামো।
একটা গুরুত্বপূর্ণ নকশা সিদ্ধান্ত — স্টেজগুলো আলাদা ফাংশন রাখুন, একটা দৈত্যাকার ফাংশনে গুলিয়ে ফেলবেন না। এটা শুধু পরিষ্কার কোডের জন্যই না — এটা এই লেসনের কেন্দ্রীয় শিক্ষার একটা প্রতিফলন। FETCH, DECODE, EXECUTE, MEMORY, WRITEBACK সত্যিই hardware-এ আলাদা, স্বতন্ত্র সার্কিট (বা multi-cycle-এ আলাদা state) — আপনার কোডের গঠন যদি এই বিভাজনকে প্রতিফলিত করে, ডিবাগ করা অনেক সহজ হবে, আর পরে যখন pipelining শিখবেন (একই স্টেজ, কিন্তু ওভারল্যাপ করে চলছে), এই একই ফাংশনগুলো পুনর্ব্যবহার করা যাবে।
x0 রেজিস্টার একটা বিশেষ কেস। RV32I-এ x0 সবসময় 0 — কোনো
instruction তাতে লিখতে পারে না (নীরবে ignore হয়)। আপনার
write_register(reg, value) ফাংশনে if reg == 0: return (বা
সমতুল্য) যোগ করতে ভুলবেন না, নাহলে এমন bug হবে যা শুধু x0
ব্যবহারকারী প্রোগ্রামেই ধরা পড়বে।
বাস্তব সিস্টেমে
Fetch-decode-execute বাস্তব সিস্টেমে
PicoRV32, SERV — real, synthesizable RV32I core। Level ২-র datapath-and-control-unit লেসনে পরিচিত এই দুইটা open-source core হুবহু এই লেসনের FETCH-DECODE-EXECUTE-MEMORY-WRITEBACK কাঠামো অনুসরণ করে (PicoRV32 multi-cycle-স্টাইল, SERV bit-serial extreme সংস্করণ)। এই লেসনের Python simulator-এর hardware-সংস্করণ, আক্ষরিক অর্থেই।
Ripes, RARS, QEMU, Spike। এই লেসনের experiment-এ ব্যবহৃত টুলগুলো — প্রতিটাই একটা fetch-decode-execute loop চালায়, শুধু C++/Java/Rust-এ লেখা, অনেক বেশি instruction সমর্থন করে, আর প্রায়ই performance-এর জন্য optimize করা (interpretation-এর বদলে JIT compilation, ইত্যাদি)।
GDB-এর stepi। যেকোনো প্রকৃত প্রসেসে single instruction-level
step করার সুবিধা — উপরের experiment-এ ব্যবহৃত — সরাসরি এই cycle-এর
প্রতিটা ধাপ observe করার একটা প্রকৃত পদ্ধতি। প্রতিটা stepi মানে
“একটা পূর্ণ fetch-decode-execute-memory-writeback চক্র পার হও।”
Von Neumann বনাম Harvard architecture। এই লেসনে “instruction memory” আর “data memory” আলাদা বলা হয়েছে সরলতার জন্য — বাস্তবে বেশিরভাগ আধুনিক CPU-তে (এবং RV32I-তে) এরা আসলে একই physical memory-র অংশ (Von Neumann architecture — instruction আর ডেটা একই address space শেয়ার করে; এই কারণেই self-modifying code বা JIT compilation সম্ভব হয়)। কিন্তু performance-এর জন্য, আধুনিক CPU-র L1 cache প্রায়ই আলাদা (L1i instruction cache, L1d data cache) — কার্যকরভাবে Harvard-স্টাইল বিভাজন cache স্তরে চালু করে, DRAM স্তরে Von Neumann একত্রীকরণ বজায় রেখেই। এটা Level ৩-এর পরের cache/memory-hierarchy topics-এর একটা প্রিভিউ।
কম্পাইলার optimization এই ট্রেসকে বদলে দিতে পারে। এই লেসনের
চার-instruction sequence GCC-র -O0 (কোনো optimization না) থেকে
এসেছে। -O2 দিয়ে কম্পাইল করলে সম্ভবত b আর c কখনোই memory-তে
যাবে না — compiler বুঝে যাবে এরা শুধু যোগ করার জন্যই দরকার, তাই
সরাসরি register-এ রেখে দেবে, lw/sw সম্পূর্ণ উধাও হয়ে যেতে পারে,
হয়তো একটা মাত্র add (বা const-folding হলে কোনো instruction-ই না,
যদি b, c compile-time constant হয়)। এই লেসনের ট্রেস তাই “একটা
সম্ভাব্য,” বাস্তবসম্মত compilation, কিন্তু একমাত্র সম্ভাব্য না —
Compiler Explorer-এ -O0 বনাম -O2 পাশাপাশি রেখে দেখা এই পার্থক্য
স্পষ্ট করে দেয়।
Interrupt আর exception — এই লুপে একটা শাখা। Level ৩-এর পরের topics (interrupts, exceptions) আসলে এই একই fetch-decode-execute লুপের একটা সম্প্রসারণ — প্রতিটা instruction cycle শেষে (বা কখনো মাঝে) CPU চেক করে কোনো interrupt pending কি না; থাকলে, স্বাভাবিক পরের PC-তে না গিয়ে একটা বিশেষ “handler” ঠিকানায় লাফ দেয়। কাঠামোগতভাবে এটা এই লেসনের লুপেরই একটা conditional branch, নতুন কোনো ধারণা না।
যে ভুলগুলো সবাই করে
“প্রতিটা instruction ঠিক এক clock cycle নেয়, সবসময়।”
এই লেসনের ১৮-cycle ট্রেস সরাসরি এর বিপরীত দেখায় — lw নিয়েছে ৫
cycle, add নিয়েছে ৪ cycle, sw নিয়েছে ৪ cycle, multi-cycle
design-এ। এমনকি single-cycle design-এও (যেখানে সংজ্ঞা অনুযায়ী প্রতিটা
instruction ঠিক এক cycle নেয়), সেই এক cycle-এর length সব
instruction-এর জন্য সবচেয়ে ধীরতমটার সমান রাখতে হয় — কার্যত ধীরতম
instruction-এর “সময়” প্রতিটা instruction-ই খরচ করে। “এক instruction
= এক নির্দিষ্ট, ছোট সময়” ধারণাটা শুধু pipelined design-এ (steady
state-এ, hazard না থাকলে) কাছাকাছি সত্য হয় — আর সেটাও Level ৩-র
পরের লেসনগুলোর একটা সতর্ক আলোচনার বিষয়।
“DECODE স্টেজ 'কী করতে হবে বুঝে ফেলে', EXECUTE স্টেজ শুধু আনুষ্ঠানিকতা।”
দুটো সম্পূর্ণ ভিন্ন কাজ, ভিন্ন hardware। DECODE শুধু কী operation আর কোন operand ঠিক করে (control signal generation, register read) — কোনো গণনা করে না। EXECUTE-ই প্রকৃত গণনা করে (ALU-তে ADD, বা যেকোনো operation)। এই পার্থক্যটা মিস করলে একটা গুরুত্বপূর্ণ সত্য হারিয়ে যায় — DECODE-এর হার্ডওয়্যার (control unit, মূলত ছোট combinational logic/decoder) সম্পূর্ণ আলাদা সিলিকন এলাকা EXECUTE-এর হার্ডওয়্যার (ALU, adder circuit) থেকে। Level ২-র datapath-and-control-unit লেসনের “datapath বনাম control” বিভাজন এখানে ঠিক এই দুই স্টেজের পার্থক্যে প্রতিফলিত।
“CPU 'জানে' যে এটা a=b+c গণনা করছে, বা কোনোভাবে C কোডের 'অর্থ' বোঝে।”
সম্পূর্ণ ভুল, আর এই লেসনের সবচেয়ে গুরুত্বপূর্ণ misconception এটা।
CPU কখনো a, b, c নামের কোনো ধারণা দেখে না — সে দেখে চারটা
স্বাধীন ৩২-বিট সংখ্যা (0xFF842303, ইত্যাদি), যার প্রতিটা বিটকে
fixed wiring অনুযায়ী register-ঠিকানা, immediate, বা control-signal
হিসেবে ব্যাখ্যা করে। “এই গণনাটা a = b + c represent করে” — এই
অর্থ সম্পূর্ণভাবে মানুষের মাথায় (এবং কম্পাইলারের সোর্স-ম্যাপিং
তথ্যে, ডিবাগ-সিম্বলে) থাকে, silicon-এ কোথাও না। এটা এই platform-এর
“Master Learning Rule”-এর সবচেয়ে concrete প্রমাণ — প্রতিটা উঁচু
স্তরের abstraction নিচের স্তরে গিয়ে সম্পূর্ণ semantic-শূন্য, যান্ত্রিক
বিট-হেরফের হয়ে যায়।
“MEMORY স্টেজ সবসময় কিছু-না-কিছু করে, কারণ এটা 'CPU-র মেমোরি অ্যাক্সেস করার সময়'।”
এই লেসনের add t3,t1,t2 উদাহরণ সরাসরি বিপরীত দেখায় — MEMORY স্টেজে
MemRead=0, MemWrite=0, কোনো data memory access ঘটেই না; ALU-র
ফলাফল শুধু পরের স্টেজে “pass through” হয়ে যায়। মাত্র দুই ধরনের
instruction (load, store) সত্যিই এই স্টেজে memory touch করে — বাকি
সব (arithmetic, logical, branch) এই স্টেজ দিয়ে কিছু না করেই পার
হয়ে যায় (multi-cycle design-এ সেই স্টেজ পুরোপুরি skip-ও হতে পারে,
এই লেসনের ট্রেসে যেমন হয়েছে)।
বুঝেছেন কি না দেখুন
1FETCH স্টেজের তিনটা মূল ফলাফল কী কী?স্মরণ
১. IR-এ নতুন instruction bits latch হয় (instruction memory থেকে,
PC-র বর্তমান মান address হিসেবে ব্যবহার করে)।
2. PC পরের instruction-এর ঠিকানায় advance করে (RV32I-তে PC ← PC + 4, ফিক্সড ৩২-বিট encoding-এর জন্য)।
3. (implicit) কোনো control signal বা register read এখনো হয়নি —
সেসব DECODE স্টেজের কাজ।
2ধরুন PC বর্তমানে 0x2000 নির্দেশ করছে, আর Memory[0x2000] = 0x00A585B3 (এটাও একটা R-type instruction)। FETCH স্টেজের পর PC-র নতুন মান কত হবে?প্রয়োগ
0x2004। FETCH স্টেজে PC সবসময় +4 হয় (RV32I fixed-length এনকোডিং
এর কারণে), instruction-এর content যাই হোক না কেন — এমনকি এই
instruction-টা কী operation করবে তা decode হওয়ার আগেই এই বৃদ্ধি ঘটে
যায় (এই লেসনের “কেন ঠিক +4” Callout দেখুন)।
3DECODE স্টেজে register file read (rs1, rs2) কেন control unit-এর opcode-বিশ্লেষণ শেষ হওয়ার জন্য অপেক্ষা করে না? এটা কোন আগের লেসনের ধারণার সরাসরি প্রয়োগ?যুক্তি
কারণ rs1[19:15] আর rs2[24:20] RV32I-এর প্রতিটা প্রাসঙ্গিক format
(R, I, S, B)-এই হুবহু একই বিট-পজিশনে থাকে — এই তথ্য জানতে
opcode বোঝার দরকার নেই। এটা instruction-encoding লেসনের “ফিক্সড
ফিল্ড পজিশন” insight-এর সরাসরি প্রয়োগ — একটা wire সরাসরি instruction
বিট থেকে register file-এর address input-এ যায়, কোনো MUX বা
“আগে জানো” ধাপ ছাড়াই। ফলে register read আর opcode decode একই clock
edge-এ সমান্তরালে শুরু হতে পারে, critical path ছোট থাকে।
4এই লেসনের চার-instruction sequence-এ কোন instruction-এ ALUSrc=0 (register, immediate না), আর কেন শুধু সেটাতেই?প্রয়োগ
শুধু add t3, t1, t2-এ ALUSrc=0। এটা R-type — দুইটা operand-ই
register (t1, t2), instruction-এ কোনো immediate নেই (গত-আগের
লেসনের R-type ফরম্যাট মনে করুন — শুধু rs2 ফিল্ড, imm ফিল্ড
নেই)। বাকি তিনটা instruction (দুইটা lw, একটা sw) I-type/S-type
— এদের effective address গণনায় একটা register (s0) আর একটা
instruction-এ-এনকোড-করা constant offset (-8, -12, -4) লাগে,
তাই ALUSrc=1 (immediate নির্বাচিত)।
5এই লেসনের ১৮-cycle multi-cycle ট্রেসে add t3,t1,t2-এর MEMORY স্টেজ সম্পূর্ণ বাদ দেওয়া হয়েছে (৪ cycle-ই, ৫ না)। যদি কেউ এই CPU-কে pipeline করতে চায় (একাধিক instruction একই সাথে ভিন্ন স্টেজে), তখন এই “স্টেজ বাদ দেওয়া” কৌশলে কী সমস্যা হবে, আর কেন?ডিজাইন
Pipelining-এ প্রতিটা instruction একই fixed সংখ্যক স্টেজের মধ্য
দিয়ে যায়, কারণ প্রতিটা স্টেজ একই সাথে ভিন্ন instruction-এর জন্য
“দখল” হয়ে থাকে (instruction ১ MEMORY স্টেজে যখন instruction ২
EXECUTE স্টেজে)। যদি add-এর মতো instruction MEMORY স্টেজ সম্পূর্ণ
বাদ দেয় (স্কিপ করে সরাসরি WRITEBACK-এ যায়), তাহলে দুইটা
instruction একই সময়ে একই স্টেজে (WRITEBACK) পৌঁছে যেতে পারে —
একটা structural hazard (দুইটা instruction একই hardware resource,
যেমন register file-এর write port, একই cycle-এ চাইছে)।
সমাধান: pipelined design-এ প্রতিটা instruction, দরকার থাকুক বা না
থাকুক, সব স্টেজের মধ্য দিয়ে যায় — add-ও MEMORY স্টেজ “পার
হয়,” শুধু সেখানে কিছুই করে না (MemRead=0, MemWrite=0, শুধু ALU
result পরের স্টেজে pass-through হয়ে যায়) — ঠিক এই লেসনের “misconception”
অংশের শেষ পয়েন্টে যা উল্লেখ করা হয়েছে। Multi-cycle design স্টেজ
বাদ দিয়ে cycle বাঁচাতে পারে (কারণ সেখানে instruction-গুলো সিরিয়ালি
চলে, ওভারল্যাপ করে না); pipelined design পারে না, কারণ ওভারল্যাপ
করা instruction-দের জন্য প্রতিটা স্টেজের “সময়slot” সবার জন্য সমান
রাখতে হয়।
6“CPU নিজে জানে না এটা a=b+c গণনা করছে” — এই দাবিটা ব্যাখ্যা করুন একটা concrete উদাহরণ দিয়ে: 0x00730E33 bit pattern-টা যদি একটা সম্পূর্ণ ভিন্ন প্রোগ্রামের অংশ হতো (ধরুন x = y + z), CPU-র আচরণে কী পার্থক্য হতো?যুক্তি
কোনো পার্থক্য হতো না, একদম বিটের স্তরে। 0x00730E33 decode হয়
add rd=x28, rs1=x6, rs2=x7 — CPU শুধু জানে “x6 আর x7-এর register
file content যোগ করে x28-এ লেখো।” এটা b+c=a না y+z=x না অন্য
কিছু বোঝাচ্ছে — সম্পূর্ণভাবে নির্ভর করে কোন compiler কোন সোর্স
ভেরিয়েবলকে x6, x7, x28-এ ম্যাপ করেছিল, যে তথ্য শুধু debug
symbol (যদি থাকে) বা কম্পাইলারের নিজস্ব internal bookkeeping-এ
থাকে — machine code-এ নিজে কোথাও এনকোড করা নেই। এই কারণেই stripped
বাইনারি (debug symbol ছাড়া) থেকে original ভেরিয়েবলের নাম উদ্ধার
করা যায় না — শুধু register-এর মধ্যে ডেটা-প্রবাহ পুনর্গঠন করা যায়
(reverse engineering-এর একটা মূল challenge)।
এরপর কী
আমরা এই মডিউলের driving question-এর সম্পূর্ণ উত্তর দিয়েছি —
a = b + c এখন আর একটা রহস্য না। C থেকে assembly, assembly থেকে
বিট, বিট থেকে PC/IR/control-unit/register-file/ALU/memory-এর একটা
নির্দিষ্ট, পুনরাবৃত্ত নাচ — প্রতিটা ধাপ Level ২-এর কোনো না কোনো
লেসনে হাতে-বানানো, আজ শুধু একসাথে জোড়া লাগানো।
কিন্তু এই লেসনের ট্রেসে একটা লুকানো ধরে-নেওয়া ছিল — প্রতিটা instruction সম্পূর্ণভাবে শেষ হওয়ার পরই পরেরটা শুরু হয়েছে, কোনো ওভারল্যাপ ছাড়া। বাস্তব উচ্চ-performance CPU এভাবে চলে না — তারা pipelining ব্যবহার করে, যেখানে instruction ১ এখনো WRITEBACK-এ থাকা অবস্থাতেই instruction ২ ইতিমধ্যে EXECUTE-এ, instruction ৩ DECODE-এ, instruction ৪ FETCH-এ — সবগুলো একই সাথে, ভিন্ন hardware স্টেজে। এটা বিশাল speedup দেয়, কিন্তু নতুন সমস্যাও আনে — hazard: যদি instruction ২-এর একটা operand instruction ১-এর এখনো-না-লেখা ফলাফলের উপর নির্ভর করে, কী হবে?
মেমোরি hierarchy (cache) আর pipelining — এই মডিউলের পরের লেসনগুলোর বিষয় — precisely সেই প্রশ্নগুলোর উত্তর।
আরও পড়ুন
- Computer Organization and Design (RISC-V Edition), Chapter 4 — The Processor — David A. Patterson, John L. Hennessy · RISC-V-ভিত্তিক single-cycle ও pipelined datapath-এর প্রামাণ্য আলোচনা — এই লেসনের কাঠামো সরাসরি এখান থেকে অনুপ্রাণিত
- The RISC-V Instruction Set Manual, Volume I, Chapter 2 — RISC-V International · এই লেসনের চারটা instruction (lw, add, sw)-এর প্রামাণ্য semantics ও এনকোডিং
- Ripes — A visual, graphical 5-stage RISC-V pipeline simulator — Morten Borup Petersen et al. · এই লেসনের experiment অংশে ব্যবহৃত টুল — fetch/decode/execute প্রতিটা স্টেজ visually দেখায়
- The Elements of Computing Systems (nand2tetris) — Noam Nisan, Shimon Schocken · Level ২-এর toy CPU-র spirit-এর মূল উৎস — এই লেসন সেই একই CPU-কে একটা real ISA দিয়ে সম্প্রসারিত করে