এই পর্বে যা শিখবেন
- Single-cycle নকশার cycle time-এর অপচয় কোথায়, আর ধাপে ভাঙলে কীভাবে memory ও ALU ভাগ করে ব্যবহার করা যায়
- Multi-cycle RISC-V datapath-এর প্রতিটি অংশ, পাঁচটি ধাপের register transfer, আর কোন instruction কয় cycle নেয়
- Control-কে finite state machine হিসেবে আঁকা — প্রতিটি state-এর control signal আর next-state logic
- Instruction mix থেকে গড় CPI, IC × CPI × Tc দিয়ে single-cycle-এর সাথে তুলনা — কখন জেতে, কখন হারে
- Microprogrammed control: control store, microinstruction-এর field, micro-PC, dispatch ROM, horizontal বনাম vertical, আর ইতিহাস থেকে আজকের x86
১ · Single-cycle-এর অপচয় থেকে multi-cycle-এর ভাবনা
পর্ব ৩২-এ (Single-cycle datapath) একটি single-cycle datapath দেখেছেন: প্রতিটি instruction একটিমাত্র clock cycle-এ শুরু থেকে শেষ। নিয়মটি সরল, CPI = ১. কিন্তু এর একটি গোপন খরচ আছে। Clock-এর দৈর্ঘ্য সব instruction-এর জন্য একই, তাই cycle-কে সবচেয়ে ধীর instruction-এর পুরো পথের সমান লম্বা রাখতে হয়। RISC-V-এ সেটি সাধারণত lw: instruction memory পড়া, register পড়া, ALU-তে ঠিকানা হিসাব, data memory পড়া, register-এ লেখা — পাঁচটি বড় কাজ পরপর। একটি beq-এর data memory বা register-এ লেখার দরকার নেই, তবু সে একই লম্বা cycle পায়, বাকি সময় বসে থাকে।
একটি রেস্তোরাঁর রান্নাঘর ভাবুন, যেখানে নিয়ম হলো: প্রতিটি অর্ডারের জন্য ঠিক ২০ মিনিট, কারণ সবচেয়ে জটিল পদ — কাচ্চি বিরিয়ানি — বানাতে ২০ মিনিট লাগে। এক কাপ চা-ও তাই ২০ মিনিট পর আসে। অপচয় স্পষ্ট। ভালো নিয়ম হলো সময়কে ৪ মিনিটের ছোট খণ্ডে ভাগ করা: চা নেবে ২ খণ্ড, ভাজি ৩ খণ্ড, বিরিয়ানি ৫ খণ্ড। আর একই চুলা আর একই বাবুর্চি বিভিন্ন খণ্ডে বিভিন্ন কাজ করবেন — প্রতিটি পদের জন্য আলাদা চুলা লাগবে না। তবে একটি শর্ত: এক খণ্ডের আধা-রান্না জিনিস পরের খণ্ড পর্যন্ত কোথাও রাখতে হবে — একটি ট্রে বা বাটি। Multi-cycle প্রসেসরে এই বাটিগুলোই নতুন register.
এই ভাবনা থেকে multi-cycle datapath-এর তিনটি নীতি:
- ধাপে ভাঙা: প্রতিটি instruction-কে কয়েকটি ধাপে ভাঙা, যাতে প্রতিটি ধাপে মোটামুটি একটিই বড় কাজ থাকে — একবার memory-তে যাওয়া, বা একবার register file পড়া/লেখা, বা একবার ALU চালানো। তাহলে ধাপগুলোর delay কাছাকাছি হয়।
- ছোট clock: প্রতিটি ধাপ একটি clock cycle. Cycle time = সবচেয়ে ধীর ধাপের delay, সবচেয়ে ধীর instruction-এর নয়। Instruction যত ধাপ চায় তত cycle নেয় — তাই CPI আর ১ নয়, instruction-ভেদে ৩ থেকে ৫.
- Hardware পুনর্ব্যবহার: Instruction আর data-র জন্য একটিই memory (fetch-এর cycle-এ instruction পড়ে, পরের কোনো cycle-এ data). একটিই ALU — PC + 4, শাখার লক্ষ্য আর আসল হিসাব তিনটিই আলাদা cycle-এ করে। Single-cycle-এ এগুলোর জন্য আলাদা adder আর আলাদা memory লাগত, কারণ সব কাজ একই cycle-এ।
পুনর্ব্যবহারের দাম হলো: এক cycle-এর ফল পরের cycle পর্যন্ত বাঁচিয়ে রাখতে হয়, কারণ পরের cycle-এ সেই unit অন্য কাজে ব্যস্ত। তাই প্রতিটি বড় unit-এর output-এ একটি register বসে — এগুলোকে বলে nonarchitectural register, কারণ ISA-তে এদের নাম নেই, কোনো instruction এদের সরাসরি পড়ে না।
| Register | কোথা থেকে আসে | কেন দরকার |
|---|---|---|
| IR (instruction register) | Memory-র output, fetch-এর cycle-এ | পরের cycle-গুলোতে memory data পড়তে বা লিখতে পারে, তখনো instruction-এর বিট (rs1, rs2, rd, imm, opcode) হাতে থাকতে হবে। শুধু IRWrite চালু থাকলেই লেখা হয়। |
| MDR (memory data register) | Memory-র output, load-এর data পড়ার cycle-এ | পড়া data পরের cycle-এ register file-এ লেখা হয়; memory আর register-এ লেখা একই cycle-এ রাখলে cycle লম্বা হতো। |
| A আর B | Register file-এর দুই output (rs1, rs2) | পরের cycle-এ ALU এগুলো নিয়ে কাজ করে; B দিয়ে sw memory-তে লেখে। |
| ALUOut | ALU-র output | ঠিকানা, ফল বা শাখার লক্ষ্য পরের cycle-এ memory, register file বা PC-তে যায়। |
| OldPC (এই নকশায়) | Fetch-এর সময়ের PC | Fetch-এই PC ← PC + 4 হয়ে যায়, অথচ শাখার লক্ষ্য = instruction-এর নিজের ঠিকানা + offset. তাই পুরোনো মানটি রেখে দেওয়া। |
Single-cycle-এ “সময়” মাপা হয় instruction দিয়ে, multi-cycle-এ “ধাপ” দিয়ে। একটি বড় combinational পথকে register দিয়ে ছোট ছোট টুকরো করলে clock দ্রুত হয়, আর প্রতিটি instruction শুধু নিজের দরকারি টুকরোগুলোর জন্য সময় দেয়। এই একই কৌশল — register দিয়ে পথ কাটা — পর্ব ৩৫-এর pipelining-এরও ভিত্তি।
২ · Multi-cycle datapath — এক memory, এক ALU, পাঁচটি নতুন register
কোন পাঠ্যবইয়ের রীতি মানা হলো: এই পর্বের datapath আর control FSM Sarah Harris ও David Harris-এর Digital Design and Computer Architecture: RISC-V Edition-এর multi-cycle প্রসেসরের ধাঁচে — সেখানে OldPC আছে, fetch-এর cycle-এই PC + 4 লেখা হয়, আর decode-এর cycle-এ শাখার লক্ষ্য হিসাব হয়। Register-এর নাম রাখা হয়েছে প্রচলিত রূপে (IR, MDR, A, B, ALUOut); Harris–Harris-এর বইয়ে এগুলোর নাম কিছুটা আলাদা (যেমন Instr, Data, WriteData). Patterson–Hennessy-র পুরোনো MIPS সংস্করণগুলোতেও প্রায় একই নকশা ছিল — সেখানে OldPC নেই, কিন্তু চক্রের সংখ্যা একই: lw ৫, sw ও R-type ৪, beq ৩ (jump সেখানে ৩, এখানে jal ৪). পরীক্ষায় কোন বইয়ের রীতি মানছেন, এক লাইনে লিখে দেওয়াই নিরাপদ।
ছবিটি বাঁ থেকে ডানে পড়ুন। নতুন কিছু mux এসেছে, কারণ একই unit বিভিন্ন cycle-এ বিভিন্ন জায়গা থেকে input নেয়:
- AdrSrc mux: Memory-র ঠিকানা কখনো PC (fetch), কখনো ALUOut-থেকে-আসা data-র ঠিকানা (lw/sw). একটিই memory — von Neumann ধাঁচ — তাই এই mux.
- ALUSrcA mux: PC (PC + 4-এর জন্য), OldPC (শাখার লক্ষ্যের জন্য) বা A (আসল হিসাব).
- ALUSrcB mux: B (R-type, beq), sign-extend করা imm (lw, sw, addi, শাখার offset) বা ধ্রুবক 4.
- ResultSrc mux: ALUOut (আগের cycle-এর ফল), MDR (load-এর data) বা এই মুহূর্তের ALU ফল (fetch-এ PC + 4 সরাসরি PC-তে)।
Register-গুলোর লেখার নিয়ম গুরুত্বপূর্ণ। PC আর IR-এ লেখা হয় শুধু PCWrite বা IRWrite চালু থাকলে — এগুলো enable-ওয়ালা register, কারণ এদের মান কয়েক cycle ধরে অপরিবর্তিত থাকতে হয়। A, B, ALUOut, MDR প্রতি cycle-এ লেখা হয় (enable নেই); তাদের মান ঠিক পরের cycle-এই ব্যবহার করতে হয়, নয়তো মুছে যায়। এতে control signal-এর সংখ্যা কমে।
৩ · পাঁচটি ধাপ — কোন instruction কয় cycle নেয়
প্রতিটি ধাপে কী হয়, তা register transfer ভাষায় (RTL, “←” মানে cycle-এর শেষের clock ধারে লেখা) লেখা যাক। প্রথম দুই ধাপ সব instruction-এর জন্য এক, কারণ তখনো control জানে না instruction কী:
| ধাপ | R-type (add) | lw | sw | beq |
|---|---|---|---|---|
| ১ · Fetch | IR ← Mem[PC]; OldPC ← PC; PC ← PC + 4 | |||
| ২ · Decode ও register fetch | A ← x[rs1]; B ← x[rs2]; ALUOut ← OldPC + imm (শাখার লক্ষ্য, আগাম) | |||
| ৩ · Execute / ঠিকানা / শাখা শেষ | ALUOut ← A op B | ALUOut ← A + imm | ALUOut ← A + imm | A − B; Zero হলে PC ← ALUOut (শেষ) |
| ৪ · Memory বা R-type শেষ | x[rd] ← ALUOut (শেষ) | MDR ← Mem[ALUOut] | Mem[ALUOut] ← B (শেষ) | — |
| ৫ · Load শেষ (write-back) | — | x[rd] ← MDR (শেষ) | — | — |
| মোট cycle | ৪ | ৫ | ৪ | ৩ |
আরও দুটি class: I-type ALU (addi) R-type-এর মতো ৪ cycle (ধাপ ৩-এ A + imm). jal-ও ৪: ধাপ ২-এ লক্ষ্য (OldPC + imm) আগেই ALUOut-এ, ধাপ ৩-এ PC ← ALUOut আর একই সময়ে ALU হিসাব করে OldPC + 4, ধাপ ৪-এ x[rd] ← সেই ফেরার ঠিকানা।
ধাপ ২-এর চালাকি। Decode-এর cycle-এ ALU এমনিতে বসে থাকত — control এখনো instruction-এর ধরন জানে না। তাই নকশাকার ALU-কে দিয়ে আগাম OldPC + imm হিসাব করিয়ে রাখেন। Instruction যদি শাখা হয়, ধাপ ৩-এ লক্ষ্য তৈরি, তাই beq তিন cycle-এই শেষ। যদি শাখা না হয়, হিসাবটি ফেলে দেওয়া হয় — ক্ষতি নেই, কারণ ALU-কে তো বসেই থাকতে হতো। একে বলা যায় অনুমাননির্ভর (speculative) কাজের সবচেয়ে সরল রূপ: কাজে লাগতে পারে এমন কিছু ফাঁকা সময়ে করে রাখা। একইভাবে A আর B সব instruction-এর জন্যই পড়া হয়, এমনকি lw-এর জন্যও (যার rs2 নেই) — অকাজের পড়া, কিন্তু নিরীহ।
এবার একটি আসল instruction — lw x5, 8(x10), যেখানে x10 = 0x40 আর memory-র 0x48 ঠিকানায় 7 — পাঁচ cycle ধরে চালানো যাক। প্রতিটি ধাপে register-এর মান সেই cycle-এর শেষের clock ধারের পরের:
| Cycle · state | |
| এই cycle-এর কাজ | |
| Memory-র ঠিকানা | |
| ALU কী করল | |
| PC | |
| IR | |
| A / B | |
| ALUOut | |
| MDR | |
| x5 |
- Cycle ১ — Fetch. AdrSrc = PC, তাই memory 0x00 থেকে 0x00852283 পড়ে, IRWrite চালু থাকায় IR-এ বসে। একই cycle-এ ALU হিসাব করে 0x00 + 4, আর ResultSrc = ALU ফল হওয়ায় PCWrite দিয়ে PC = 0x04. একটিই ALU, তবু এখন সে শুধু PC + 4-এর কাজ করছে।
- Cycle ২ — Decode. IR-এর rs1 = 10, তাই A = x10 = 0x40. rs2-এর ঘরে (বিট 24:20) আসলে imm-এর অংশ, 01000 = 8, তাই B = x8 = 0 — অকাজের, কিন্তু নিরীহ। ALU আগাম OldPC + imm = 0x00 + 8 = 0x08 হিসাব করে; lw-এর জন্য এটি অকাজের। Control opcode 0000011 দেখে ঠিক করে পরের state MemAdr.
- Cycle ৩ — MemAdr. ALUSrcA = A, ALUSrcB = imm: 0x40 + 8 = 0x48, ALUOut-এ। এটি data-র ঠিকানা। Memory এই cycle-এ বসে আছে।
- Cycle ৪ — MemRead. AdrSrc = Result (= ALUOut), তাই memory 0x48 থেকে 7 পড়ে, MDR-এ জমা হয়। একই memory, যেটি cycle ১-এ instruction দিয়েছিল — এই পুনর্ব্যবহারের জন্যই IR লাগে, নইলে instruction-এর বিট হারিয়ে যেত।
- Cycle ৫ — MemWB. ResultSrc = MDR, RegWrite চালু, rd = 5: x5 = 7. lw শেষ, মোট ৫ cycle, আর পরের cycle-এ FSM আবার Fetch-এ — PC = 0x04-এর instruction আনতে।
প্রচলিত ভুল: “Multi-cycle-এ প্রতিটি instruction ৫ cycle নেয়” — না। সেটি ধাপের সর্বোচ্চ সংখ্যা। Control instruction অনুযায়ী আগেই Fetch-এ ফিরে যায়; এটাই multi-cycle-এর পুরো লাভ। যদি সব instruction ৫ cycle নিত, তাহলে CPI = ৫ আর single-cycle-এর চেয়ে প্রায় নিশ্চিতভাবেই ধীর।
৪ · Control-কে FSM হিসেবে — state diagram আর প্রতিটি state-এর signal
Single-cycle-এর control (পর্ব ৩৩, Control unit) ছিল একটি combinational টেবিল: opcode ঢোকে, signal বেরোয়। Multi-cycle-এ সেটি যথেষ্ট নয়, কারণ একই instruction-এর জন্য cycle ১-এ এক রকম signal, cycle ৩-এ আরেক রকম লাগে। Control-কে মনে রাখতে হয় “এই instruction-এর কোন ধাপে আছি”। স্মৃতিসহ control মানেই finite state machine — পর্ব ১৭-এর (Finite state machine) পদ্ধতি এখানে সরাসরি কাজে লাগে। প্রতিটি state একটি ধাপ; state-এর output হলো সেই ধাপের control signal; input হলো opcode (আর beq-এর জন্য ALU-র Zero).
প্রতিটি state-এ কোন signal চালু (বাকি সব enable 0; mux-এর মান উল্লেখ না থাকলে “don't care”). কোডের মানগুলো (00, 01, 10) এই পর্বের নকশার; বই-ভেদে মানের ক্রম আলাদা হতে পারে:
| State | চালু signal আর mux-এর মান | ফল |
|---|---|---|
| S0 Fetch | AdrSrc = 0 (PC), IRWrite, ALUSrcA = 00 (PC), ALUSrcB = 10 (4), ALUOp = 00 (add), ResultSrc = 10 (ALU ফল), PCUpdate | IR ← Mem[PC], PC ← PC + 4 |
| S1 Decode | ALUSrcA = 01 (OldPC), ALUSrcB = 01 (imm), ALUOp = 00 | A, B পড়া; ALUOut ← লক্ষ্য |
| S2 MemAdr | ALUSrcA = 10 (A), ALUSrcB = 01 (imm), ALUOp = 00 | ALUOut ← A + imm |
| S3 MemRead | ResultSrc = 00 (ALUOut), AdrSrc = 1 | MDR ← Mem[ALUOut] |
| S4 MemWB | ResultSrc = 01 (MDR), RegWrite | x[rd] ← MDR |
| S5 MemWrite | ResultSrc = 00, AdrSrc = 1, MemWrite | Mem[ALUOut] ← B |
| S6 ExecuteR | ALUSrcA = 10, ALUSrcB = 00 (B), ALUOp = 10 (funct দেখে) | ALUOut ← A op B |
| S7 ALUWB | ResultSrc = 00, RegWrite | x[rd] ← ALUOut |
| S8 ExecuteI | ALUSrcA = 10, ALUSrcB = 01, ALUOp = 10 | ALUOut ← A op imm |
| S9 JAL | ALUSrcA = 01, ALUSrcB = 10, ALUOp = 00, ResultSrc = 00, PCUpdate | PC ← লক্ষ্য; ALUOut ← OldPC + 4 |
| S10 BEQ | ALUSrcA = 10, ALUSrcB = 00, ALUOp = 01 (sub), ResultSrc = 00, Branch | Zero হলে PC ← লক্ষ্য |
PC-তে লেখার আসল enable হলো PCWrite = PCUpdate + Branch · Zero. Signal-গুলোর বেশিরভাগ শুধু state-এর ওপর নির্ভর করে, তাই এটি মূলত একটি Moore machine; শুধু PCWrite-এ Zero ঢোকে। ALUOp থেকে ALU-র আসল কাজ বের করে আলাদা ছোট ALU control decoder (funct3, funct7 দেখে) — পর্ব ৩৩-এর মতো, একটি পার্থক্যসহ: এখানে S8 (I-type ALU)-ও ALUOp = 10 পায়, কারণ Harris–Harris-এর decoder বিট ৩০-কে sub-এর চিহ্ন ধরে শুধু তখনই, যখন opcode-এর বিট ৫ = 1 (অর্থাৎ R-type); তাই addi-এর immediate-এর বিট ৩০ বিয়োগ বানায় না। পর্ব ৩৩-এর সরল ALU control শুধু বিট ৩০ দেখে, তাই সেখানে (আর এই কোর্সের রীতিতে) addi-এর ALUOp = 00.
Next-state logic
১১টি state, তাই binary encoding-এ ⌈log2 ১১⌉ = ৪টি flip-flop-এর state register (one-hot চাইলে ১১টি). Next-state logic-এর টেবিল ছোট: বেশিরভাগ state-এর পরের state নির্দিষ্ট, শুধু দুই জায়গায় opcode দেখে শাখা।
| বর্তমান state | শর্ত (opcode) | পরের state |
|---|---|---|
| S0 | যেকোনো | S1 |
| S1 | 0000011 (lw) বা 0100011 (sw) | S2 |
| 0110011 (R-type) | S6 | |
| 0010011 (I-type ALU) | S8 | |
| 1101111 (jal) | S9 | |
| 1100011 (beq) | S10 | |
| S2 | lw → S3, sw → S5 | S3 / S5 |
| S3 | যেকোনো | S4 |
| S6, S8, S9 | যেকোনো | S7 |
| S4, S5, S7, S10 | যেকোনো | S0 |
এই টেবিল থেকে পর্ব ১৭-এর মতো K-map বা synthesis tool দিয়ে সমীকরণ পাওয়া যায়। Opcode-এর ৭ বিট + state-এর ৪ বিট = ১১টি input, তাই হাতে K-map অসম্ভব; বাস্তবে Verilog-এ case লিখে synthesis tool-কে দেওয়া হয়, বা টেবিলটিকে একটি ROM বা PLA-তে বসানো হয়। ROM-এ বসানোর ভাবনাই আমাদের অংশ ৬-এর microprogramming-এর দিকে নিয়ে যাবে।
নিচের stepper-এ দুটি beq — একটি যেটির শাখা নেওয়া হয় না, একটি যেটির নেওয়া হয় — FSM-এর পথ ধরে দেখুন:
| Instruction | |
| এখনকার state | |
| ALU | |
| A / B | |
| Zero · PCWrite | |
| PC (ধারের পরে) | |
| ALUOut (ধারের পরে) | |
| পরের state |
- প্রথম beq, Fetch. PC = 0x0C থেকে instruction এল, PC ← 0x10. Fetch-এর সময় control জানে না এটি শাখা — সব instruction-এর মতো একই কাজ। A, B-তে আগের sw-এর পুরোনো মান।
- Decode. A = x5 = 7, B = x6 = 14. ফাঁকা ALU আগাম লক্ষ্য হিসাব করে: OldPC + 8 = 0x14, ALUOut-এ রাখে। Next-state logic opcode 1100011 দেখে S10-এ পাঠায়।
- S10 BEQ — শাখা নয়। ALU এখন A − B = −7, তাই Zero = 0, Branch · Zero = 0, PC-তে লেখা হয় না — PC 0x10-ই থাকে, অর্থাৎ পরের instruction. ALUOut প্রতি cycle-এ লেখা হয়, তাই এখন তাতে −7; লক্ষ্য 0x14 আর লাগবে না। মোট ৩ cycle, পরের state S0.
- দ্বিতীয় beq, Fetch. 0x10 থেকে
beq x0, x0, 8, PC ← 0x14. এটি সবসময় শাখা নেয় (x0 = x0), অনেকটা নিঃশর্ত লাফের মতো। - Decode. A = B = x0 = 0. আগাম লক্ষ্য = 0x10 + 8 = 0x18, ALUOut-এ। আবার S10.
- S10 BEQ — শাখা নেওয়া। 0 − 0 = 0, Zero = 1, PCWrite = 1. ResultSrc = ALUOut, যেখানে এখনো Decode-এর লক্ষ্য 0x18 — PC ← 0x18, ঠিকানা 0x14-এর instruction লাফিয়ে পার। একই clock ধারে ALUOut-এ নতুন মান 0 বসে, কিন্তু PC আগের মানটিই নেয়, কারণ ধারের আগে mux পুরোনো ALUOut দেখছিল। দুই beq-ই ৩ cycle — শাখা নেওয়া হোক বা না হোক।
এবার পুরো ছোট প্রোগ্রাম — lw, add, sw, দুটি beq — cycle ধরে চালানো যাক। নিচের simulator একটি আসল RV32I machine code (hex) decode করে, ওপরের FSM-এর state ধরে এগোয়, আর প্রতিটি cycle-এর state, register আর চালু signal ছাপে:
// Multi-cycle RV32I: one memory, one ALU, extra registers IR, MDR, A, B, ALUOut (+OldPC).
// Control = FSM (Harris-Harris RISC-V style). Values shown are AFTER each clock edge.
const mem = { 0x00: 0x00852283, 0x04: 0x00528333, 0x08: 0x00652623, 0x0c: 0x00628463,
0x10: 0x00000463, 0x14: 0x006303b3, 0x48: 7 }; // program + one data word
const x = new Array(32).fill(0); x[10] = 0x40; // x10 = base address
const sx = (v, b) => (v << (32 - b)) >> (32 - b); // sign-extend b bits
const imm = i => { const op = i & 0x7f;
if (op === 0x23) return sx(((i >>> 25) << 5) | ((i >>> 7) & 31), 12); // S
if (op === 0x63) return sx(((i >>> 31) << 12) | (((i >>> 7) & 1) << 11) |
(((i >>> 25) & 63) << 5) | (((i >>> 8) & 15) << 1), 13); // B
return sx(i >>> 20, 12); }; // I
const DISPATCH = { 0x03: "MemAdr", 0x23: "MemAdr", 0x33: "ExecuteR", 0x63: "BEQ" };
let PC = 0, OldPC = 0, IR = 0, A = 0, B = 0, ALUOut = 0, MDR = 0, st = "Fetch", cyc = 0, n = 0;
const hx = v => (v >>> 0).toString(16).padStart(2, "0");
console.log("cyc state PC IR A B ALUOut MDR | asserted signals");
while (!(st === "Fetch" && PC === 0x18)) { // stop when the next fetch is at 0x18
const s = st, op = IR & 0x7f, rd = (IR >>> 7) & 31; let sig = "";
if (s === "Fetch") { IR = mem[PC]; OldPC = PC; ALUOut = PC + 4; PC += 4; n++;
sig = "IRWrite PCWrite SrcA=PC SrcB=4"; st = "Decode"; }
else if (s === "Decode") { A = x[(IR >>> 15) & 31]; B = x[(IR >>> 20) & 31];
ALUOut = OldPC + imm(IR); sig = "SrcA=OldPC SrcB=imm"; st = DISPATCH[IR & 0x7f]; }
else if (s === "MemAdr") { ALUOut = A + imm(IR); sig = "SrcA=A SrcB=imm";
st = op === 0x03 ? "MemRead" : "MemWrite"; }
else if (s === "MemRead") { MDR = mem[ALUOut] ?? 0; sig = "AdrSrc=ALUOut"; st = "MemWB"; }
else if (s === "MemWB") { x[rd] = MDR; sig = `RegWrite x${rd}<-MDR`; st = "Fetch"; }
else if (s === "MemWrite") { mem[ALUOut] = B; sig = "AdrSrc=ALUOut MemWrite"; st = "Fetch"; }
else if (s === "ExecuteR") { ALUOut = A + B; sig = "SrcA=A SrcB=B ALUOp=funct"; st = "ALUWB"; }
else if (s === "ALUWB") { x[rd] = ALUOut; sig = `RegWrite x${rd}<-ALUOut`; st = "Fetch"; }
else if (s === "BEQ") { const target = ALUOut, zero = A - B === 0; ALUOut = A - B;
if (zero) PC = target; sig = `SrcA=A SrcB=B sub Zero=${+zero}${zero ? " PCWrite" : ""}`; st = "Fetch"; }
console.log(`${String(++cyc).padStart(3)} ${s.padEnd(9)} ${hx(PC)} ${hx(IR).padStart(8, "0")} ` +
`${String(A).padStart(3)} ${String(B).padStart(3)} ${String(ALUOut).padStart(6)} ${String(MDR).padStart(3)} | ${sig}`);
}
console.log(`instructions = ${n}, cycles = ${cyc}, CPI = ${(cyc / n).toFixed(2)}; ` +
`x5 = ${x[5]}, x6 = ${x[6]}, x7 = ${x[7]}, mem[0x4c] = ${mem[0x4c]}`);আউটপুট (নির্ধারিত — প্রতিবার একই; PC আর IR hex-এ, বাকি সংখ্যা দশমিকে, সব মান সেই cycle-এর clock ধারের পরের):
cyc state PC IR A B ALUOut MDR | asserted signals
1 Fetch 04 00852283 0 0 4 0 | IRWrite PCWrite SrcA=PC SrcB=4
2 Decode 04 00852283 64 0 8 0 | SrcA=OldPC SrcB=imm
3 MemAdr 04 00852283 64 0 72 0 | SrcA=A SrcB=imm
4 MemRead 04 00852283 64 0 72 7 | AdrSrc=ALUOut
5 MemWB 04 00852283 64 0 72 7 | RegWrite x5<-MDR
6 Fetch 08 00528333 64 0 8 7 | IRWrite PCWrite SrcA=PC SrcB=4
7 Decode 08 00528333 7 7 9 7 | SrcA=OldPC SrcB=imm
8 ExecuteR 08 00528333 7 7 14 7 | SrcA=A SrcB=B ALUOp=funct
9 ALUWB 08 00528333 7 7 14 7 | RegWrite x6<-ALUOut
10 Fetch 0c 00652623 7 7 12 7 | IRWrite PCWrite SrcA=PC SrcB=4
11 Decode 0c 00652623 64 14 20 7 | SrcA=OldPC SrcB=imm
12 MemAdr 0c 00652623 64 14 76 7 | SrcA=A SrcB=imm
13 MemWrite 0c 00652623 64 14 76 7 | AdrSrc=ALUOut MemWrite
14 Fetch 10 00628463 64 14 16 7 | IRWrite PCWrite SrcA=PC SrcB=4
15 Decode 10 00628463 7 14 20 7 | SrcA=OldPC SrcB=imm
16 BEQ 10 00628463 7 14 -7 7 | SrcA=A SrcB=B sub Zero=0
17 Fetch 14 00000463 7 14 20 7 | IRWrite PCWrite SrcA=PC SrcB=4
18 Decode 14 00000463 0 0 24 7 | SrcA=OldPC SrcB=imm
19 BEQ 18 00000463 0 0 0 7 | SrcA=A SrcB=B sub Zero=1 PCWrite
instructions = 5, cycles = 19, CPI = 3.80; x5 = 7, x6 = 14, x7 = 0, mem[0x4c] = 14
পড়ার কৌশল: প্রথম কলামে cycle, দ্বিতীয়তে সেই cycle-এর state. lw cycle ১–৫: cycle ৩-এ ALUOut = 72 (= 0x48), cycle ৪-এ MDR = 7, cycle ৫-এ RegWrite x5<-MDR. add cycle ৬–৯ (৪টি): Decode-এ A = B = 7, ExecuteR-এ ALUOut = 14, ALUWB-এ x6. লক্ষ করুন cycle ৭-এ ALUOut = 9 — R-type-এর জন্যও Decode আগাম “লক্ষ্য” হিসাব করেছে (OldPC 4 + বিট 31:20-এর মান 5), যা কেউ ব্যবহার করে না। sw cycle ১০–১৩: ঠিকানা 76 (= 0x4C), MemWrite. প্রথম beq cycle ১৪–১৬: ALUOut = −7, Zero = 0, PC 10-তেই। দ্বিতীয় beq cycle ১৭–১৯: Zero=1 PCWrite, PC = 18 — তাই ঠিকানা 0x14-এর add x7 চলেনি, শেষ লাইনে x7 = 0. মোট ৫টি instruction, ১৯ cycle: ৫ + ৪ + ৪ + ৩ + ৩ = ১৯, CPI = ৩.৮০. নিজে বদলে দেখুন: 0x48: 7-কে 0x48: 0 করলে কী হবে? (অনুশীলন ৪-এ উত্তর।)
৫ · CPI আর পারফরম্যান্স — multi-cycle কখন জেতে, কখন হারে
পর্ব ০৩-এর (পারফরম্যান্স মাপা) iron law এখানে সব প্রশ্নের উত্তর দেয়:
একই প্রোগ্রাম, একই ISA, তাই IC দুই নকশায় এক। লড়াইটা CPI আর Tc-এর মধ্যে: single-cycle-এর CPI = ১ কিন্তু Tc লম্বা; multi-cycle-এর Tc ছোট কিন্তু CPI ৩–৫. Instruction mix থেকে গড় CPI হলো ভারযুক্ত গড়: CPI = Σ fi × CPIi, যেখানে fi হলো class i-এর ভগ্নাংশ।
প্রচলিত পরীক্ষার হিসাব — পূর্ণ সমাধান
প্রশ্ন: একটি প্রোগ্রামে ২৫% lw, ১০% sw, ৫২% R-type, ১১% beq, ২% jal. Unit-এর delay: memory ২০০ ps, register file (পড়া বা লেখা) ১০০ ps, ALU ২০০ ps; mux, register-এর setup আর clock-to-Q উপেক্ষা করুন। Single-cycle আর multi-cycle নকশার মধ্যে কোনটি দ্রুত, কতগুণ?
- Multi-cycle-এর গড় CPI: ০.২৫ × ৫ + ০.১০ × ৪ + ০.৫২ × ৪ + ০.১১ × ৩ + ০.০২ × ৪ = ১.২৫ + ০.৪০ + ২.০৮ + ০.৩৩ + ০.০৮ = ৪.১৪.
- Single-cycle-এর Tc: সবচেয়ে লম্বা পথ lw-এর — memory (instruction) + register পড়া + ALU + memory (data) + register লেখা = ২০০ + ১০০ + ২০০ + ২০০ + ১০০ = ৮০০ ps.
- Multi-cycle-এর Tc: প্রতিটি ধাপে একটি বড় unit, তাই সবচেয়ে ধীর unit-এর delay: max(২০০, ১০০, ২০০) = ২০০ ps.
- গড় সময় প্রতি instruction: single = ১ × ৮০০ = ৮০০ ps; multi = ৪.১৪ × ২০০ = ৮২৮ ps.
- Speedup (single ÷ multi) = ৮০০ / ৮২৮ ≈ ০.৯৬৬ — অর্থাৎ multi-cycle প্রায় ৩.৪% ধীর. IC = ১০৯ হলে single ০.৮০০ s, multi ০.৮২৮ s.
অবাক লাগে? কারণটি একটি সরল অসমতায় লুকানো। দাবি: register-এর খরচ বাদ দিলেও multi-cycle তখনই জেতে, যখন CPImulti < Tsingle / Tmulti. যুক্তি: সময় তুলনা করলে IC × ১ × Tsingle > IC × CPImulti × Tmulti, দুই পাশ IC × Tmulti দিয়ে ভাগ করলেই শর্তটি আসে। এখানে Tsingle / Tmulti = ৮০০ / ২০০ = ৪, অথচ CPI = ৪.১৪ > ৪. সমস্যা হলো ধাপগুলো সমান নয়: register file-এর ধাপ ১০০ ps-এ শেষ হতো, তবু ২০০ ps-এর cycle পায়। Load-এর পাঁচ ধাপের delay যোগ করলে ৮০০, কিন্তু multi-cycle-এ তা নেয় ৫ × ২০০ = ১০০০ ps. বাস্তবে প্রতিটি cycle-এ register-এর setup আর clock-to-Q-এর খরচও যোগ হয় (tpcq + tsetup), যা multi-cycle-কে একাধিকবার দিতে হয় — single-cycle দেয় একবার।
নিচের calculator চারটি পরিস্থিতিতে একই হিসাব করে, যার দুটিতে multi-cycle হারে আর দুটিতে জেতে:
// Single-cycle vs multi-cycle: execution time = IC x CPI x Tc. Delays in picoseconds (ps).
const IC = 1e9; // 10^9 instructions
function compare(name, d, mix) {
// Single-cycle: one long cycle that must fit the slowest instruction's whole path.
const path = { lw: d.mem + d.rf + d.alu + d.mem + d.rf, sw: d.mem + d.rf + d.alu + d.mem,
R: d.mem + d.rf + d.alu + d.rf, beq: d.mem + d.rf + d.alu, jal: d.mem + d.alu + d.rf,
mul: d.mem + d.rf + (d.mul || 0) + d.rf };
const classes = Object.keys(mix);
const Tc1 = d.ovh + Math.max(...classes.map(c => path[c]));
// Multi-cycle: each step uses one big unit, so Tc = slowest unit (+ register overhead).
const step = Math.max(d.mem, d.rf, d.alu), Tc2 = d.ovh + step;
const cycles = { lw: 5, sw: 4, R: 4, beq: 3, jal: 4, mul: 3 + Math.ceil((d.mul || 0) / step) };
const CPI = classes.reduce((s, c) => s + mix[c] * cycles[c], 0);
const t1 = IC * 1 * Tc1 * 1e-12, t2 = IC * CPI * Tc2 * 1e-12; // seconds
console.log(`${name}\n single: CPI 1.00 Tc ${Tc1} ps time ${t1.toFixed(3)} s` +
`\n multi : CPI ${CPI.toFixed(2)} Tc ${Tc2} ps time ${t2.toFixed(3)} s` +
`\n speedup (single/multi) = ${(t1 / t2).toFixed(3)} -> ${t2 < t1 ? "multi-cycle wins" : "multi-cycle LOSES"}`);
}
const mix = { lw: 0.25, sw: 0.10, R: 0.52, beq: 0.11, jal: 0.02 };
compare("A) classic delays: mem 200, regfile 100, ALU 200", { mem: 200, rf: 100, alu: 200, ovh: 0 }, mix);
compare("B) same + 30 ps register overhead per cycle", { mem: 200, rf: 100, alu: 200, ovh: 30 }, mix);
compare("C) balanced units: mem 100, regfile 100, ALU 100", { mem: 100, rf: 100, alu: 100, ovh: 0 }, mix);
compare("D) classic + 5% mul needing 800 ps of ALU time", { mem: 200, rf: 100, alu: 200, mul: 800, ovh: 0 },
{ lw: 0.25, sw: 0.10, R: 0.47, beq: 0.11, jal: 0.02, mul: 0.05 });আউটপুট:
A) classic delays: mem 200, regfile 100, ALU 200
single: CPI 1.00 Tc 800 ps time 0.800 s
multi : CPI 4.14 Tc 200 ps time 0.828 s
speedup (single/multi) = 0.966 -> multi-cycle LOSES
B) same + 30 ps register overhead per cycle
single: CPI 1.00 Tc 830 ps time 0.830 s
multi : CPI 4.14 Tc 230 ps time 0.952 s
speedup (single/multi) = 0.872 -> multi-cycle LOSES
C) balanced units: mem 100, regfile 100, ALU 100
single: CPI 1.00 Tc 500 ps time 0.500 s
multi : CPI 4.14 Tc 100 ps time 0.414 s
speedup (single/multi) = 1.208 -> multi-cycle wins
D) classic + 5% mul needing 800 ps of ALU time
single: CPI 1.00 Tc 1200 ps time 1.200 s
multi : CPI 4.29 Tc 200 ps time 0.858 s
speedup (single/multi) = 1.399 -> multi-cycle wins
A ওপরের হাতের হিসাবটি: CPI 4.14, ৮০০ বনাম ৮২৮ ps, speedup 0.966 — multi-cycle হারে। B-তে প্রতি cycle-এ ৩০ ps register-এর খরচ যোগ হয়েছে: single-cycle এটি একবার দেয় (৮৩০), multi-cycle প্রতি ধাপে (২৩০ × ৪.১৪ ≈ ৯৫২) — হার আরও বড়, 0.872. C-তে তিনটি unit-এর delay সমান (১০০ ps): ধাপ নিখুঁতভাবে সুষম, single-এর Tc = ৫০০, multi-র ১০০ × ৪.১৪ = ৪১৪ — এবার multi-cycle ১.২১ গুণ দ্রুত। D সবচেয়ে শিক্ষণীয়: মাত্র ৫% instruction একটি ধীর গুণ (৮০০ ps ALU সময়)। Single-cycle-কে এই দুর্লভ instruction-এর জন্য cycle ১২০০ ps করতে হয়, আর বাকি ৯৫% instruction সেই দাম দেয়। Multi-cycle-এ গুণ নেয় ৩ + ৪ = ৭ cycle, CPI বাড়ে মাত্র ৪.২৯-এ, সময় ৮৫৮ ps — speedup ১.৪০. শিক্ষা: instruction-গুলোর দৈর্ঘ্যে যত বেশি পার্থক্য, আর ধাপগুলো যত সুষম, multi-cycle তত লাভজনক। এই কারণেই জটিল, নানা দৈর্ঘ্যের instruction-ওয়ালা CISC machine-এ multi-cycle (আর microcode) এত স্বাভাবিক ছিল।
৬ · Microprogrammed control — control store, microinstruction আর sequencer
অংশ ৪-এর FSM-কে logic gate দিয়ে বানালে তাকে বলে hardwired control. বিকল্প একটি ভাবনা প্রস্তাব করেছিলেন ব্রিটিশ কম্পিউটার বিজ্ঞানী Maurice Wilkes — সাধারণত ১৯৫১ সালের একটি সম্মেলনের প্রবন্ধকে এর সূচনা ধরা হয়: control-কে circuit হিসেবে না ভেবে একটি ছোট প্রোগ্রাম হিসেবে ভাবা। প্রতিটি state-এর signal-গুলো একটি শব্দ (word) হিসেবে একটি memory-তে লেখা থাকবে; প্রতি cycle-এ একটি শব্দ পড়া হবে, তার বিটগুলোই সরাসরি control signal. এই কৌশলের নাম microprogramming.
- Control store: microinstruction রাখার ছোট, দ্রুত memory — সাধারণত ROM.
- Microinstruction: control store-এর একটি শব্দ = FSM-এর এক state = এক cycle-এর কাজ। কয়েকটি field-এ ভাগ করা।
- Micro-PC (µPC): এখন কোন ঠিকানার microinstruction চলছে।
- Microsequencer: পরের µPC বেছে নেয় — তিনটি উপায়ে: Seq (µPC + 1, পরের লাইন), Fetch (০-তে ফেরা, নতুন instruction) আর Dispatch (opcode দিয়ে একটি dispatch ROM থেকে ঠিকানা পড়া — microprogram-এর “switch-case”).
- Microcode: পুরো control store-এর বিষয়বস্তু — প্রসেসরের ভেতরের প্রোগ্রাম।
লাইব্রেরির ক্যাটালগের কথা ভাবুন। পাঠক একটি বইয়ের কোড (opcode) দেন; গ্রন্থাগারিক ক্যাটালগ (dispatch ROM) দেখে জানেন কোন তাকে (ঠিকানা) যেতে হবে। তারপর সেই তাকের নির্দেশিকা কার্ড একটার পর একটা (Seq) পড়ে কাজ করেন, শেষ কার্ডে লেখা থাকে “কাউন্টারে ফিরুন” (Fetch). Hardwired control হলো এমন গ্রন্থাগারিক যিনি সব মুখস্থ রেখেছেন — দ্রুত, কিন্তু নিয়ম বদলালে নতুন লোক লাগে। Microprogrammed control-এ শুধু কার্ড বদলালেই হয়।
আমাদের FSM-এর ১১টি state থেকে ১১টি microinstruction. প্রতিটির field: ALU (কাজ), SRC1 ও SRC2 (ALUSrcA, ALUSrcB), Register (পড়া/লেখা), Memory, PCWrite আর Sequencing. এই ছোট microprogram-টি নিচে (ঠিকানা ০ থেকে ১০):
| µPC | Label | ALU | SRC1 | SRC2 | Register | Memory | PCWrite | Sequencing |
|---|---|---|---|---|---|---|---|---|
| 0 | FETCH | add | PC | 4 | — | Read PC → IR | ALU | Seq |
| 1 | DECODE | add | OldPC | Imm | Read A, B | — | — | Dispatch 1 |
| 2 | MEMADR | add | A | Imm | — | — | — | Dispatch 2 |
| 3 | MEMREAD | — | — | — | — | Read ALUOut | — | Seq |
| 4 | MEMWB | — | — | — | Write MDR | — | — | Fetch |
| 5 | MEMWRITE | — | — | — | — | Write ALUOut | — | Fetch |
| 6 | EXECR | funct | A | B | — | — | — | Seq |
| 7 | ALUWB | — | — | — | Write ALUOut | — | — | Fetch |
| 8 | EXECI | funct | A | Imm | — | — | — | Dispatch 2 |
| 9 | JAL | add | OldPC | 4 | — | — | ALUOut | Dispatch 2 |
| 10 | BEQ | sub | A | B | — | — | ALUOut-cond | Fetch |
“Write MDR” মানে x[rd] ← MDR; “Write ALUOut” Register কলামে x[rd] ← ALUOut, আর Memory কলামে Mem[ALUOut] ← B. “ALUOut-cond” মানে Zero হলে PC ← ALUOut. দুটি dispatch ROM:
| Opcode | Class | Dispatch ROM 1 (DECODE থেকে) | Dispatch ROM 2 |
|---|---|---|---|
| 0000011 | lw | 2 (MEMADR) | 3 (MEMREAD) |
| 0100011 | sw | 2 (MEMADR) | 5 (MEMWRITE) |
| 0110011 | R-type | 6 (EXECR) | — |
| 0010011 | I-type ALU | 8 (EXECI) | 7 (ALUWB) |
| 1101111 | jal | 9 (JAL) | 7 (ALUWB) |
| 1100011 | beq | 10 (BEQ) | — |
লক্ষ করুন, EXECR-এর পরের ঠিকানা স্বাভাবিকভাবেই ৭ (ALUWB), তাই Seq যথেষ্ট। কিন্তু EXECI (৮) আর JAL (৯)-ও ALUWB-তে যেতে চায়, অথচ তাদের পরের লাইন ৯ ও ১০. ক্লাসিক sequencer-এ “যেকোনো ঠিকানায় লাফ” নেই, তাই dispatch ROM 2-কে কাজে লাগানো হয়েছে — opcode দেখে ৭-এ পাঠায়। বাস্তব microsequencer-এ প্রায়ই সরাসরি next-address field থাকে, যা এই সমস্যা মেটায়, কিন্তু প্রতিটি শব্দ চওড়া করে।
- µPC 0
FETCHSeq - µPC 1
DECODEDispatch 1 - µPC 2
MEMADRDispatch 2 - µPC 3
MEMREADSeq - µPC 4
MEMWBFetch
| µPC | |
| Microinstruction-এর field | |
| Sequencing field | |
| পরের µPC-র হিসাব |
- µPC 0 — FETCH. সব instruction এখান থেকে শুরু। Field-গুলো সরাসরি signal: ALU = add, SRC1 = PC, SRC2 = 4, memory থেকে IR-এ, PC ← ALU ফল। Sequencing = Seq, তাই পরের µPC = 0 + 1 = 1.
- µPC 1 — DECODE. A, B পড়া, আগাম লক্ষ্য। Sequencing = Dispatch 1: sequencer IR-এর opcode 0000011-কে ঠিকানা ধরে ROM 1 পড়ে — উত্তর ২. এখানেই instruction-এর ধরন অনুযায়ী পথ আলাদা হয়, ঠিক FSM-এর S1-এর শাখার মতো।
- µPC 2 — MEMADR. ALUOut ← A + imm. lw আর sw দুজনেই এখানে আসে, তাই আবার opcode দেখতে হয়: Dispatch 2, ROM2[0000011] = ৩ (sw হলে ৫)।
- µPC 3 — MEMREAD. Memory ALUOut ঠিকানা থেকে পড়ে MDR-এ। Seq: ৩ + ১ = ৪. Microprogram-এ “পরের লাইন” সবচেয়ে সস্তা — শুধু একটি incrementer.
- µPC 4 — MEMWB. x[rd] ← MDR. Sequencing = Fetch: µPC ← ০. lw-এ পাঁচটি microinstruction — FSM-এর পাঁচ state-এর সাথে এক-এক মিল।
- অন্য opcode হলে। sw-এর পথ: 0 → 1 → 2 → 5 (ROM 2 → ৫), ৪ cycle; beq-এর: 0 → 1 → 10 (ROM 1 → ১০), ৩ cycle. Control বদলাতে চাইলে circuit নয়, শুধু টেবিল দুটি আর control store বদলান — এটাই microprogramming-এর প্রতিশ্রুতি।
এখন একই microprogram আর dispatch ROM একটি ছোট sequencer-এ চালানো যাক — lw আর beq-এর জন্য:
// Microprogrammed control: a control store (ROM) + micro-PC + sequencer with two dispatch ROMs.
// Fields: label, ALU, SRC1, SRC2, Register, Memory, PCWrite, Sequencing.
const STORE = [
["FETCH", "add", "PC", "4", "-", "Read PC->IR", "ALU", "Seq"],
["DECODE", "add", "OldPC", "Imm", "Read A,B", "-", "-", "Disp1"],
["MEMADR", "add", "A", "Imm", "-", "-", "-", "Disp2"],
["MEMREAD", "-", "-", "-", "-", "Read ALUOut", "-", "Seq"],
["MEMWB", "-", "-", "-", "Write MDR", "-", "-", "Fetch"],
["MEMWRITE", "-", "-", "-", "-", "Write ALUOut", "-", "Fetch"],
["EXECR", "funct", "A", "B", "-", "-", "-", "Seq"],
["ALUWB", "-", "-", "-", "Write ALUOut","-", "-", "Fetch"],
["EXECI", "funct", "A", "Imm", "-", "-", "-", "Disp2"],
["JAL", "add", "OldPC", "4", "-", "-", "ALUOut", "Disp2"],
["BEQ", "sub", "A", "B", "-", "-", "ALUOut-cond", "Fetch"],
];
const OPC = { lw: "0000011", sw: "0100011", R: "0110011", I: "0010011", jal: "1101111", beq: "1100011" };
const DISP1 = { "0000011": 2, "0100011": 2, "0110011": 6, "0010011": 8, "1101111": 9, "1100011": 10 };
const DISP2 = { "0000011": 3, "0100011": 5, "0010011": 7, "1101111": 7 };
function run(kind) {
const opcode = OPC[kind]; let upc = 0, steps = 0;
console.log(`--- ${kind} (opcode ${opcode}) ---`);
do {
const [label, alu, s1, s2, reg, mem, pcw, seq] = STORE[upc];
let next;
if (seq === "Seq") next = upc + 1; // micro-PC + 1
else if (seq === "Fetch") next = 0; // back to FETCH
else next = (seq === "Disp1" ? DISP1 : DISP2)[opcode]; // ROM indexed by opcode
console.log(`uPC ${String(upc).padStart(2)} ${label.padEnd(8)} ALU=${alu.padEnd(5)} ${s1}/${s2}`.padEnd(38) +
`Reg=${reg.padEnd(12)} Mem=${mem.padEnd(12)} PC=${pcw.padEnd(11)} ${seq} -> ${next}`);
upc = next; steps++;
} while (upc !== 0);
console.log(`${kind}: ${steps} microinstructions = ${steps} clock cycles`);
}
run("lw"); run("beq");আউটপুট:
--- lw (opcode 0000011) ---
uPC 0 FETCH ALU=add PC/4 Reg=- Mem=Read PC->IR PC=ALU Seq -> 1
uPC 1 DECODE ALU=add OldPC/Imm Reg=Read A,B Mem=- PC=- Disp1 -> 2
uPC 2 MEMADR ALU=add A/Imm Reg=- Mem=- PC=- Disp2 -> 3
uPC 3 MEMREAD ALU=- -/- Reg=- Mem=Read ALUOut PC=- Seq -> 4
uPC 4 MEMWB ALU=- -/- Reg=Write MDR Mem=- PC=- Fetch -> 0
lw: 5 microinstructions = 5 clock cycles
--- beq (opcode 1100011) ---
uPC 0 FETCH ALU=add PC/4 Reg=- Mem=Read PC->IR PC=ALU Seq -> 1
uPC 1 DECODE ALU=add OldPC/Imm Reg=Read A,B Mem=- PC=- Disp1 -> 10
uPC 10 BEQ ALU=sub A/B Reg=- Mem=- PC=ALUOut-cond Fetch -> 0
beq: 3 microinstructions = 3 clock cycles
lw: µPC 0 → 1 → 2 → 3 → 4 → 0. দ্বিতীয় লাইনে Disp1 -> 2 — DECODE opcode 0000011 দিয়ে ROM 1 পড়েছে; তৃতীয় লাইনে Disp2 -> 3. beq: Disp1 -> 10 সরাসরি BEQ microinstruction-এ, যেখানে PC=ALUOut-cond আর Fetch -> 0. গোনা ৫ আর ৩ — ঠিক FSM-এর cycle সংখ্যা, কারণ প্রতিটি microinstruction এক cycle. নিজে চেষ্টা করুন: শেষ লাইনে run("sw"); run("jal"); যোগ করুন — sw-এর পথে ৫ নম্বর, jal-এর পথে 9 → 7 দেখবেন।
৭ · Horizontal বনাম vertical, hardwired বনাম microprogrammed
একটি microinstruction-এ signal কীভাবে লেখা হবে, তার দুই প্রান্ত:
Horizontal microcode
প্রায় প্রতিটি control line-এর নিজস্ব বিট। আমাদের নকশায়: AdrSrc ১, IRWrite ১, ALUSrcA ২, ALUSrcB ২, ALUOp ২, ResultSrc ২, PCUpdate ১, Branch ১, RegWrite ১, MemWrite ১ = ১৪ বিট, সাথে sequencing ২ বিট — প্রতি শব্দ ১৬ বিট, ১১টি শব্দে ১৭৬ বিট। Decode লাগে না, তাই দ্রুত; এক শব্দে অনেক কাজ একসাথে বলা যায়। বড় machine-এ এই শব্দ শতাধিক বিট চওড়া হতো।
চওড়া, দ্রুত, বড় ROMVertical microcode
Signal-গুলো ছোট কোডে লেখা, ব্যবহারের আগে decoder দিয়ে খোলা হয়। চরম উদাহরণ: আমাদের ১১টি আলাদা microinstruction-কে ⌈log2 ১১⌉ = ৪ বিটের একটি “micro-operation নম্বর” দিয়ে চেনানো, আর একটি decoder সেটি থেকে ১৪টি signal বানায়। ROM ছোট, কিন্তু decode-এর বাড়তি delay, আর সাধারণত এক শব্দে কম কাজ — তাই একই কাজে বেশি শব্দ।
সরু, ছোট ROM, decode লাগেএই দুটি horizontal আর vertical microcode-এর মাঝামাঝি নকশাই বাস্তবে বেশি দেখা যায় (field-encoded): যেমন অংশ ৬-এর টেবিলে প্রতিটি field-এর কয়েকটি মাত্র বিকল্প, তাই প্রতি field ২ বিট, ৭টি field-এ ১৪ বিট।
Hardwired বনাম microprogrammed — কোনটি কখন
| দিক | Hardwired (FSM-এর gate/PLA) | Microprogrammed (control store) |
|---|---|---|
| গতি | দ্রুত — signal সরাসরি logic থেকে | প্রতি cycle-এ ROM পড়ার delay, তাই সাধারণত একটু ধীর |
| বদল ও ভুল সংশোধন | কঠিন — নতুন circuit, নতুন chip | সহজ — ROM-এর বিষয়বস্তু বদল; লেখা যায় এমন store হলে বাজারে আসার পরেও |
| নকশার সময় | Instruction বাড়লে logic জটিল হয় | নিয়মিত কাঠামো; নতুন instruction = নতুন কয়েকটি লাইন |
| কোথায় মানায় | RISC, সরল instruction, pipeline | CISC — শত শত instruction, নানা দৈর্ঘ্য, কোনোটি শত cycle নেয় |
| উদাহরণ | বেশিরভাগ RISC-V ও ARM core | IBM System/360-এর বেশিরভাগ মডেল, VAX; আজকের x86-এর জটিল instruction |
১৯৬০ থেকে ১৯৭০-এর দশকে এক প্রবল যুক্তি ছিল: তখন ROM ছিল তুলনামূলক দ্রুত আর সস্তা, আর main memory ছিল ধীর। তাই জটিল instruction একবার memory থেকে এনে ভেতরের দ্রুত microcode দিয়ে অনেক কাজ করানো লাভজনক। পরে cache এসে main memory-কে কার্যত দ্রুত করল, আর compiler সরল instruction ভালো ব্যবহার করতে শিখল — তখন microcode-এর এই সুবিধা কমে গেল (ভাবনার প্রশ্ন ৩ দেখুন)।
৮ · Microcode-এর ইতিহাস আর আজকের CPU — System/360 থেকে x86-এর update
এক ISA, অনেক মডেল। ১৯৬৪ সালে ঘোষিত IBM System/360 সম্ভবত microprogramming-এর সবচেয়ে প্রভাবশালী ব্যবহার। IBM চেয়েছিল ছোট আর বড়, সস্তা আর দামি সব মডেল একই ISA মানবে, যাতে গ্রাহক বড় মডেলে গেলে software আবার লিখতে না হয়। ছোট মডেলের datapath সরু (যেমন ৮ বিট), বড় মডেলের চওড়া — তবু বেশিরভাগ মডেলে আলাদা microcode একই ৩২-বিট instruction-গুলো চালাত; শুধু সবচেয়ে দ্রুত কয়েকটি মডেল hardwired ছিল বলে জানা যায়। কিছু মডেল microcode দিয়ে IBM-এর পুরোনো machine-এর (যেমন 1401) প্রোগ্রামও চালাতে পারত — microcode-ভিত্তিক emulation. পরের দশকে DEC-এর VAX (১৯৭৭) জটিল CISC-এর শিখরে পৌঁছায়: polynomial হিসাবের মতো একক instruction আর অনেক addressing mode, সবই বড় microcode-এ।
আজকের x86-এ microcode. আধুনিক x86 প্রসেসর বেশিরভাগ সাধারণ instruction (যেমন add, mov) hardwired decoder দিয়ে সরাসরি কয়েকটি micro-op-এ ভাঙে, আর সেগুলো pipeline-এ চলে। কিন্তু জটিল বা কম ব্যবহৃত instruction — যেমন string-এর লম্বা copy, কিছু system instruction, বা অস্বাভাবিক ক্ষেত্রের সামলানো — একটি microcode ROM থেকে micro-op-এর দীর্ঘ ক্রম হিসেবে আসে। অর্থাৎ আজকের CPU-তে hardwired আর microcode পাশাপাশি থাকে: সাধারণ পথ দ্রুত, জটিল পথ নমনীয়।
Microcode update. Intel-এর ক্ষেত্রে মোটামুটি Pentium Pro-র যুগ থেকে (১৯৯০-এর দশকের মাঝামাঝি) প্রসেসরের microcode-এর একটি অংশ চালু হওয়ার সময় সংশোধন করা যায়; AMD-ও একই ধরনের ব্যবস্থা রাখে। সংশোধন আসে BIOS/UEFI-এর update-এ, অথবা operating system চালু হওয়ার সময় লোড করে (Windows Update বা Linux-এর microcode package). Update সাধারণত নির্মাতার digital signature দিয়ে যাচাই করা হয়, আর বন্ধ করলে মুছে যায় — প্রতিবার আবার লোড হয়। ২০১৮ সালে প্রকাশিত Spectre-এর একটি রূপের (শাখার লক্ষ্যে অনুমান) বিরুদ্ধে microcode update নতুন নিয়ন্ত্রণ-ব্যবস্থা যোগ করেছিল, যা operating system ব্যবহার করে branch predictor-এর প্রভাব সীমিত করে। Meltdown-এর প্রধান প্রতিরক্ষা অবশ্য ছিল operating system-এ (kernel-এর page table আলাদা রাখা, KPTI) আর পরের chip-এ hardware বদল — সব ত্রুটি microcode দিয়ে সারানো যায় না। এই আক্রমণগুলোর ধারণা আর প্রতিরক্ষা পর্ব ৪১-এ (Speculative execution আর নিরাপত্তা)।
কেন pipelining multi-cycle-কে সরিয়ে দিল। Multi-cycle-এ প্রতিটি cycle-এ hardware-এর বেশিরভাগ অংশ বসে থাকে: fetch-এর সময় register file অলস, MemRead-এর সময় ALU অলস। পর্ব ৩৫-এর (Pipelining) ভাবনা হলো: একই পাঁচ ধাপ রেখে প্রতিটি ধাপে আলাদা instruction রাখা — গার্মেন্টস কারখানার assembly line-এর মতো, যেখানে কেউ কাটে, কেউ সেলাই করে, কেউ ইস্ত্রি — সবাই একসাথে, ভিন্ন ভিন্ন শার্টে। তখন cycle time ছোট থাকে (multi-cycle-এর মতো) আর আদর্শ CPI প্রায় ১ (single-cycle-এর মতো)। দাম হলো আলাদা instruction ও data memory (বা cache) আবার ফিরে আসা, আর hazard সামলানো (পর্ব ৩৬). Multi-cycle-এর ধাপ-ভাগ, মাঝের register আর FSM-এ ভাবার অভ্যাস — সবই pipeline-এর সিঁড়ি।
আরও গভীরে: খুব ছোট, কম-শক্তির RISC-V core-এ multi-cycle নকশা আজও টিকে আছে — যেমন খোলা-উৎসের PicoRV32 সরাসরি pipeline ছাড়াই কয়েক cycle-এ প্রতিটি instruction চালায়, কারণ এতে chip-এর জায়গা আর শক্তি কম লাগে; আর আরও চরম কিছু নকশা ডেটাকে এক বিট করে প্রক্রিয়া করে। x86-এ microcode শুধু জটিল instruction নয়, কিছু ব্যতিক্রমী ঘটনা (যেমন অস্বাভাবিক floating-point মান বা কিছু memory-র সীমা পেরোনো) সামলাতেও “microcode assist” হিসেবে আসে, যা হঠাৎ শত cycle খরচ করতে পারে। গবেষণা ও শিল্পে microcode-এর নিরাপত্তা — update-এর signature, আর microcode বিশ্লেষণ — এখন আলাদা একটি ক্ষেত্র।
পরীক্ষা ও ইন্টারভিউতে — কয়েকটি প্রচলিত ধরনের প্রশ্ন আর এক লাইনের উত্তর:
- Multi-cycle-এ IR আর MDR আলাদা কেন? একটিই memory; fetch-এর পরে memory data-র কাজে লাগে, তখনো instruction-এর বিট হাতে রাখতে IR, আর পড়া data পরের cycle-এ লিখতে MDR.
- lw, sw, R-type, beq কয় cycle? ৫, ৪, ৪, ৩ (jal এই নকশায় ৪; MIPS বইয়ে jump ৩).
- শাখার লক্ষ্য decode-এ কেন হিসাব হয়? ALU তখন অলস; আগাম হিসাব করে রাখলে beq ৩ cycle-এ শেষ হয়, আর অকাজে লাগলে কোনো ক্ষতি নেই।
- Multi-cycle কি সবসময় single-cycle-এর চেয়ে দ্রুত? না; CPI < Tsingle/Tmulti হলেই জেতে — অসম ধাপ আর register-এর খরচ এই শর্ত ভাঙতে পারে।
- Hardwired বনাম microprogrammed? Hardwired দ্রুত কিন্তু অনমনীয়; microprogrammed নমনীয়, CISC-বান্ধব, update করা যায়, কিন্তু সাধারণত ধীর।
৯ · AI-এর যুগে — FSM আর microcode AI দিয়ে, যাচাই simulation দিয়ে
“Multi-cycle RISC-V-এর control FSM দিন” বললে AI দ্রুত একটি টেবিল দেয়। কিন্তু এই বিষয়ে তার ভুলগুলো নির্দিষ্ট ধরনের:
- MIPS আর RISC-V মেশানো: RISC-V-এর নকশায় MIPS-এর opcode,
j-এর ৩ cycle, বা PC + 4-এর পরে offset-কে ৪ দিয়ে গুণ আর PC + 4-এর সাথে যোগ করা (RISC-V-এ লক্ষ্য = instruction-এর নিজের ঠিকানা + offset). - Cycle গোনায় ভুল: সব instruction-কে ৫ cycle ধরা, বা beq-কে ৪ বলা; mix-এর CPI-তে ভগ্নাংশ ১০০% না মেলা।
- Signal-এর ভুল সময়: lw-এর MemRead cycle-এই RegWrite চালু করা, বা BEQ state-এ ALUOut-কে লক্ষ্য হিসেবে ধরে আবার একই cycle-এ ALUOut-এ লক্ষ্য হিসাব করা।
- Single বনাম multi তুলনায় অন্ধ বিশ্বাস: “multi-cycle সবসময় দ্রুত” — অথচ ওপরের পরিস্থিতি A আর B-তে হারে।
- বানানো ইতিহাস ও specs: কোনো chip-এর control store-এর আকার, কোন মডেল hardwired ছিল, বা কোন microcode update কোন দুর্বলতা সারিয়েছে — আত্মবিশ্বাসী কিন্তু ভুল সংখ্যা।
AI-কে বই নয়, সহকারী হিসেবে ব্যবহার করুন — রীতি নির্দিষ্ট করে আর যাচাইযোগ্য রূপে চেয়ে নিন:
AI-এর উত্তর যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Wilkes-এর microprogramming-এর ধারণা এত প্রভাবশালী হলো কেন — শুধু একটি circuit-কৌশল, নাকি তার চেয়ে বেশি?
১৯৪০-এর দশকের শেষে কেমব্রিজে Maurice Wilkes-এর দল EDSAC বানায়, যা প্রথম দিকের stored-program কম্পিউটারগুলোর একটি। তাঁদের অভিজ্ঞতা ছিল: datapath — adder, register, memory — অনেকটা নিয়মিত কাঠামোর, নকশা করা তুলনামূলক সহজ। কিন্তু control — কোন মুহূর্তে কোন gate খুলবে — ছিল অগোছালো, জটিল আর ভুলে ভরা। প্রতিটি নতুন instruction মানে নতুন তার, নতুন ভুলের সম্ভাবনা। সাধারণত ১৯৫১ সালের একটি সম্মেলনের প্রবন্ধকে Wilkes-এর প্রস্তাবের সূচনা ধরা হয়: control-এর প্রতিটি ধাপকে একটি “microinstruction” হিসেবে একটি নিয়মিত matrix-এ (তাঁর নকশায় diode matrix) লিখে রাখা, আর একটি ছোট sequencer দিয়ে সেগুলো ক্রমে পড়া।
এর গুরুত্ব তিন স্তরে। প্রথম, প্রকৌশলে: অগোছালো logic-কে নিয়মিত memory-তে রূপান্তর — যা বানাতে, পরীক্ষা করতে আর বদলাতে সহজ। এই পর্বের অংশ ৬-এ দেখেছেন, FSM-এর প্রতিটি state-ই একটি ROM-এর লাইন হয়ে যায়। দ্বিতীয়, পদ্ধতিতে: control নকশা একটি “প্রোগ্রামিং” কাজ হয়ে গেল — microprogrammer আলাদা পেশা হয়ে ওঠে, আর ভুল সারাতে নতুন chip নয়, নতুন microcode লাগে। তৃতীয়, স্থাপত্যে: যেহেতু ISA এখন microcode-এর একটি “interpretation”, একই hardware-এ ভিন্ন ISA, বা ভিন্ন hardware-এ একই ISA চালানো সম্ভব হলো — যা System/360-এর পরিবার-ধারণার ভিত্তি (প্রশ্ন ২). Wilkes-এর দলের পরের machine EDSAC 2 (১৯৫০-এর দশকের শেষ) microprogrammed control ব্যবহার করেছিল বলে জানা যায়।
তবে প্রথম দিকে microprogramming ব্যাপক হয়নি, কারণ দ্রুত আর সস্তা control store তখন সহজলভ্য ছিল না। ১৯৬০-এর দশকে memory প্রযুক্তি এগোলে ধারণাটি মূলধারায় আসে। আজও “control-কে টেবিল হিসেবে ভাবা” — ROM, PLA, বা Verilog-এর case টেবিল — প্রতিটি নকশাকারের সাধারণ অভ্যাস, এমনকি hardwired নকশাতেও।
মূল উপলব্ধি: Microprogramming-এর আসল উপহার হলো জটিলতা সামলানোর একটি উপায় — অগোছালো control-কে নিয়মিত, বদলযোগ্য, প্রোগ্রামের মতো একটি টেবিলে রূপান্তর।
প্র ০২IBM System/360 কীভাবে microcode দিয়ে “এক ISA, অনেক দামের মডেল” সম্ভব করল, আর এর প্রভাব আজকের কম্পিউটারে কোথায়?
১৯৬০-এর দশকের শুরুতে IBM-এর নিজেরই কয়েকটি আলাদা, পরস্পর-অসামঞ্জস্যপূর্ণ কম্পিউটার-লাইন ছিল — ব্যবসার জন্য একটি, বিজ্ঞানের জন্য আরেকটি। গ্রাহক বড় machine-এ যেতে চাইলে প্রোগ্রাম নতুন করে লিখতে হতো, যা ছিল ব্যয়বহুল আর ঝুঁকিপূর্ণ। ১৯৬৪ সালের এপ্রিলে ঘোষিত System/360 এই সমস্যার একটি সাহসী উত্তর: একটিই ISA, ছোট থেকে বড় অনেক মডেল, দাম আর গতিতে বহুগুণ পার্থক্য, কিন্তু একই প্রোগ্রাম সবখানে চলবে। এখান থেকেই “architecture” শব্দটি প্রোগ্রামারের দেখা চুক্তি অর্থে জনপ্রিয় হয়, আর তার বাস্তবায়ন — organization বা microarchitecture — আলাদা ধারণা হয়ে ওঠে।
কৌশলটি ছিল microprogramming. সস্তা মডেলের datapath সরু — বলা হয় সবচেয়ে ছোট মডেলগুলোর কিছু ৮-বিট datapath দিয়ে ৩২-বিট instruction চালাত, ফলে একটি ৩২-বিট যোগ কয়েকটি microinstruction-এ ৮ বিট করে হতো। দামি মডেলের datapath চওড়া, microcode ছোট আর দ্রুত। সবচেয়ে দ্রুত কয়েকটি মডেল গতির জন্য hardwired ছিল বলে জানা যায়। এই পর্বের অংশ ৫-এর পরিস্থিতি D মনে করুন: যখন instruction-এর কাজের পরিমাণ hardware-এর তুলনায় বদলায়, multi-cycle-ধাঁচের microcode সেই পার্থক্য নমনীয়ভাবে শোষণ করে।
আরেকটি প্রায়ই-বলা সুবিধা: কিছু System/360 মডেলে বাড়তি microcode দিয়ে IBM-এর পুরোনো জনপ্রিয় machine (যেমন 1401)-এর প্রোগ্রাম চালানো যেত — emulation. এতে গ্রাহক পুরোনো software ফেলে না দিয়েই নতুন machine কিনতে পারতেন। ব্যবসায়িক সাফল্য বিশাল ছিল, আর System/360-এর উত্তরসূরি IBM mainframe আজও backward compatibility বজায় রেখে ব্যাংক আর বড় প্রতিষ্ঠানে চলে বলে জানা যায়।
আজকের প্রভাব সবখানে। x86-এর ইতিহাসও একই শিক্ষা দেয়: ১৯৭৮ সালের 8086-এর প্রোগ্রাম আজকের chip-এ চলতে পারে, কারণ ISA-কে চুক্তি হিসেবে রাখা হয়েছে আর ভেতরের বাস্তবায়ন বারবার বদলেছে। ARM-এর ছোট microcontroller থেকে server chip পর্যন্ত একই ISA-পরিবার, RISC-V-এর একই base ISA-র ওপর ভিন্ন ভিন্ন core — সবই System/360-এর ধারণার উত্তরাধিকার।
মূল উপলব্ধি: ISA-কে বাস্তবায়ন থেকে আলাদা করা — আর microcode দিয়ে সেই আলাদা করাকে সস্তায় সম্ভব করা — কম্পিউটার শিল্পের ব্যবসায়িক মডেলই বদলে দিয়েছিল।
প্র ০৩১৯৮০-এর দশকে RISC-এর প্রবক্তারা কেন microcode-কে সমস্যা বলে দেখলেন, আর তবু microcode পুরো হারিয়ে গেল না কেন?
১৯৭০-এর দশকের শেষে microcode-ভিত্তিক CISC — বিশেষত VAX — শিখরে। যুক্তি ছিল “semantic gap” কমানো: উচ্চস্তরের ভাষার কাজের কাছাকাছি জটিল instruction দিলে প্রোগ্রাম ছোট আর দ্রুত হবে। কিন্তু মাপজোখে অন্য ছবি এল। IBM-এ John Cocke-এর দলের 801 প্রকল্প (১৯৭০-এর দশকের মাঝামাঝি থেকে), Berkeley-তে David Patterson-এর RISC আর Stanford-এ John Hennessy-র MIPS প্রকল্প দেখাল: compiler-এর তৈরি কোডে বেশিরভাগ সময় অল্প কয়েকটি সরল instruction — load, store, যোগ, শাখা — চলে; জটিল instruction কম ব্যবহৃত হয়, আর কখনো কখনো সেগুলোর কাজ কয়েকটি সরল instruction দিয়ে করলে দ্রুত হয়। Patterson ও Ditzel-এর ১৯৮০ সালের “The Case for the Reduced Instruction Set Computer” প্রবন্ধ এই বিতর্কের পরিচিত সূচনা।
Microcode-এর বিরুদ্ধে যুক্তিগুলো ছিল এই পর্বেরই উপাদান। এক, প্রতিটি microinstruction-এ ROM পড়ার delay — hardwired-এর চেয়ে ধীর। দুই, জটিল, নানা দৈর্ঘ্যের instruction pipeline-এ বসানো কঠিন; অংশ ৮-এ দেখেছেন, pipeline চায় প্রায় সমান ধাপের সরল instruction. তিন, cache আসায় main memory আর ধীর নয়, ফলে “একবার এনে ভেতরে অনেক কাজ” — microcode-এর মূল অর্থনৈতিক যুক্তি — দুর্বল হলো। চার, chip-এ যে জায়গা control store নিত, সেখানে বেশি register বা cache বসানো লাভজনক। RISC নকশা তাই hardwired control আর pipeline বেছে নেয়।
তবু microcode টিকে গেল, কারণ বাজার CISC ছাড়েনি — x86-এর বিশাল software ভান্ডার আর backward compatibility-এর চাহিদা। Intel আর AMD সমাধান করল মাঝামাঝি পথে: সাধারণ x86 instruction hardwired decoder দিয়ে RISC-ধাঁচের micro-op-এ ভেঙে pipeline-এ চালানো, আর বিরল, জটিল instruction-এর জন্য microcode ROM রাখা। ফলে “RISC বনাম CISC”-এর লড়াই অনেকটা ভেতরে মিলেমিশে গেছে: x86-এর ভেতরে RISC-ধাঁচের core, বাইরে CISC-এর চুক্তি, আর পাশে একটি microcode ইঞ্জিন। আরেকটি কারণ: microcode update — বাজারে আসার পরেও ভুল সারানোর সুযোগ — এত মূল্যবান যে নির্মাতারা তা ছাড়তে চান না।
মূল উপলব্ধি: প্রযুক্তির খরচ বদলালে নকশার সেরা উত্তরও বদলায়; microcode সাধারণ পথ থেকে সরে গিয়ে বিরল আর জটিল কাজের, আর সংশোধনের হাতিয়ার হয়ে টিকে আছে।
প্র ০৪Microcode update দিয়ে বাজারে থাকা প্রসেসরের নিরাপত্তা-দুর্বলতা সারানো যায় — এর সম্ভাবনা আর সীমা কী?
Hardware-এর ভুল ঐতিহাসিকভাবে স্থায়ী ছিল। ১৯৯৪ সালে Intel Pentium-এর floating-point ভাগের ত্রুটি (FDIV bug) প্রকাশ পেলে কোম্পানিকে শেষ পর্যন্ত বিপুল খরচে chip বদলে দেওয়ার প্রস্তাব দিতে হয়েছিল। এর কিছু পরে, সাধারণত Pentium Pro-র যুগ থেকে, Intel প্রসেসরে microcode-এর একটি অংশ চালু হওয়ার সময় update করার ব্যবস্থা রাখে, আর AMD-ও একই ধরনের ব্যবস্থা ব্যবহার করে। Update থাকে BIOS/UEFI-তে, বা operating system চালু হওয়ার সময় লোড করে; নির্মাতার digital signature দিয়ে যাচাই হয়, আর প্রতিবার চালু হলে আবার লোড করতে হয়। বাংলাদেশে যাঁরা ল্যাপটপ বা ডেস্কটপ ব্যবহার করেন, তাঁদের Windows Update বা Linux-এর microcode package অনেক সময় নিঃশব্দে এই কাজটি করে দেয় — তাই operating system আর BIOS হালনাগাদ রাখা কেবল software-এর বিষয় নয়।
২০১৮ সালের জানুয়ারিতে প্রকাশিত Spectre আর Meltdown এই ব্যবস্থার সবচেয়ে বড় পরীক্ষা। দুটিই অনুমাননির্ভর execution-এর পার্শ্বপ্রতিক্রিয়া — cache-এ রেখে যাওয়া চিহ্ন — কাজে লাগায়। Spectre-এর একটি রূপের (শাখার লক্ষ্য অনুমানে প্রভাব ফেলা) বিরুদ্ধে microcode update নতুন control যোগ করে, যার মাধ্যমে operating system দরকারি মুহূর্তে branch predictor-এর পুরোনো তথ্য মুছে দিতে বা সীমিত করতে পারে; compiler-এর দিক থেকে “retpoline”-এর মতো কৌশলও আসে। Meltdown-এর প্রধান প্রতিরক্ষা ছিল operating system-এ — kernel-এর memory-কে user প্রোগ্রামের page table থেকে আলাদা রাখা (KPTI) — আর নতুন chip-এ hardware সংশোধন। পরবর্তী বছরগুলোতে আরও কিছু দুর্বলতা microcode update দিয়ে আংশিক বা পুরো সামলানো হয়েছে বলে নির্মাতারা জানিয়েছেন।
সীমাগুলোও স্পষ্ট। এক, microcode শুধু সেই অংশ বদলাতে পারে যা microcode-এর নিয়ন্ত্রণে; hardwired decoder, cache-এর গঠন বা pipeline-এর মূল আচরণ বদলানো যায় না। দুই, অনেক সংশোধনের দাম পারফরম্যান্স — কিছু কাজের চাপে উল্লেখযোগ্য ধীরতা দেখা গেছে। তিন, update-এর নিজের নিরাপত্তা: signature ব্যবস্থা ভাঙলে microcode-ই আক্রমণের পথ হতে পারে, তাই এটি গবেষণার সক্রিয় বিষয়। চার, update সব ব্যবহারকারীর কাছে পৌঁছায় না — পুরোনো যন্ত্রে নির্মাতা সমর্থন বন্ধ করে দিলে দুর্বলতা থেকেই যায়।
মূল উপলব্ধি: Microcode update hardware-কে কিছুটা software-এর মতো সংশোধনযোগ্য করেছে, কিন্তু এটি একটি সীমিত, দামি আর নিজেও সুরক্ষা-দাবি করা হাতিয়ার — নকশার সময়ের যত্নের বিকল্প নয়।
প্র ০৫Pipelining যখন এত ভালো, তখন বিশ্ববিদ্যালয়ে আর চাকরির পরীক্ষায় এখনো multi-cycle শেখানো হয় কেন — আর বাস্তবে এটি কোথায় টিকে আছে?
প্রথম কারণ শেখার সিঁড়ি। Single-cycle থেকে সরাসরি pipeline-এ গেলে একসাথে তিনটি নতুন ধারণা আসে: ধাপে ভাঙা, ধাপের মাঝে register, আর একই সময়ে কয়েকটি instruction. Multi-cycle প্রথম দুটি আলাদা করে শেখায় — এক সময়ে একটিই instruction, তাই hazard নেই — আর control-কে FSM হিসেবে ভাবতে শেখায়। Pipeline-এর IF, ID, EX, MEM, WB ধাপগুলো multi-cycle-এর পাঁচ ধাপেরই উত্তরসূরি; IR, A, B, ALUOut, MDR-এর জায়গায় আসে pipeline register. যিনি multi-cycle-এর সংকেত-টেবিল বোঝেন, তিনি pipeline-এর control-ও সহজে বোঝেন।
দ্বিতীয় কারণ হিসাবের দক্ষতা। Multi-cycle-এর প্রশ্ন — cycle গোনা, mix থেকে CPI, speedup — iron law-এর নিখুঁত অনুশীলন, আর পরীক্ষায় এগুলো নিয়মিত আসে কারণ এগুলো যাচাইযোগ্য সংখ্যা দেয়। “Multi-cycle সবসময় দ্রুত নয়” — অংশ ৫-এর এই ফল একটি গুরুত্বপূর্ণ প্রকৌশল-শিক্ষা: অনুমান নয়, মেপে সিদ্ধান্ত।
তৃতীয় কারণ বাস্তব ব্যবহার। খুব ছোট, কম-শক্তির core-এ pipeline-এর বাড়তি register, forwarding আর hazard-এর logic জায়গা আর শক্তি খায়। তাই অনেক ছোট microcontroller, FPGA-তে বসানো “soft” প্রসেসর, আর বড় chip-এর ভেতরে নিয়ন্ত্রণের কাজে বসানো ছোট core-এ multi-cycle বা দুই-তিন ধাপের সরল নকশা দেখা যায় — যেমন খোলা-উৎসের PicoRV32, যার নকশাকাররা আকার ছোট রাখাকে প্রধান লক্ষ্য বলেছেন। এর ওপর, যেকোনো জটিল hardware block — division unit, DMA controller, memory controller — আসলে FSM + datapath, অর্থাৎ multi-cycle ধাঁচে কাজ করে: কয়েক cycle-এ একটি কাজ, প্রতি cycle-এ একই hardware-এর পুনর্ব্যবহার।
চতুর্থত, microcode-এর ধারণা বুঝতে multi-cycle-এর FSM ছাড়া উপায় নেই — আর microcode আজও x86-এ, update-এ আর নিরাপত্তা-বিতর্কে জীবন্ত। চাকরির ইন্টারভিউতে, বিশেষত hardware-verification বা embedded পদে, “একটি FSM-controlled datapath নকশা করুন” ধরনের প্রশ্ন তাই স্বাভাবিক।
মূল উপলব্ধি: Multi-cycle আধুনিক উচ্চগতির CPU-র নকশা নয়, কিন্তু ধাপ, register আর FSM-এ ভাবার এই ভাষাই pipeline, microcode আর প্রায় প্রতিটি hardware block-এর ভিত্তি।
অনুশীলন
কাগজ-কলমে আগে নিজে করুন, তারপর পূর্ণ সমাধান মিলিয়ে নিন। (রীতি: lw ৫, sw ৪, R ও I-type ৪, beq ৩, jal ৪ cycle.)
-
Cycle গুনুন: x10-এ একটি array-র ঠিকানা, x7 = 0. নিচের প্রোগ্রাম multi-cycle প্রসেসরে মোট কত cycle নেয়? মোট instruction কত, CPI কত? Single-cycle (Tc = ৮০০ ps) আর multi-cycle (Tc = ২০০ ps)-এ মোট সময় কত?
addi x5, x0, 3 loop: beq x5, x0, done lw x6, 0(x10) add x7, x7, x6 addi x10, x10, 4 addi x5, x5, -1 jal x0, loop done: sw x7, 0(x11)কতবার চলে: loop-এর দেহ (lw থেকে jal, ৫টি instruction) x5 = 3, 2, 1-এ মোট ৩ বার। beq চলে ৪ বার — ৩ বার শাখা নয়, শেষবার (x5 = 0) শাখা নিয়ে done-এ। শুরুর addi আর শেষের sw একবার করে।
Cycle: addi ১ × ৪ = ৪; beq ৪ × ৩ = ১২; প্রতি দেহ = lw ৫ + add ৪ + addi ৪ + addi ৪ + jal ৪ = ২১, তিনবারে ৬৩; sw ৪. মোট = ৪ + ১২ + ৬৩ + ৪ = ৮৩ cycle. Instruction = ১ + ৪ + ১৫ + ১ = ২১, CPI = ৮৩ / ২১ ≈ ৩.৯৫.
সময়: single-cycle = ২১ × ৮০০ = ১৬,৮০০ ps; multi-cycle = ৮৩ × ২০০ = ১৬,৬০০ ps. এখানে multi-cycle সামান্য এগিয়ে (speedup ≈ ১.০১), কারণ এই প্রোগ্রামে ৩-cycle-এর beq বেশি আর lw কম — mix বদলালেই ফল বদলায়।
-
গড় CPI আর speedup: একটি প্রোগ্রামে ৩০% lw, ১৫% sw, ৪০% R-type, ১০% beq, ৫% jal. Delay: memory ২৫০ ps, register file ১৫০ ps, ALU ২০০ ps, আর প্রতি cycle-এ register-এর খরচ (tpcq + tsetup) ৫০ ps. (ক) multi-cycle-এর গড় CPI, (খ) দুই নকশার Tc আর speedup, (গ) multi-cycle সমান হতে CPI সর্বোচ্চ কত হতে পারত?
(ক) CPI = ০.৩০ × ৫ + ০.১৫ × ৪ + ০.৪০ × ৪ + ০.১০ × ৩ + ০.০৫ × ৪ = ১.৫০ + ০.৬০ + ১.৬০ + ০.৩০ + ০.২০ = ৪.২০.
(খ) Single-cycle Tc = ৫০ + (২৫০ + ১৫০ + ২০০ + ২৫০ + ১৫০) = ১০৫০ ps (lw-এর পথ, register-এর খরচ একবার)। Multi-cycle Tc = ৫০ + max(২৫০, ১৫০, ২০০) = ৩০০ ps; গড় সময় = ৪.২ × ৩০০ = ১২৬০ ps. Speedup = ১০৫০ / ১২৬০ ≈ ০.৮৩ — multi-cycle প্রায় ১৭% ধীর।
(গ) সমান হতে CPI × ৩০০ = ১০৫০, অর্থাৎ CPI = ৩.৫. আমাদের ৪.২০ তার চেয়ে অনেক বেশি। বাড়তি প্রশ্ন: memory ১৫০ ps-এ নামলে? Single = ৫০ + ৮০০ = ৮৫০, multi Tc = ৫০ + ২০০ (এবার ALU সবচেয়ে ধীর) = ২৫০, সময় ১০৫০ — speedup ≈ ০.৮১. অসম ধাপ আর প্রতি-cycle-এর খরচই মূল বাধা।
-
Microinstruction লিখুন: অংশ ৬-এর microprogram-এ
bne(শাখা যদি A ≠ B) যোগ করতে হবে। (ক) নতুন microinstruction-টির প্রতিটি field লিখুন। (খ) bne আর beq দুটির opcode-ই 1100011 (পার্থক্য funct3-এ: beq 000, bne 001). Dispatch ROM 1 শুধু opcode দেখলে কী সমস্যা, আর দুটি সমাধান কী?(ক) ঠিকানা ১১-তে BNE: ALU = sub, SRC1 = A, SRC2 = B, Register = —, Memory = —, PCWrite = ALUOut-cond-not (Zero = 0 হলে PC ← ALUOut), Sequencing = Fetch. লক্ষ্য আগেই DECODE-এ ALUOut-এ রাখা আছে, তাই এটিও ৩ cycle.
(খ) ROM 1 একই opcode-এ একটিই ঠিকানা দিতে পারে, তাই bne আর beq আলাদা microinstruction-এ যেতে পারবে না — bne-ও BEQ-তে গিয়ে উল্টো আচরণ করবে। সমাধান ১: dispatch ROM-এর ঠিকানায় opcode-এর সাথে funct3-ও জোড়া (৭ + ৩ = ১০ বিট, ১০২৪টি ঘর — বড় কিন্তু সহজ)। সমাধান ২: একটিই BRANCH microinstruction রাখা, আর PCWrite-এর শর্ত hardware-এ funct3 দেখে বাছা (beq: Zero, bne: ¬Zero, blt: চিহ্নের তুলনা…) — বাস্তবে এটিই বেশি প্রচলিত, কারণ সব শাখা একই ধাপ নেয়।
-
আউটপুট অনুমান করুন: অংশ ৪-এর simulator-এ data word
0x48: 7-কে0x48: 0করা হলো, বাকি সব একই। শেষ লাইনে instruction, cycle, CPI আর x5, x6, x7, mem[0x4c]-এর মান কী হবে? চালানোর আগে লিখে ফেলুন।lw-এ x5 = 0, add-এ x6 = 0 + 0 = 0, sw-এ mem[0x4c] = 0. এবার
beq x5, x6, 8-এ 0 = 0, তাই শাখা নেওয়া হয়: PC ← 0x0C + 8 = 0x14. ফলে 0x10-এর দ্বিতীয় beq বাদ পড়ে, আর 0x14-এরadd x7, x6, x6চলে (x7 = 0), তারপর PC = 0x18-এ থামে। Instruction সংখ্যা তবু ৫ (lw, add, sw, beq, add), cycle = ৫ + ৪ + ৪ + ৩ + ৪ = ২০. আসল আউটপুটের শেষ লাইন:instructions = 5, cycles = 20, CPI = 4.00; x5 = 0, x6 = 0, x7 = 0, mem[0x4c] = 0শিক্ষা: একই প্রোগ্রামে data বদলালে নিয়ন্ত্রণের পথ বদলায়, আর multi-cycle-এ পথ বদলালে CPI-ও বদলায় (৩.৮০ থেকে ৪.০০) — কারণ ৩-cycle-এর একটি beq-এর জায়গায় ৪-cycle-এর add চলেছে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমMulti-cycle RISC-V প্রসেসরে lw instruction-এর পাঁচটি cycle-এর কাজ সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- IR ← Mem[PC]; PC ← PC + 4
- A ← x[rs1], B ← x[rs2]; ALUOut ← OldPC + imm (আগাম শাখার লক্ষ্য)
- ALUOut ← A + imm (data-র ঠিকানা)
- MDR ← Mem[ALUOut]
- x[rd] ← MDR
Harris–Harris RISC-V রীতির multi-cycle প্রসেসরে lw, sw, R-type আর beq যথাক্রমে কয় cycle নেয়?
- ৫, ৫, ৫, ৫
- ৫, ৫, ৪, ৪
- ৫, ৪, ৪, ৩
- ৫, ৪, ৩, ৩
একটি multi-cycle প্রসেসরে lw ৫, sw ৪, R-type ৪, beq ৩ cycle নেয়। একটি প্রোগ্রামে ২০% lw, ১০% sw, ৫০% R-type, ২০% beq. গড় CPI কত?
- ৪.২৫
- ৪.০
- ৩.৮
- ৫.০
Single-cycle নকশার Tc = ৮০০ ps. একই ISA-র multi-cycle নকশার Tc = ২০০ ps আর গড় CPI = ৪.১৪. একই প্রোগ্রামে কোনটি দ্রুত?
- Multi-cycle, ৪ গুণ দ্রুত, কারণ clock ৪ গুণ ছোট
- দুটি সমান দ্রুত
- Single-cycle, ৪.১৪ গুণ দ্রুত
- Single-cycle, প্রায় ৩.৫% দ্রুত (৮০০ বনাম ৮২৮ ps)
Multi-cycle RISC-V নকশায় beq-এর শাখার লক্ষ্য (instruction-এর ঠিকানা + offset) decode-এর cycle-এই ALU দিয়ে হিসাব করে রাখা হয়, যদিও তখনো জানা নেই instruction-টি শাখা কি না। কেন?
- ALU তখন অলস; এতে beq এক cycle কম নেয় আর ভুল হলে কোনো ক্ষতি নেই
- লক্ষ্যটি memory-তে লিখে রাখতে হয়
- Register file decode-এ নিজেই লক্ষ্য হিসাব করে, ALU শুধু যাচাই করে
- Fetch-এ হিসাব করলে imm জানা থাকে না বলে ভুল হয়, তাই এটি বাধ্যতামূলক নিয়ম
একটি microprogrammed control unit-এ microinstruction-এর sequencing field-এর মান “Dispatch”. Microsequencer পরের micro-PC কীভাবে পায়?
- বর্তমান micro-PC-র সাথে ১ যোগ করে
- সবসময় ঠিকানা ০-তে ফিরে
- Instruction-এর opcode দিয়ে একটি dispatch ROM পড়ে
- প্রোগ্রামের PC-র মান কপি করে
Horizontal আর vertical microcode-এর তুলনায় কোন কথাটি সঠিক?
- Horizontal-এর শব্দ সরু, vertical-এর চওড়া
- Horizontal চওড়া ও decode-হীন, তাই দ্রুত কিন্তু ROM বড়; vertical সরু, decode লাগে
- Vertical microcode আসলে hardwired control-এর অন্য নাম
- Vertical সবসময় দ্রুত, কারণ শব্দ ছোট
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Multi-cycle = ছোট ধাপ, ছোট clock, hardware পুনর্ব্যবহার. এক memory, এক ALU; মাঝের ফল IR, MDR, A, B, ALUOut (আর OldPC)-এ।
- পাঁচ ধাপ: Fetch (PC + 4) → Decode (A, B, আগাম লক্ষ্য) → Execute/ঠিকানা/শাখা শেষ → Memory বা R-type শেষ → Load-এর write-back. lw ৫, sw ৪, R ৪, beq ৩, jal ৪.
- Control = FSM: প্রতিটি state এক cycle আর তার signal; S1 আর S2-তে opcode দেখে শাখা; PCWrite = PCUpdate + Branch · Zero.
- পারফরম্যান্স: সময় = IC × CPI × Tc; CPI = Σ fi × CPIi. Multi-cycle জেতে শুধু CPI < Tsingle/Tmulti হলে — সুষম ধাপ আর নানা দৈর্ঘ্যের instruction-এ জেতে, অসম ধাপ আর register-এর খরচে হারে।
- Microprogramming: control store + micro-PC + sequencer (Seq, Fetch, Dispatch ROM). Horizontal চওড়া-দ্রুত, vertical সরু-decode-নির্ভর; hardwired দ্রুত-অনমনীয়, microprogrammed নমনীয়-CISC-বান্ধব — System/360 থেকে x86-এর microcode update পর্যন্ত।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩৫ · Pipelining — পাঁচ ধাপের pipeline আর speedup-এর হিসাবপরবর্তী পর্বMulti-cycle-এর ধাপগুলোকে একসাথে চালানো।
- পর্ব ৩৩ · Control unit — hardwired control আর control signal-এর টেবিলআগের পর্বSingle-cycle-এর combinational control.
- পর্ব ৩২ · Single-cycle datapath — একটি instruction-এর পুরো পথভিত্তিযে নকশার cycle-অপচয় থেকে এই পর্বের শুরু।
- পর্ব ১৭ · Finite state machine — Moore, Mealy আর state diagram থেকে circuitভিত্তিControl FSM বানানোর পদ্ধতি।
- পর্ব ০৩ · পারফরম্যান্স মাপা — CPU time, CPI, MIPS, Amdahl-এর সূত্র আর benchmarkহিসাবIron law আর instruction mix থেকে CPI.
- কম্পিউটার আর্কিটেকচার ও ডিজিটাল লজিক · কোর্স হোম৬২টি পর্বপুরো কোর্সের রোডম্যাপ — logic gate থেকে multicore.
- কম্পিউটার ফান্ডামেন্টালস · CPU কীভাবে কাজ করেসহজ পরিচয়Fetch–decode–execute-এর সহজ ছবি।
- অপারেটিং সিস্টেম · OS-এর জন্য hardwareপাশের ট্র্যাকCPU, memory আর interrupt — OS-এর চোখে।