এই পর্বে যা শিখবেন
- একটি ISA ঠিক কী কী নির্ধারণ করে, কী করে না, আর কেন একটি ISA চল্লিশ বছরেরও বেশি টিকে থাকে
- Stack, accumulator, register–memory আর load–store যন্ত্রে C = A + B — instruction আর মেমরি-ট্রাফিক গুনে তুলনা
- 0, 1, 2 আর 3-address instruction দিয়ে একটি expression লেখা আর instruction গোনা — পরীক্ষার ক্লাসিক প্রশ্ন
- RISC আর CISC-এর দর্শন, ১৯৮০-র বিতর্ক, আর আজকের x86, ARM ও RISC-V কোথায় দাঁড়িয়ে
- RISC-V-এর গঠন — base আর extension, register, মেমরি, privilege level — যা মডিউল ৫-এর বাকি পর্বের ভিত্তি
১ · ISA — একটি চুক্তি, আর চুক্তিতে কী কী লেখা থাকে
পর্ব ০১-এ (চারটি শব্দ) দেখেছেন, ISA (instruction set architecture) হলো hardware আর software-এর মাঝখানের চুক্তি: compiler আর programmer যা দেখেন, chip যা মেনে চলার প্রতিশ্রুতি দেয়। আর microarchitecture হলো সেই চুক্তি ভেতরে কীভাবে পালন করা হয়। একই পর্বের fetch–decode–execute অংশে দেখেছেন CPU কীভাবে একটির পর একটি instruction আনে আর চালায়। এই মডিউলে আমরা চুক্তিটির ভেতরে ঢুকব — প্রথমে সাধারণ ছবি (এই পর্ব), তারপর পর্ব ২৬ থেকে ৩১-এ RISC-V-এর instruction, encoding, addressing, শাখা, function call আর C থেকে assembly.
একটি রেস্তোরাঁর মেনু-কার্ডের কথা ভাবুন। মেনুতে লেখা থাকে কোন কোন খাবার পাওয়া যায়, প্রতিটিতে কী থাকে, কত বড় প্লেট, আর অর্ডার কীভাবে দিতে হয়। খদ্দের শুধু মেনু দেখে অর্ডার দেন; রান্নাঘরে কয়জন বাবুর্চি, কয়টি চুলা, কোন পদ আগে রান্না হয় — তা তাঁর জানার দরকার নেই। মালিক রান্নাঘর নতুন করে সাজাতে পারেন, দ্বিগুণ চুলা বসাতে পারেন; মেনু না বদলালে পুরোনো খদ্দেরের অর্ডার আগের মতোই চলে। ISA হলো মেনু, microarchitecture হলো রান্নাঘর, আর compiler হলো সেই নিয়মিত খদ্দের, যিনি মেনু মুখস্থ রাখেন।
চুক্তিটি কতটা খুঁটিনাটি, তা বোঝাতে নিচের টেবিলে একটি ISA manual-এ সাধারণত যা যা নির্দিষ্ট থাকে, আর RISC-V ও x86-64-এ তার উদাহরণ:
| ISA যা নির্ধারণ করে | মানে | RISC-V (RV32I) | x86-64 |
|---|---|---|---|
| Instruction আর তাদের অর্থ (semantics) | কোন কোন কাজ আছে, প্রতিটি ঠিক কোন বিট কোথায় লেখে, সীমানার ক্ষেত্রে কী হয় | add mod 232 যোগ করে, কোনো flag লেখে না | add যোগ করে আর CF, OF, ZF, SF ইত্যাদি flag লেখে |
| Register আর তাদের ভূমিকা | কয়টি, কত বিটের, কোনটির বিশেষ কাজ | ৩২টি (x0–x31), x0 সবসময় ০; PC আলাদা | ১৬টি সাধারণ register (RAX…R15), RSP stack-এর জন্য, RFLAGS |
| ডেটার ধরন আর মাপ | byte, halfword, word; signed/unsigned; floating point | ৮, ১৬, ৩২ বিট (RV64-এ ৬৪); F/D-তে IEEE 754 | ৮ থেকে ৬৪ বিট integer, x87-এর ৮০ বিট, SSE/AVX vector |
| মেমরি মডেল আর addressing | ঠিকানা কত বিটের, byte-ক্রম, alignment, ঠিকানা বানানোর নিয়ম, একাধিক core-এ মেমরির ক্রম | byte-addressed, little-endian; শুধু base + offset | little-endian; base + index×scale + displacement |
| Encoding | প্রতিটি instruction বিটে কীভাবে লেখা হয় | ৩২ বিট স্থির (C extension-এ ১৬ বিটও) | ১ থেকে ১৫ byte, পরিবর্তনশীল |
| Privilege level আর exception | কোন কাজ শুধু OS করতে পারে, ভুল হলে বা OS-কে ডাকলে কী হয় | U, S, M mode; ecall, mret | ring 0–3; syscall, interrupt, page fault |
টেবিলের প্রতিটি সারি এমন জিনিস, যা ভুল হলে প্রোগ্রামের ফল বদলে যায়। ধরুন একটি chip add-এর পর flag লেখে না, অথচ প্রোগ্রাম ধরে নিয়েছে লিখবে — তাহলে পরের শাখা ভুল দিকে যাবে। মেমরির byte-ক্রম ভুল ধরলে একই চারটি byte ভিন্ন সংখ্যা হয়ে যায় (পর্ব ০৬-এর endianness). তাই এগুলো চুক্তির অংশ।
চুক্তির বাইরে যা থাকে
উল্টো দিকে, নিচের জিনিসগুলো ISA manual-এ পাবেন না, কারণ এগুলো শুধু গতি বদলায়, ফল নয়:
- Pipeline কয় ধাপের (পর্ব ৩৫) — ৫ ধাপ, ১৪ ধাপ বা ২০ ধাপ; প্রোগ্রাম একই ফল দেয়।
- Cache কত বড়, কয় স্তরের (পর্ব ৪৪–৪৭) — ৩২ KiB না ৪৮ KiB L1, তা compiler-কে জানতে হয় না।
- একসাথে কয়টি instruction চলে, branch predictor কেমন, register renaming হয় কি না (পর্ব ৩৭, ৩৯, ৪০)।
- প্রতিটি instruction কত cycle নেয় — অর্থাৎ CPI. একটি ISA-তে গুণ এক chip-এ ৩ cycle, অন্যটিতে ৩০ cycle হতে পারে।
- Clock কত GHz আর chip কোন কারখানায় কোন প্রযুক্তিতে বানানো।
প্রশ্নটা করুন: “এটা বদলালে কি কোনো সঠিক প্রোগ্রামের ফল বদলাতে পারে?” হ্যাঁ হলে সেটি ISA-র বিষয়; না হলে (শুধু দ্রুত বা ধীর হয়) microarchitecture-এর। এই এক প্রশ্নেই পরীক্ষার “ISA না organization?” ধরনের প্রায় সব প্রশ্নের উত্তর মেলে।
একটি সূক্ষ্ম ব্যতিক্রম মনে রাখার মতো: সময় নিজেও তথ্য বহন করতে পারে। Cache-এর আকার ফল বদলায় না, কিন্তু কোন ডেটা cache-এ আছে তা সময় মেপে আন্দাজ করা যায় — Spectre-এর মতো নিরাপত্তা-সমস্যা এই ফাঁক থেকেই আসে (পর্ব ৪১)। চুক্তি বলে “ফল কী হবে”, “কত সময় লাগবে” সাধারণত বলে না — আর এই নীরবতার দাম মাঝে মাঝে দিতে হয়।
২ · কেন একটি ISA দশকের পর দশক বেঁচে থাকে — binary compatibility
আপনার ল্যাপটপে যে Windows বা Linux প্রোগ্রাম চলে, সেটি একটি machine code ফাইল — x86-64 instruction-এর বিটের সারি। এই ফাইল একবার compile করে লাখো কম্পিউটারে পাঠানো হয়, যার ভেতরে Intel বা AMD-এর ভিন্ন ভিন্ন প্রজন্মের core. সবগুলোতে একই ফাইল চলে, কারণ সবাই একই চুক্তি মানে। এই গুণের নাম backward compatibility (binary compatibility): নতুন chip-এ পুরোনো বাইনারি বদল ছাড়াই চলে।
এর সবচেয়ে বড় উদাহরণ x86. Intel ১৯৭৮ সালে ১৬-বিট 8086 বাজারে আনে। তারপর ৩২-বিট (80386, ১৯৮৫), তারপর AMD-এর নকশায় ৬৪-বিট x86-64 (প্রথম chip ২০০৩ সালে) — প্রতিবার পুরোনো মোড রেখে নতুন মোড যোগ হয়েছে। ফলে আজকের একটি x86-64 chip, অন্তত নীতিগতভাবে, চল্লিশ বছরের পুরোনো ১৬-বিট instruction-ও চালাতে পারে (৬৪-বিট OS-এর নিচে সরাসরি না হলেও)। তারও আগে IBM ১৯৬৪ সালে System/360 পরিবারে প্রথম দেখিয়েছিল যে এক ISA, বহু মডেল — এই কৌশলে software-এর বিনিয়োগ বাঁচে (পর্ব ০২-এর অংশ ৩); সেই বংশের ISA আজও IBM-এর mainframe-এ (IBM Z) চলে।
দেশের বিদ্যুতের প্লাগ আর সকেটের মাপের কথা ভাবুন। বিশ বছর আগে কেনা টেবিল-ফ্যান আজকের নতুন বাড়ির সকেটেও লাগে, কারণ মাপটা “চুক্তি”। সকেটের পেছনে তার, মিটার, পাওয়ার স্টেশন — সব বদলে গেছে, তবু প্লাগ একই। মাপ বদলালে প্রতিটি বাড়ির প্রতিটি যন্ত্র বাতিল হতো। ISA বদলানোর খরচও তেমনই — সব প্রোগ্রাম নতুন করে compile করা, আর যেগুলোর source নেই সেগুলো হারানো।
এর দুটি পরিণতি এই পর্বের বাকি অংশ বুঝতে জরুরি:
- ISA-র ভুল বা পুরোনো সিদ্ধান্ত সহজে মোছা যায় না। x86-এ আজও আছে ১৯৭০-এর দশকের অনেক instruction আর অদ্ভুত নিয়ম; বাদ দিলে পুরোনো প্রোগ্রাম ভাঙবে। তাই নতুন ISA নকশাকারেরা শুরুতেই খুব সাবধানে ভাবেন — যা যোগ করবেন, তা হয়তো চিরকাল রাখতে হবে।
- উদ্ভাবন বেশিরভাগ হয় microarchitecture-এ। চুক্তি স্থির রেখে ভেতরটা বারবার নতুন করে বানানো যায় — pipeline, out-of-order, বড় cache. এজন্যই ১৯৭৮ সালের ISA-র chip আজ হাজার হাজার গুণ দ্রুত।
উল্টো উদাহরণও আছে। Intel আর HP মিলে ২০০১ সালে Itanium (IA-64) নামে সম্পূর্ণ নতুন ৬৪-বিট ISA আনে, যা x86 চালাত ধীরে। বাজার সেটি খুব একটা গ্রহণ করেনি, আর AMD-এর x86-সামঞ্জস্যপূর্ণ ৬৪-বিট নকশা জিতে যায় — শেষে Intel-ও সেটি গ্রহণ করে। আবার Apple তিনবার ISA বদলেছে (68000 → PowerPC → x86 → ARM), প্রতিবার পুরোনো প্রোগ্রাম চালানোর জন্য অনুবাদক software (যেমন Rosetta) দিয়ে। অর্থাৎ ISA বদলানো সম্ভব, কিন্তু তার জন্য একটি বড় সেতু লাগে।
“Instruction set” শব্দটির চেয়ে “architecture” শব্দটি এই অর্থে প্রথম জোর দিয়ে ব্যবহার করেন System/360-এর নকশাকারেরা — Gene Amdahl, Gerrit Blaauw আর Fred Brooks. তাঁদের ১৯৬৪ সালের প্রবন্ধে architecture বলতে বোঝানো হয়েছিল “programmer যেভাবে যন্ত্রটিকে দেখেন” — ভেতরের নির্মাণ নয়। ষাট বছর পরেও সংজ্ঞাটি প্রায় অপরিবর্তিত।
৩ · Operand কোথায় থাকে — stack, accumulator, register–memory আর load–store
একটি যোগের instruction-কে দুটি operand (যে মানগুলো নিয়ে কাজ হবে) পেতে হয় আর ফল কোথাও রাখতে হয়। ISA-গুলোর সবচেয়ে মৌলিক শ্রেণিবিভাগ হলো: operand-গুলো CPU-র ভেতরে কোথায় থাকে, আর মেমরির operand কোন instruction সরাসরি ছুঁতে পারে। চারটি প্রধান ধরন:
📚 Stack
Stack architecture-এ operand থাকে একটি stack-এর মাথায়। add নিজে কোনো ঠিকানা বলে না — মাথার দুটি মান তুলে যোগফল আবার মাথায় রাখে। শুধু push আর pop মেমরি ছোঁয়।
🧮 Accumulator
Accumulator architecture-এ একটিমাত্র বিশেষ register — accumulator — সবসময় একটি operand আর ফলের জায়গা। অন্য operand আসে মেমরি থেকে: add B মানে ACC = ACC + মেমরি[B].
🔀 Register–memory
Register–memory architecture-এ অনেকগুলো register থাকে, আর হিসাবের instruction-এর একটি operand সরাসরি মেমরিতে থাকতে পারে — একে বলে memory operand. যেমন x86-এর add eax, [rbx].
📦 Load–store
Load–store architecture-এ (register–register-ও বলে) হিসাব শুধু register-এ। মেমরিতে যাওয়ার একমাত্র পথ load আর store. এটিই RISC-এর সংজ্ঞাসূচক বৈশিষ্ট্য।
আরও একটি ধরন ইতিহাসে আছে: memory–memory architecture, যেখানে তিনটি operand-ই মেমরিতে থাকতে পারে — যেমন DEC-এর VAX-এ ADDL3 A, B, C (VAX-এ গন্তব্য শেষে) এক instruction-এ C = A + B. এখন এটি প্রায় বিলুপ্ত, তবে পরের অংশের “3-address” আলোচনায় এর ধারণা কাজে লাগবে।
ক্লাসিক তুলনা: C = A + B চার স্টাইলে
পরীক্ষায় সবচেয়ে বেশি আসে এই প্রশ্নটি: A, B, C তিনটি মেমরির চলক; C = A + B লিখুন প্রতিটি ধরনে। Hennessy–Patterson-এর বইয়ে যে রূপে দেখানো হয়, প্রায় সেই রূপে (register-এর নাম আমাদের; register–memory-র জন্য x86-ধাঁচের ২-operand রূপ):
# উদাহরণ — C = A + B (A, B, C মেমরিতে)
# Stack # Accumulator # Register-memory (x86-ধাঁচ) # Load-store (RISC-V-ধাঁচ)
push A load A mov r1, A lw r1, A
push B add B add r1, B # B মেমরিতে lw r2, B
add store C mov C, r1 add r3, r1, r2
pop C sw r3, C
(Load–store স্টাইলে আসল lw instruction সরাসরি নাম দিয়ে মেমরি পড়ে না — A-এর ঠিকানা আগে একটি register-এ আনতে হয়, তারপর lw t0, 0(a0); assembler-এ lw t0, A লিখলে সেটি একটি pseudo-instruction, যা ভেতরে এমন দুটি instruction হয়ে যায়। এখানে তুলনা সহজ রাখতে নামটিই লেখা হলো; আসল রূপ পর্ব ২৬ আর ২৮-এ।) এবার চারটিকে পাশাপাশি এক এক ধাপ চালিয়ে দেখুন — A = 7, B = 5:
| ধাপ | |
| Stack | |
| Accumulator | |
| Register–memory | |
| Load–store | |
| মেমরি-ছোঁয়া (S · Acc · RM · LS) |
- প্রথম instruction. চারটি যন্ত্রই A পড়ে — stack-এর মাথায়, accumulator-এ, বা r1-এ। প্রত্যেকে এ পর্যন্ত মেমরি ছুঁয়েছে একবার।
- দ্বিতীয় instruction. Stack আর load–store B-কে CPU-তে আনে (stack [7, 5], r2 = 5) — এখনো যোগ হয়নি। Accumulator আর register–memory যন্ত্র মেমরির B সরাসরি যোগ করে: ACC = 12, r1 = 12. এখানেই memory operand-এর সুবিধা — load আর যোগ এক instruction-এ।
- তৃতীয় instruction. Stack-এর
addমেমরি ছোঁয় না, মাথার দুটি মান যোগ করে। Load–store-এরadd-ও শুধু register-এ। Accumulator আর register–memory যন্ত্র ফল C-তে লিখে শেষ — মাত্র ৩টি instruction. - চতুর্থ instruction. Stack
pop Cআর load–storesw r3, Cদিয়ে শেষ করে। চারটিতেই C = 12, আর চারটিতেই ঠিক তিনবার মেমরি ছোঁয়া হয়েছে। - সারাংশ। একবারের হিসাবে instruction ৩ বা ৪, মেমরি-ট্রাফিক সমান। তাহলে load–store-এর লাভ কোথায়? উত্তর আসে যখন মান আবার ব্যবহার হয় — তখন register-এ রাখা মানের জন্য মেমরিতে যেতে হয় না। নিচের simulator সেটিই দেখায়।
নিচের simulator চারটি ছোট যন্ত্র বানায় — প্রতিটির নিজস্ব instruction টেবিল — একই মেমরিতে প্রোগ্রাম চালায়, আর গোনে কতবার মেমরি পড়া আর লেখা হলো। দ্বিতীয় অংশে C = A + B-এর পর D = C + A — যেখানে A আর C আবার লাগে। Run চাপুন:
// One tiny memory, four ISA styles: who touches memory, and how often?
function machine(name, prog) {
const M = { A: 7, B: 5, C: 0, D: 0 }, R = {}, S = [];
let acc = 0, rd = 0, wr = 0;
const ld = a => { rd++; return M[a]; }, st = (a, v) => { wr++; M[a] = v; };
const ops = {
stack: { push: a => S.push(ld(a)), pop: a => st(a, S.pop()),
add: () => S.push(S.pop() + S.pop()) }, // operands: top of stack
accumulator: { load: a => { acc = ld(a); }, add: a => { acc += ld(a); },
store: a => st(a, acc) }, // one implicit register
"reg-memory": { mov: (d, s) => (d in M ? st(d, R[s]) : (R[d] = ld(s))),
add: (d, s) => { R[d] += (s in M ? ld(s) : R[s]); } }, // src may be memory
"load-store": { lw: (r, a) => { R[r] = ld(a); }, sw: (r, a) => st(a, R[r]),
add: (d, x, y) => { R[d] = R[x] + R[y]; } }, // registers only
}[name];
for (const line of prog) { const [op, ...args] = line.split(/[ ,]+/); ops[op](...args); }
return `${name.padEnd(12)} ${String(prog.length).padStart(5)} ${String(rd).padStart(6)}` +
`${String(wr).padStart(7)} C=${M.C} D=${M.D}`;
}
const progs = { // C = A + B
stack: ["push A", "push B", "add", "pop C"],
accumulator: ["load A", "add B", "store C"],
"reg-memory": ["mov r1, A", "add r1, B", "mov C, r1"],
"load-store": ["lw r1, A", "lw r2, B", "add r3, r1, r2", "sw r3, C"],
};
const more = { // then D = C + A, reusing whatever is already inside the CPU
stack: ["push C", "push A", "add", "pop D"], accumulator: ["add A", "store D"],
"reg-memory": ["add r1, A", "mov D, r1"], "load-store": ["add r4, r3, r1", "sw r4, D"],
};
console.log("C = A + B (A = 7, B = 5)\nstyle instr reads writes result");
for (const n in progs) console.log(machine(n, progs[n]));
console.log("\nC = A + B; D = C + A\nstyle instr reads writes result");
for (const n in progs) console.log(machine(n, progs[n].concat(more[n])));আউটপুট (নির্ধারিত — প্রতিবার একই):
C = A + B (A = 7, B = 5)
style instr reads writes result
stack 4 2 1 C=12 D=0
accumulator 3 2 1 C=12 D=0
reg-memory 3 2 1 C=12 D=0
load-store 4 2 1 C=12 D=0
C = A + B; D = C + A
style instr reads writes result
stack 8 4 2 C=12 D=19
accumulator 5 3 2 C=12 D=19
reg-memory 5 3 2 C=12 D=19
load-store 6 2 2 C=12 D=19
প্রথম টেবিল stepper-এর সাথে হুবহু মেলে: ৪, ৩, ৩, ৪টি instruction, আর সবার ২টি পড়া ও ১টি লেখা। দ্বিতীয় টেবিলে পার্থক্য স্পষ্ট। Stack যন্ত্রকে C আর A আবার মেমরি থেকে আনতে হয় (৪টি পড়া) — stack-এর মাথার মান pop-এর পর হারিয়ে যায়। Accumulator-এ C তখনও ACC-তে আছে, কিন্তু A আবার পড়তে হয় (৩টি)। Register–memory-ও A আবার মেমরি থেকে যোগ করে (৩টি)। Load–store যন্ত্রে A আগে থেকেই r1-এ, C আছে r3-এ — তাই মাত্র ২টি পড়া, যদিও instruction ৬টি। মেমরি register-এর চেয়ে বহু গুণ ধীর (পর্ব ৪৪), তাই বাস্তবের বড় প্রোগ্রামে মানগুলো register-এ ধরে রাখার এই ক্ষমতাই load–store নকশার আসল শক্তি — আর সেটি কাজে লাগাতে দরকার অনেকগুলো register ও ভালো compiler.
তুলনাটি এক টেবিলে:
| স্টাইল | সুবিধা | অসুবিধা |
|---|---|---|
| Stack | Instruction খুব ছোট (ঠিকানা নেই), compiler বানানো সহজ, ভালো code density | মাথার মান ছাড়া কিছু ধরা যায় না; একই মান বারবার আনতে হয়; pipeline-এ সব instruction একটি stack-মাথার ওপর নির্ভর করে |
| Accumulator | Hardware সবচেয়ে সরল — একটি register | প্রতিটি মধ্যবর্তী ফল মেমরিতে রাখতে হয়; মেমরি-ট্রাফিক বেশি |
| Register–memory | Load আলাদা লাগে না, কম instruction, ছোট কোড | Instruction-এর দৈর্ঘ্য আর সময় অসমান (মেমরি operand থাকলে বেশি), decode জটিল |
| Load–store | সব instruction সরল আর সমান মাপের, pipeline সহজ, মান register-এ ধরে রাখা যায় | Instruction সংখ্যা বেশি, কোড কিছুটা বড়; অনেক register লাগে |
Stack যন্ত্র মরেনি — software-এ বেঁচে আছে। Java-র JVM, .NET-এর CIL, WebAssembly আর CPython-এর bytecode সবই stack-ভিত্তিক “যন্ত্রের” জন্য লেখা — কারণ compiler বানানো সহজ আর কোড ছোট। তারপর JIT compiler সেটি আসল CPU-র register-ভিত্তিক instruction-এ অনুবাদ করে। HP-র বিখ্যাত বৈজ্ঞানিক ক্যালকুলেটরগুলো (১৯৭২ সালের HP-35 থেকে) RPN (Reverse Polish Notation) ব্যবহার করত: 3 ENTER 4 + — অর্থাৎ ঠিক push 3, push 4, add.
৪ · কয়টি address — 0, 1, 2 আর 3-address instruction
আগের অংশের শ্রেণিবিভাগের আরেকটি চেহারা হলো: একটি হিসাবের instruction-এ কয়টি operand-এর ঠিকানা স্পষ্টভাবে লেখা থাকে। Hamacher, Stallings প্রভৃতি পাঠ্যবইয়ে আর চাকরির লিখিত পরীক্ষায় এই প্রশ্ন খুব প্রচলিত। চারটি রূপ (দেখানোর জন্য X = A op B):
| ধরন | রূপ | অর্থ | কোথায় দেখা যায় |
|---|---|---|---|
| 3-address | ADD X, A, B | X ← A + B | VAX (মেমরিতে); RISC-V, ARM (register-এ) |
| 2-address | ADD X, A | X ← X + A (একটি operand-ই গন্তব্য) | x86, PDP-11 |
| 1-address | ADD A | ACC ← ACC + A | accumulator যন্ত্র |
| 0-address | ADD | stack-এর মাথার দুটি → যোগফল মাথায় | stack যন্ত্র (PUSH/POP-এ একটি ঠিকানা থাকে) |
Worked example: X = (A + B) × (C − D)
নিয়ম (পাঠ্যবইয়ের প্রচলিত ধারা): সব নামযুক্ত operand মেমরিতে; মধ্যবর্তী ফলের জন্য অস্থায়ী মেমরি-ঘর T1, T2 ব্যবহার করা যাবে; গন্তব্যটিকেও অস্থায়ী হিসেবে ব্যবহার করা যায়।
# উদাহরণ — X = (A + B) × (C − D)
# 3-address (৩টি) # 2-address (৫টি) # 1-address (৭টি) # 0-address (৮টি)
ADD T1, A, B MOV X, A LOAD C PUSH A
SUB T2, C, D ADD X, B # X = A+B SUB D PUSH B
MUL X, T1, T2 MOV T1, C STORE T1 # T1 = C-D ADD
SUB T1, D # T1 = C-D LOAD A PUSH C
MUL X, T1 ADD B PUSH D
MUL T1 SUB
STORE X MUL
POP X
লক্ষ করুন 1-address কোড ডান দিক (C − D) আগে হিসাব করে T1-এ রাখে। কারণ accumulator একটিই; বাঁ দিক আগে করলে A + B-কে রাখতে হতো, তারপর C − D, তারপর আবার T1 আনতে — একটি বাড়তি instruction. গুণ বিনিময়যোগ্য, তাই (C − D) × (A + B) একই ফল দেয়। 0-address কোডটি আসলে expression-টির postfix রূপ: A B + C D − ×. Stack-এ কী ঘটে, ধাপে ধাপে দেখুন (A = 7, B = 5, C = 9, D = 4):
1PUSH A2PUSH B3ADD4PUSH C5PUSH D6SUB7MUL8POP X
| Stack | |
| Stack-এর গভীরতা | |
| মেমরি পড়া / লেখা | |
| X |
- PUSH A. মেমরির A (৭) stack-এ ওঠে। একটি মেমরি-পড়া।
- PUSH B. B (৫) তার ওপরে। এখন stack-এ [7, 5], মাথায় 5.
- ADD. 0-address instruction — কোনো ঠিকানা নেই। মাথার দুটি তুলে 7 + 5 = 12 আবার মাথায়। মেমরি ছোঁয়া হলো না।
- PUSH C. C (৯) ওঠে 12-এর ওপরে। 12 নিচে অপেক্ষা করছে — accumulator যন্ত্রের মতো একে মেমরিতে রাখতে হলো না।
- PUSH D. D (৪) ওঠে। Stack-এর গভীরতা এখন ৩ — এই expression-এর জন্য সর্বোচ্চ। মেমরি থেকে মোট চারটি পড়া শেষ।
- SUB. ক্রম গুরুত্বপূর্ণ: নিচেরটি থেকে মাথারটি বিয়োগ, 9 − 4 = 5. উল্টো করলে −5 আসত। Stack এখন [12, 5].
- MUL. 12 × 5 = 60. Stack-এ একটিই মান।
- POP X. 60 মেমরির X-এ লেখা হলো। মোট ৮টি instruction, ৪টি পড়া আর ১টি লেখা — কোনো অস্থায়ী মেমরি-ঘর লাগেনি।
এবার চার ধরনের কোড হাতে না লিখে একটি ছোট program দিয়ে বানানো যাক। নিচের code generator একটি expression tree থেকে চার ধরনের কোড তৈরি করে, প্রতিটি একটি ছোট interpreter-এ চালায় (সব নামযুক্ত operand আর T1, T2 মেমরিতে), গোনে কতবার মেমরি পড়া ও লেখা হলো, আর ফলটি সরাসরি হিসাবের সাথে মেলায়। দ্বিতীয় expression: W = A × (B + C) − D.
// Generate 3-, 2-, 1- and 0-address code from an expression tree, run it, count memory traffic
const NAME = { "+": "ADD", "-": "SUB", "*": "MUL", "/": "DIV" }, COMM = { "+": 1, "*": 1 };
const F = { ADD: (a, b) => a + b, SUB: (a, b) => a - b, MUL: (a, b) => a * b, DIV: (a, b) => a / b };
const leaf = n => typeof n === "string", swap = ([op, l, r]) => leaf(l) && !leaf(r) && COMM[op] ? [op, r, l] : [op, l, r];
function compile(e, dst) {
const c = { 3: [], 2: [], 1: [], 0: [] }; let t = 0; const tmp = () => "T" + ++t;
const g3 = (n, d) => { if (leaf(n)) return n; // d = a op b
const a = g3(n[1]), b = g3(n[2]), r = d || tmp(); c[3].push([NAME[n[0]], r, a, b]); return r; };
const g2 = (n, d) => { const [op, l, r] = swap(n); // d = d op s
if (leaf(l)) c[2].push(["MOV", d, l]); else g2(l, d);
if (leaf(r)) c[2].push([NAME[op], d, r]); else { const s = tmp(); g2(r, s); c[2].push([NAME[op], d, s]); } };
const g1 = n => { if (leaf(n)) return c[1].push(["LOAD", n]); const [op, l, r] = swap(n); // ACC = ACC op m
if (leaf(r)) { g1(l); c[1].push([NAME[op], r]); return; }
g1(r); const s = tmp(); c[1].push(["STORE", s]); g1(l); c[1].push([NAME[op], s]); };
const g0 = n => leaf(n) ? c[0].push(["PUSH", n]) : (g0(n[1]), g0(n[2]), c[0].push([NAME[n[0]]])); // postfix
g3(e, dst); t = 0; g2(e, dst); t = 0; g1(e); c[1].push(["STORE", dst]); g0(e); c[0].push(["POP", dst]);
return c; }
function run(k, code, mem) { // every named operand (incl. temps T1, T2) lives in memory
const M = { ...mem }, S = []; let acc = 0, rd = 0, wr = 0;
const R = a => (rd++, M[a]), W = (a, v) => (wr++, M[a] = v);
for (const [o, x, y, z] of code) {
if (k === 3) W(x, F[o](R(y), R(z)));
else if (k === 2) W(x, o === "MOV" ? R(y) : F[o](R(x), R(y)));
else if (k === 1) o === "LOAD" ? (acc = R(x)) : o === "STORE" ? W(x, acc) : (acc = F[o](acc, R(x)));
else o === "PUSH" ? S.push(R(x)) : o === "POP" ? W(x, S.pop()) : S.push(F[o](...S.splice(-2)));
}
return { rd, wr, v: M }; }
const ev = (n, m) => leaf(n) ? m[n] : F[NAME[n[0]]](ev(n[1], m), ev(n[2], m));
for (const [dst, e, mem] of [["X", ["*", ["+", "A", "B"], ["-", "C", "D"]], { A: 7, B: 5, C: 9, D: 4 }],
["W", ["-", ["*", "A", ["+", "B", "C"]], "D"], { A: 6, B: 2, C: 3, D: 10 }]]) {
const c = compile(e, dst), want = ev(e, mem);
for (const k of [3, 2, 1, 0]) {
const r = run(k, c[k], mem), ok = r.v[dst] === want ? "ok" : "WRONG", txt = c[k].map(i => i[0] + (i.length > 1 ? " " + i.slice(1).join(",") : ""));
console.log(`${k}-address: ${c[k].length} instr, ${r.rd} reads, ${r.wr} writes, ${dst}=${r.v[dst]} ${ok}\n ` + txt.join("; "));
} }আউটপুট:
3-address: 3 instr, 6 reads, 3 writes, X=60 ok
ADD T1,A,B; SUB T2,C,D; MUL X,T1,T2
2-address: 5 instr, 8 reads, 5 writes, X=60 ok
MOV X,A; ADD X,B; MOV T1,C; SUB T1,D; MUL X,T1
1-address: 7 instr, 5 reads, 2 writes, X=60 ok
LOAD C; SUB D; STORE T1; LOAD A; ADD B; MUL T1; STORE X
0-address: 8 instr, 4 reads, 1 writes, X=60 ok
PUSH A; PUSH B; ADD; PUSH C; PUSH D; SUB; MUL; POP X
3-address: 3 instr, 6 reads, 3 writes, W=20 ok
ADD T1,B,C; MUL T2,A,T1; SUB W,T2,D
2-address: 4 instr, 7 reads, 4 writes, W=20 ok
MOV W,B; ADD W,C; MUL W,A; SUB W,D
1-address: 5 instr, 4 reads, 1 writes, W=20 ok
LOAD B; ADD C; MUL A; SUB D; STORE W
0-address: 8 instr, 4 reads, 1 writes, W=20 ok
PUSH A; PUSH B; PUSH C; ADD; MUL; PUSH D; SUB; POP W
X-এর জন্য generator হুবহু হাতের কোডটি বানিয়েছে: ৩, ৫, ৭, ৮টি instruction, আর চারটিতেই X = 60 = (7 + 5) × (9 − 4). এবার মেমরি-ট্রাফিক দেখুন — ছবিটা উল্টো। 3-address-এ instruction সবচেয়ে কম, কিন্তু প্রতিটি instruction তিনবার মেমরি ছোঁয় (৬টি পড়া, ৩টি লেখা, কারণ T1, T2 মেমরিতে); 2-address সবচেয়ে বেশি ট্রাফিক (৮ + ৫), কারণ গন্তব্য একই সাথে পড়া ও লেখা হয়; 0-address-এ instruction সবচেয়ে বেশি, কিন্তু ট্রাফিক সবচেয়ে কম (৪ + ১) — মধ্যবর্তী ফল stack-এ থাকে। W-এর ক্ষেত্রে 1-address-এ কোনো অস্থায়ী ঘরই লাগেনি (৫টি instruction), কারণ expression-টি বাঁ থেকে একটানা accumulator-এ জমানো যায়: B + C, × A, − D. অর্থাৎ instruction-এর সংখ্যা expression-এর আকারের ওপরও নির্ভর করে।
Instruction-এর সংখ্যা কম মানেই ভালো নয়
একটি 3-address মেমরি instruction-এ তিনটি পূর্ণ মেমরি-ঠিকানা লিখতে হয়। ৩২-বিট ঠিকানা হলে শুধু ঠিকানাতেই ৯৬ বিট, সাথে opcode — প্রায় ১৩ byte-এর instruction. 0-address-এর ADD হয়তো ১ byte. তাই পরীক্ষায় প্রায়ই সাথে প্রশ্ন থাকে “মোট কোডের মাপ কত byte”. আধুনিক RISC এই দ্বন্দ্বের মাঝামাঝি সমাধান নিয়েছে: 3-address, কিন্তু register-এ। ৩২টি register-এর নাম দিতে লাগে মাত্র ৫ বিট, তাই তিনটি operand-ই ১৫ বিটে ধরে, আর পুরো instruction ৩২ বিটে। RISC-V-এ X-এর কোড:
# উদাহরণ — RISC-V (load-store, register-এ 3-address), A..D-এর ঠিকানা a0-ভিত্তিক ধরে
lw t0, 0(a0) # A
lw t1, 4(a0) # B
lw t2, 8(a0) # C
lw t3, 12(a0) # D
add t0, t0, t1 # A + B
sub t2, t2, t3 # C - D
mul t0, t0, t2 # (A+B)(C-D) (M extension)
sw t0, 16(a0) # X
# ৮টি instruction × ৪ byte = ৩২ byte; ৪টি পড়া, ১টি লেখা
Instruction-এর সংখ্যা 0-address-এর সমান, মেমরি-ট্রাফিকও সমান (৪ + ১), কিন্তু সব মধ্যবর্তী ফল register-এ — আর প্রতিটি instruction একই মাপের, একই ধাঁচের। মধ্যবর্তী অস্থায়ী মানের জন্য register ব্যবহার করলে আগের কোডগুলোও বদলায়: যেমন 3-address-এ ADD R1, A, B; SUB R2, C, D; MUL X, R1, R2 — ৩টি instruction, কিন্তু মাত্র ৪টি পড়া আর ১টি লেখা। পরীক্ষায় তাই প্রশ্নে স্পষ্ট থাকা উচিত যে অস্থায়ী মান মেমরিতে না register-এ; না থাকলে উত্তরে নিজের ধরে নেওয়া নিয়মটি লিখে দিন।
৫ · Instruction-এর প্রকারভেদ — পাঁচটি দল
যেকোনো ISA-র instruction তালিকা খুললে দেখবেন, শত শত instruction আসলে কয়েকটি দলে পড়ে। দলগুলো জানলে নতুন ISA শেখা অনেক সহজ — শুধু জানতে হয় প্রতিটি দলে এই ISA কী নাম দিয়েছে।
| দল | কাজ | RISC-V | x86-64 |
|---|---|---|---|
| Data transfer | মেমরি ↔ register, register ↔ register, ধ্রুবক আনা | lw, sw, lb, lui | mov, push, pop, lea |
| Arithmetic ও logic | যোগ, বিয়োগ, AND, OR, XOR, shift, তুলনা | add, sub, and, sll, slt; M-এ mul, div | add, sub, and, shl, imul, cmp |
| Control flow | শাখা, লাফ, function call আর ফেরা | beq, bne, blt, jal, jalr | jmp, je/jl…, call, ret |
| System | OS-কে ডাকা, debugger, বিশেষ register, মেমরির ক্রম | ecall, ebreak, fence, csrrw (Zicsr), mret | syscall, int3, cpuid, mfence, hlt |
| Floating point (ও vector) | ভগ্নাংশের হিসাব, অনেক ডেটা একসাথে | flw, fadd.s, fmul.d (F/D); V-তে vadd.vv | SSE/AVX: addss, mulsd, vaddps; পুরোনো x87: fadd |
বাস্তব প্রোগ্রামে কোন দল কত ঘন ঘন চলে? মাপজোখে বারবার দেখা গেছে, সাধারণ integer প্রোগ্রামে load/store আর যোগ-তুলনা-শাখা মিলে বেশিরভাগ — আর জটিল instruction খুব কম। (সঠিক শতাংশ benchmark, compiler আর ISA-ভেদে অনেক বদলায়, তাই কোনো একটি সংখ্যা মুখস্থ না করাই ভালো।) এই পর্যবেক্ষণই RISC আন্দোলনের সূচনা করেছিল — পরের অংশে।
Data transfer দলের ভেতরে আরেকটি প্রশ্ন আছে: মেমরির ঠিকানা কীভাবে বলা হবে — ধ্রুবক হিসেবে instruction-এর ভেতরেই (immediate), register-এ, register + offset, নাকি PC থেকে দূরত্ব হিসেবে। এই নিয়মগুলোকে বলে addressing mode; RISC-V-এর চারটি সরল mode আর x86-এর জটিলগুলোর তুলনা পর্ব ২৮-এ। শাখা আর লুপের assembly পর্ব ২৯-এ, function call পর্ব ৩০-এ।
৬ · RISC বনাম CISC — দুই দর্শন, এক বিতর্ক, আর আজকের মিলন
পর্ব ০২-এর অংশ ৫-এ RISC আন্দোলনের ইতিহাস দেখেছেন — IBM 801, Berkeley RISC, Stanford MIPS. এখানে প্রশ্নটা নকশার: দুই শিবির আসলে কোন কোন সিদ্ধান্তে আলাদা, আর কেন।
| বিষয় | RISC দর্শন | CISC দর্শন |
|---|---|---|
| Instruction-এর দৈর্ঘ্য | স্থির (যেমন সবসময় ৩২ বিট) | পরিবর্তনশীল (x86-এ ১–১৫ byte) |
| মেমরি ছোঁয়া | শুধু load আর store | প্রায় যেকোনো instruction-এ memory operand |
| Register | অনেক (৩২টি সাধারণ) | কম (8086-এ ৮টি, x86-64-এ ১৬টি) |
| Addressing mode | কম আর সরল | অনেক আর জটিল |
| এক instruction-এ কাজ | ছোট, এক ধাপের | বড় — যেমন পুরো string কপি (rep movsb) |
| Control | সরাসরি hardwired (পর্ব ৩৩) | ঐতিহাসিকভাবে microcode (পর্ব ৩৪) |
| কাজের ভার | Compiler-এর ওপর বেশি | Hardware-এর ওপর বেশি |
| লক্ষ্য | কম CPI, দ্রুত clock, সহজ pipelining | কম instruction count, ছোট কোড |
CISC-কে “ভুল নকশা” ভাবা ঠিক নয়। ১৯৬০–৭০-এর দশকে মেমরি ছিল খুব দামি আর ধীর, compiler ছিল দুর্বল, আর অনেক প্রোগ্রাম লেখা হতো সরাসরি assembly-তে। তখন একটি instruction-এ বেশি কাজ মানে ছোট প্রোগ্রাম, কম মেমরি-ট্রাফিক, আর programmer-এর সুবিধা। Microcode দিয়ে জটিল instruction বানানোও সস্তা ছিল — control unit-এর ভেতরে একটি ছোট ROM-এর program. সমস্যা দেখা দিল যখন পরিস্থিতি বদলাল: compiler ভালো হলো, cache এলো, আর একটি chip-এ পুরো CPU বসানোর যুগে প্রতিটি transistor মূল্যবান হয়ে উঠল।
১৯৮০-র বিতর্ক
১৯৮০ সালে David Patterson আর David Ditzel একটি ছোট প্রবন্ধ প্রকাশ করেন — “The Case for the Reduced Instruction Set Computer” (ACM-এর Computer Architecture News-এ)। তাঁদের যুক্তির সারকথা, যতটা সাধারণভাবে উদ্ধৃত হয়: ISA ক্রমাগত জটিল হচ্ছে, অথচ compiler সেই জটিল instruction-গুলো কমই ব্যবহার করে; জটিলতা নকশার সময় বাড়ায়, ভুলের ঝুঁকি বাড়ায়, আর সাধারণ instruction-গুলোকেও ধীর করে; একটি chip-এ সরল ISA বানালে বাঁচানো জায়গা cache বা register-এ খরচ করা যায়। প্রায় একই সময়ে DEC-এর VAX নকশাকারদের পক্ষ থেকে (Douglas Clark ও William Strecker) পাল্টা মন্তব্য প্রকাশিত হয় — জটিল instruction-এর পক্ষে, আর মাপজোখ ছাড়া সিদ্ধান্ত নেওয়ার বিপক্ষে। এর পরের এক দশক মাপজোখ, prototype chip আর বাণিজ্যিক RISC processor (ARM, MIPS, SPARC, পরে PowerPC) দিয়ে বিতর্ক এগোয়।
বিতর্কটি iron law দিয়ে পরিষ্কার বোঝা যায় (পর্ব ০৩-এর অংশ ৩): CPU time = IC × CPI × T. CISC কমাতে চায় IC, RISC কমাতে চায় CPI আর T — বেশি instruction-এর দাম দিয়ে। কোনটি জিতবে, তা নির্ভর করে তিনটির গুণফলের ওপর, শুধু একটির ওপর নয়। অংশ ৯-এ একটি সংখ্যার উদাহরণ।
একটি CISC instruction বনাম তার RISC সমতুল্য
মেমরির একটি সংখ্যার সাথে register-এর মান যোগ করে আবার মেমরিতেই রাখা — C-তে *p += k;. x86-এ এটি একটি instruction, RISC-V-এ তিনটি। নিচে ধাপে ধাপে (মেমরির ঠিকানা 0x1000-এ আছে 40, k = 2):
rbx = 0x1000 eax = 2 mem[0x1000] = 40 encoding: 01 03 (2 bytes: opcode 01, ModRM 03) one instruction: read memory, add, write memory mem[0x1000] = 42
uop 1: load tmp <- [rbx] ; 40 uop 2: add tmp <- tmp + eax ; 42 uop 3: store [rbx] <- tmp ; (often split: address + data) exact count and fusion vary by core
a0 = 0x1000 a1 = 2
lw a2, 0(a0) a2 = 40 (memory read)
add a2, a2, a1 a2 = 40 + 2 = 42 (registers only)
sw a2, 0(a0) mem[0x1000] = 42 (memory write)
x86 : 1 instruction, 2 bytes
RV32I : 3 instructions, 12 bytes
RV32IC: 3 instructions, 6 bytes (c.lw, c.add, c.sw)
both : 1 memory read + 1 memory write
- এক CISC instruction. x86-এর
add dword ptr [rbx], eaxমেমরি পড়ে, যোগ করে, আবার মেমরিতে লেখে — এক instruction-এ। এর encoding মাত্র ২ byte: opcode01(ADD r/m32, r32) আর ModRM byte03, যা বলে “মেমরির ঠিকানা rbx-এ, অন্য operand eax”. 40 + 2 = 42. - ভেতরে আসলে কয়েকটি কাজ। আধুনিক x86 core এই instruction-কে decode করে ছোট ছোট micro-op-এ ভাঙে — মোটামুটি একটি load, একটি add, একটি store (store নিজেও প্রায়ই ঠিকানা আর ডেটা — দুই ভাগে)। কয়টি micro-op আর কোনগুলো জোড়া লাগে (micro-fusion), তা core-ভেদে আলাদা। অর্থাৎ বাইরে CISC, ভেতরে RISC-এর মতো।
- RISC-V: load. Load–store ISA-তে হিসাবের instruction মেমরি ছুঁতে পারে না, তাই আগে
lwদিয়ে 0x1000-এর মান a2-তে আনা হলো: a2 = 40. - RISC-V: শুধু register-এ যোগ।
add a2, a2, a1: 40 + 2 = 42. এই instruction-এর সময় নির্দিষ্ট — কোনো মেমরি-অপেক্ষা নেই, যা pipeline-কে সরল রাখে। - RISC-V: store আর হিসাব।
sw42-কে মেমরিতে লেখে। দুই ISA-তেই কাজ সমান — একটি পড়া, একটি লেখা। পার্থক্য instruction সংখ্যা আর কোডের মাপে: x86-এ ১টি/২ byte, RV32I-এ ৩টি/১২ byte, আর C extension থাকলে ৩টি/৬ byte (এখানে a0, a1, a2 register বেছে নেওয়ায় তিনটিই ১৬-বিট রূপে লেখা যায়)।
আজকের মিলন — দুই দিক থেকেই
১৯৯৫ সালে Intel-এর Pentium Pro (P6) থেকে প্রধান x86 core-গুলো ঠিক stepper-এর ধাপ ২-এর মতো কাজ করে: সামনে একটি জটিল decoder x86 instruction-কে RISC-ধাঁচের micro-op-এ ভাঙে, আর পেছনের out-of-order “কারখানা” (পর্ব ৪০) সেই micro-op চালায়। জটিল বা বিরল instruction এখনও microcode ROM থেকে আসে। অর্থাৎ RISC-এর মূল কৌশল — সরল, সমান মাপের ছোট কাজ pipeline-এ চালানো — x86-ও গ্রহণ করেছে, শুধু চুক্তি না বদলে।
উল্টো দিকে RISC শিবিরও “শুদ্ধ” থাকেনি। ARM-এর ৬৪-বিট ISA AArch64-এ আছে একসাথে দুটি register load/store করা (ldp/stp), জটিল addressing mode, মেমরিতে সরাসরি atomic যোগ (ldadd, ARMv8.1 থেকে), AES আর SHA-র cryptography instruction, এমনকি JavaScript-এর সংখ্যা রূপান্তরের জন্য বিশেষ একটি instruction (fjcvtzs, ARMv8.3). RISC-V-তেও extension-এর মাধ্যমে বিট-কৌশল (B), cryptography আর vector instruction যোগ হয়েছে। আজ “RISC” মানে কম instruction নয় — মানে মূলত load–store, স্থির দৈর্ঘ্য আর সরল encoding.
সতর্কতা — “কোনটি ভালো” প্রশ্নের এক কথায় উত্তর নেই। আজকের দ্রুততম chip-গুলোতে performance-এর পার্থক্য বেশিরভাগ আসে microarchitecture, cache, তৈরির প্রযুক্তি আর power-এর লক্ষ্য থেকে; ISA-র প্রভাব আছে, কিন্তু সাধারণত ছোট। ISA-র পার্থক্য সবচেয়ে বেশি টের পাওয়া যায় decoder-এর জটিলতায় (x86-এ পরিবর্তনশীল দৈর্ঘ্যের instruction একসাথে অনেকগুলো decode করা কঠিন), কোডের মাপে, আর নকশা-যাচাইয়ের খরচে। কোনো লেখা “ARM সবসময় বেশি power-সাশ্রয়ী” বা “x86 সবসময় দ্রুত” বললে সন্দেহ করুন।
৭ · আজকের প্রধান ISA — কোথায় কোনটি চলে
| ISA | ধরন | প্রধান ব্যবহার (মোটামুটি) | লক্ষণীয় |
|---|---|---|---|
| x86-64 (AMD64 / Intel 64) | CISC, register–memory | বেশিরভাগ desktop, laptop আর data-center server | ১৯৭৮-এর 8086 পর্যন্ত সামঞ্জস্য; ১৬টি সাধারণ register; ১–১৫ byte instruction |
| ARM (AArch64 / ARMv8-A, ARMv9-A) | RISC, load–store | প্রায় সব স্মার্টফোন ও ট্যাবলেট; Apple-এর M-series Mac (২০২০ থেকে); ক্রমবর্ধমান cloud server (যেমন Amazon-এর Graviton); অসংখ্য microcontroller (Cortex-M) | ৩১টি সাধারণ register আর একটি zero register; AArch64-এ স্থির ৩২-বিট instruction; ISA-টি license দেওয়া হয় |
| RISC-V (RV32/RV64) | RISC, load–store | embedded controller, chip-এর ভেতরের সহায়ক core, শিক্ষা ও গবেষণা; Linux-চালানো বোর্ড আর বড় core-এর দিকে বাড়ছে | উন্মুক্ত — রয়্যালটি ছাড়া ব্যবহার করা যায়; মডিউলার extension |
| MIPS | RISC | পুরোনো router, set-top box, PlayStation 1–2; বহু পাঠ্যবইয়ে | নতুন নকশা প্রায় নেই; MIPS কোম্পানি নিজেই পরে RISC-V-তে সরে গেছে বলে জানা যায় |
| Power / PowerPC | RISC | IBM-এর POWER server; আগে Mac (১৯৯৪–২০০৬) আর কিছু game console | IBM ২০১৯ সালে ISA-টি OpenPOWER-এর মাধ্যমে উন্মুক্ত করার ঘোষণা দেয় |
| SPARC | RISC | Sun/Oracle আর Fujitsu-র server (ঐতিহাসিক) | Berkeley RISC-এর বংশধর; register window-এর জন্য বিখ্যাত |
বাজারের ভাগ বছরে বছরে বদলায়, তাই এখানে কোনো শতাংশ দেওয়া হলো না। একটি কথা অবশ্য নিশ্চিত: আপনার একটি ফোনের ভেতরেই কয়েকটি ISA একসাথে থাকতে পারে — প্রধান CPU-তে ARM, আর modem, ক্যামেরা বা power-নিয়ন্ত্রক অংশে ছোট ছোট অন্য core (কখনো RISC-V). ব্যবহারকারী শুধু প্রধান ISA-টি দেখেন।
৮ · RISC-V — এই কোর্সের শিক্ষার ISA
এই কোর্সের মূল ISA RISC-V, Patterson–Hennessy-র RISC-V সংস্করণের ধারায়। কারণ তিনটি: এটি সরল আর নিয়মিত, তাই datapath আর pipeline (মডিউল ৬) হাতে আঁকা যায়; এটি উন্মুক্ত, তাই spec, simulator আর এমনকি পূর্ণ CPU নকশা বিনামূল্যে পাওয়া যায়; আর এটি বাস্তবে ব্যবহৃত, শুধু খেলনা নয়। ISA-টির মান নির্ধারণ করে RISC-V International নামের একটি অলাভজনক সংস্থা (আগে RISC-V Foundation; ২০২০ সালের দিকে সুইজারল্যান্ডে নিবন্ধিত হয়ে নতুন নাম)।
Base আর extension — মডিউলার নকশা
RISC-V একটি ISA নয়, একটি পরিবার। ছোট একটি base ISA, তার ওপর প্রয়োজনমতো extension:
| নাম | কী যোগ করে |
|---|---|
| RV32I / RV64I | Base: ৩২ বা ৬৪ বিটের integer register আর মৌলিক instruction (load/store, যোগ-বিয়োগ, logic, shift, তুলনা, শাখা, লাফ, ecall, fence) — সব মিলিয়ে চল্লিশের কাছাকাছি। ছোট chip-এর জন্য ১৬টি register-এর RV32E-ও আছে। |
| M | গুণ আর ভাগ (mul, mulh, div, rem) |
| A | Atomic instruction (lr.w/sc.w, amoadd.w) — multicore-এর জন্য (পর্ব ৫৩) |
| F / D | Single / double precision floating point, আলাদা ৩২টি f-register (পর্ব ২৩) |
| C | Compressed: প্রচলিত instruction-এর ১৬-বিট রূপ — কোড ছোট হয় |
| V | Vector extension (১.০ সংস্করণ ২০২১ সালে অনুমোদিত) — পর্ব ৪২ |
| Zicsr, Zifencei | Control and status register (CSR) পড়া-লেখা; instruction-fetch-এর জন্য fence. আগে base-এর অংশ ছিল, পরে আলাদা নাম পায়। |
| G | “General” সংক্ষেপ: IMAFD + Zicsr + Zifencei. Linux চালানো chip সাধারণত RV64GC (G আর C). |
এত ঐচ্ছিক অংশ থাকলে software কীভাবে জানবে কোন chip-এ কী আছে? এজন্য RISC-V International profile প্রকাশ করে — যেমন RVA22, RVA23 — যা বলে “application processor হতে হলে অন্তত এই extension-গুলো থাকতেই হবে”। এটি এখনো বিবর্তনশীল একটি এলাকা; নির্দিষ্ট profile-এর তালিকা মূল নথিতে দেখে নেওয়াই নিরাপদ।
যা programmer দেখেন
- ৩২টি integer register, x0 থেকে x31, প্রতিটি XLEN বিটের (RV32-এ ৩২, RV64-এ ৬৪)। এদের register file-এ একসাথে রাখা হয়।
- x0 সবসময় ০ — একে বলে zero register. x0-তে লিখলে লেখাটি ফেলে দেওয়া হয়, পড়লে সবসময় ০ আসে। এতে অনেক কাজ আলাদা instruction ছাড়াই হয়:
mv a0, a1আসলেaddi a0, a1, 0;nopআসলেaddi x0, x0, 0. এমন সুবিধার নামগুলোকে বলে pseudo-instruction (পর্ব ২৬)। - PC আলাদা — x0–x31-এর অংশ নয়; সাধারণ instruction দিয়ে সরাসরি লেখা যায় না, শুধু শাখা আর লাফ দিয়ে বদলায়।
- মেমরি byte-addressed, little-endian: প্রতিটি byte-এর নিজস্ব ঠিকানা; একটি ৩২-বিট word-এর সবচেয়ে কম গুরুত্বের byte সবচেয়ে ছোট ঠিকানায় (পর্ব ০৬)। (Spec পরে ঐচ্ছিক big-endian মোডের জায়গা রেখেছে, তবে বাস্তবে প্রায় সব RISC-V সিস্টেম little-endian.)
- Instruction ৩২ বিটের, C extension থাকলে ১৬ বিটেরও। কোনো flag register নেই (পর্ব ২০-এর অংশ ২); তুলনা আর শাখা এক instruction-এ।
- Register-গুলোর calling-convention নাম আছে —
a0–a7argument,t0–t6অস্থায়ী,s0–s11সংরক্ষিত,sp,ra— বিস্তারিত পর্ব ২৬ আর ৩০-এ।
এবার একটি ছোট প্রোগ্রাম instruction ধরে ধরে চালানো যাক। মেমরির 0x100-এ আছে 25, 0x104-এ 17. প্রোগ্রামটি দুটি সংখ্যা পড়ে, যোগ-বিয়োগ-AND করে, যোগফল 0x108-এ রাখে — আর মাঝখানে ইচ্ছা করে x0-তে লেখার চেষ্টা করে:
00addi x5, x0, 25604lw x6, 0(x5)08lw x7, 4(x5)0cadd x8, x6, x710sub x9, x6, x714addi x0, x8, 10018and x10, x6, x71csw x8, 8(x5)
| PC (instruction-এর) | |
| x0 | |
| x5 | |
| x6 · x7 | |
| x8 · x9 · x10 | |
| mem[0x108] |
- ঠিকানা তৈরি। RISC-V-এ মেমরির ঠিকানা আগে একটি register-এ আনতে হয়।
addi x5, x0, 256: x0 (সবসময় ০) + 256 = 256 = 0x100. এখানে x0 “শূন্য ধ্রুবক” হিসেবে কাজ করল — আলাদা “load constant” instruction লাগল না। - প্রথম load.
lw x6, 0(x5): ঠিকানা = x5 + 0 = 0x100, সেখানকার ৪ byte পড়ে x6 = 25. মেমরি ছোঁয় শুধু load আর store — load–store নিয়ম। - দ্বিতীয় load.
lw x7, 4(x5): ঠিকানা 0x100 + 4 = 0x104 (byte-addressed, তাই পরের word ৪ byte দূরে), x7 = 17. - যোগ।
add x8, x6, x7: 25 + 17 = 42. তিনটি register operand — 3-address, কিন্তু সব register-এ। - বিয়োগ।
sub x9, x6, x7: 25 − 17 = 8. কোনো flag লেখা হয় না; শুধু x9. - x0-তে লেখার চেষ্টা।
addi x0, x8, 100হিসাব করে 142, কিন্তু গন্তব্য x0 — hardware লেখাটি ফেলে দেয়। x0 আগের মতোই 0. এটি ভুল নয়, নকশা: এমন instruction-এর বিট-রূপ প্রায়ইnop-এর মতো “কিছু না করা”-র কাজে লাগে। - AND.
and x10, x6, x7: 25 = 11001, 17 = 10001; বিট ধরে AND = 10001 = 17. - Store আর byte-ক্রম।
sw x8, 8(x5): 42 লেখা হলো 0x108-এ। Little-endian বলে 0x108-এ বসে সবচেয়ে ছোট byte2a(৪২), তারপর তিনটি00. প্রতিটি instruction ৪ byte, তাই PC বেড়েছে 0x00, 0x04 … 0x1c.
নিচের interpreter এই RV32I অংশটুকু (add, addi, sub, and, or, lw, sw) সত্যিই নকল করে: ৩২টি ৩২-বিট register (Int32Array, তাই যোগফল আপনা থেকেই mod 232), x0-তে লেখা বাতিল, আর একটি byte-ভিত্তিক little-endian মেমরি। প্রতিটি instruction-এর পর register ছাপে:
// Tiny RV32I subset: 32 registers (x0 hard-wired to 0), byte-addressed little-endian memory
const x = new Int32Array(32), mem = new Uint8Array(512);
const lw = a => mem[a] | mem[a + 1] << 8 | mem[a + 2] << 16 | mem[a + 3] << 24;
const sw = (a, v) => { for (let i = 0; i < 4; i++) mem[a + i] = (v >>> (8 * i)) & 0xff; };
const set = (rd, v) => { if (rd !== 0) x[rd] = v; }; // writes to x0 are discarded
sw(0x100, 25); sw(0x104, 17); // data: A at 0x100, B at 0x104
const prog = [
"addi x5, x0, 256", "lw x6, 0(x5)", "lw x7, 4(x5)", "add x8, x6, x7",
"sub x9, x6, x7", "addi x0, x8, 100", "and x10, x6, x7", "sw x8, 8(x5)",
];
const R = s => +s.slice(1);
let pc = 0;
const show = [0, 5, 6, 7, 8, 9, 10];
console.log("pc instruction " + show.map(r => ("x" + r).padStart(5)).join(""));
for (const line of prog) {
const [op, ...a] = line.replace(/[(),]/g, " ").trim().split(/\s+/);
if (op === "add") set(R(a[0]), x[R(a[1])] + x[R(a[2])]);
else if (op === "sub") set(R(a[0]), x[R(a[1])] - x[R(a[2])]);
else if (op === "and") set(R(a[0]), x[R(a[1])] & x[R(a[2])]);
else if (op === "or") set(R(a[0]), x[R(a[1])] | x[R(a[2])]);
else if (op === "addi") set(R(a[0]), x[R(a[1])] + Number(a[2]));
else if (op === "lw") set(R(a[0]), lw(x[R(a[2])] + Number(a[1])));
else if (op === "sw") sw(x[R(a[2])] + Number(a[1]), x[R(a[0])]);
console.log(pc.toString(16).padStart(2, "0") + " " + line.padEnd(20) + show.map(r => String(x[r]).padStart(5)).join(""));
pc += 4; // every RV32I instruction is 4 bytes
}
const bytes = [...mem.slice(0x108, 0x10c)].map(b => b.toString(16).padStart(2, "0")).join(" ");
console.log(`mem[0x108] = ${lw(0x108)} bytes at 0x108..0x10b: ${bytes} (little-endian)`);
console.log(`x0 after "addi x0, x8, 100": ${x[0]} (the result ${x[8] + 100} was thrown away)`);আউটপুট:
pc instruction x0 x5 x6 x7 x8 x9 x10
00 addi x5, x0, 256 0 256 0 0 0 0 0
04 lw x6, 0(x5) 0 256 25 0 0 0 0
08 lw x7, 4(x5) 0 256 25 17 0 0 0
0c add x8, x6, x7 0 256 25 17 42 0 0
10 sub x9, x6, x7 0 256 25 17 42 8 0
14 addi x0, x8, 100 0 256 25 17 42 8 0
18 and x10, x6, x7 0 256 25 17 42 8 17
1c sw x8, 8(x5) 0 256 25 17 42 8 17
mem[0x108] = 42 bytes at 0x108..0x10b: 2a 00 00 00 (little-endian)
x0 after "addi x0, x8, 100": 0 (the result 142 was thrown away)
প্রতিটি সারি stepper-এর একটি ধাপ। 0x14-এর সারিতে লক্ষ করুন: instruction চলেছে, কিন্তু x0 কলাম 0-ই রইল — পুরো x0-নিয়মটি simulator-এ এক লাইনের (if (rd !== 0)); বাস্তব hardware-এও এটি প্রায় এতটাই সস্তা: register file-এ x0-এর কোনো আসল ঘর রাখাই হয় না, পড়লে ০ পাঠানো হয়। শেষ লাইনের আগের লাইনে 42 মেমরিতে 2a 00 00 00 ক্রমে বসেছে — little-endian. sw কোনো register বদলায়নি, তাই শেষ দুই সারি একই। এই interpreter-এর বড় রূপ — decode, সব RV32I instruction, শাখা — আপনি ক্যাপস্টোনে (পর্ব ৫৭) বানাবেন।
৯ · ISA আর পারফরম্যান্স, নকশার তিন নীতি আর privilege level
IC বনাম CPI — একটি হিসাব
Iron law (পর্ব ০৩) বলে, ISA-র সিদ্ধান্ত IC আর CPI দুটিকেই প্রভাবিত করে — প্রায়ই উল্টো দিকে। ধরুন একই C প্রোগ্রাম দুটি ISA-র জন্য compile করা হলো, দুটি chip-ই 3 GHz-এ চলে:
| ISA | IC | গড় CPI | CPU time = IC × CPI ÷ f |
|---|---|---|---|
| P (CISC-ধাঁচ) | 1.0 × 109 | 3.0 | 1.0 × 109 × 3.0 ÷ (3 × 109) = 1.0 s |
| Q (RISC-ধাঁচ) | 1.6 × 109 | 1.25 | 1.6 × 109 × 1.25 ÷ (3 × 109) ≈ 0.667 s |
Q-তে ৬০% বেশি instruction চলেছে, তবু Q 1.0 ÷ 0.667 = 1.5 গুণ দ্রুত, কারণ CPI কমেছে আরও বেশি হারে। কিন্তু P-এর microarchitecture যদি micro-op আর out-of-order দিয়ে CPI-কে 2.0-তে নামায়, তাহলে P-এর সময় 0.667 s — সমান। এ কারণেই ISA একা performance ঠিক করে না: সংখ্যাগুলো সবসময় নির্দিষ্ট chip আর compiler-এর।
Patterson–Hennessy-র নকশা-নীতি
“Computer Organization and Design”-এর RISC-V সংস্করণে ISA নকশার তিনটি নীতি বলা হয়েছে (পুরোনো MIPS সংস্করণগুলোতে এগুলোর সাথে “make the common case fast”-সহ তালিকাটি একটু ভিন্ন ছিল):
- Simplicity favours regularity (সরলতা নিয়মিততাকে পছন্দ করে)। সব হিসাবের instruction-এ ঠিক তিনটি operand, একই ক্রমে; সব instruction একই মাপের। নিয়মিত হলে hardware সরল, আর সরল hardware দ্রুত ও সস্তা।
- Smaller is faster (ছোট হলে দ্রুত)। Register মাত্র ৩২টি, ৩২০টি নয় — বেশি হলে register file বড় হয়, signal-কে বেশি দূর যেতে হয়, clock ধীর হয়। এজন্যই operand register-এ, মেমরিতে নয়।
- Good design demands good compromises (ভালো নকশায় ভালো আপস লাগে)। সব instruction ৩২ বিট রাখার নিয়ম আর বড় ধ্রুবক রাখার প্রয়োজন — দুটি একসাথে পুরো মেটে না। RISC-V তাই কয়েকটি format রেখেছে (R, I, S, B, U, J), যাদের বিটগুলো যতটা সম্ভব একই জায়গায় — পর্ব ২৭-এর বিষয়।
Privilege level — কে কী করতে পারে
ISA শুধু সাধারণ প্রোগ্রামের instruction নয়, operating system-এর নিয়মও বেঁধে দেয়। একটি অ্যাপ যেন অন্য অ্যাপের মেমরি পড়তে বা hardware সরাসরি চালাতে না পারে, তার জন্য CPU কয়েকটি privilege level-এ চলে। RISC-V-এর privileged spec-এ তিনটি প্রধান mode: U (user — সাধারণ অ্যাপ), S (supervisor — OS kernel), M (machine — সবচেয়ে ক্ষমতাবান, firmware). x86-এ এর সমতুল্য ring 0 (kernel) থেকে ring 3 (user), ARM-এ EL0–EL3.
অ্যাপ যখন ফাইল খুলতে চায়, সে নিজে disk ছুঁতে পারে না; সে একটি system call করে। RISC-V-এ এর instruction ecall: এটি একটি exception তোলে, CPU নিয়ন্ত্রিতভাবে উচ্চতর mode-এ গিয়ে OS-এর নির্দিষ্ট handler-এ লাফ দেয়, কাজ শেষে sret/mret দিয়ে ফেরে। Pipeline-এ exception কীভাবে সামলানো হয়, তা পর্ব ৩৮-এ; OS-এর দিক থেকে system call দেখতে পাবেন অপারেটিং সিস্টেম কোর্সের System call পর্বে।
এই runner কোন ISA বলে?
শেষে একটি বাস্তব যন্ত্রকে প্রশ্ন করা যাক। এই পাতার কোড যে সার্ভারে চলে, সেটি কোন ISA-র, কয় বিটের, আর সেখানে gcc কোনো বাড়তি option ছাড়া কোন x86 extension ব্যবহার করতে পারে? (Script-টি ইচ্ছা করে CPU-র model-এর নাম বা flag-তালিকা ছাপে না।)
# Which ISA does this runner speak? (no CPU model name or flag list is printed)
echo "machine : $(uname -m)"
command -v getconf >/dev/null && echo "LONG_BIT : $(getconf LONG_BIT)" || echo "getconf not found"
v=$(printf '\001\000' | od -An -tu2 | tr -d ' ') # bytes 01 00 read as one 16-bit number
[ "$v" = "1" ] && echo "byte order : little-endian (01 00 -> 1)" || echo "byte order : big-endian ($v)"
if command -v gcc >/dev/null; then
m=$(echo | gcc -dM -E -x c - 2>/dev/null) # macros of gcc's default target
echo "x86 extensions gcc may use with no -march option:"
for f in SSE2 SSE4_2 AVX AVX2 AVX512F; do
case "$m" in *"__${f}__ 1"*) s=yes ;; *) s=no ;; esac
printf ' %-8s %s\n' "$f" "$s"
done
else
echo "gcc not found"
fiWandbox-এর bash-এ একটি চালানোর আউটপুট:
machine : x86_64
LONG_BIT : 64
byte order : little-endian (01 00 -> 1)
x86 extensions gcc may use with no -march option:
SSE2 yes
SSE4_2 no
AVX no
AVX2 no
AVX512F no
Runner-টি একটি shared virtual machine-এর x86-64 CPU: uname -m বলছে x86_64, LONG_BIT 64, আর byte 01 00-কে ১৬-বিট সংখ্যা হিসেবে পড়লে 1 আসে — অর্থাৎ ছোট byte আগে, little-endian. তারপর gcc-র ডিফল্ট target-এর তালিকা: কোনো -march না দিলে gcc শুধু SSE2 ধরে নেয় (SIMD, পর্ব ৪২), কারণ x86-64-এর মূল চুক্তিতে SSE2 বাধ্যতামূলক। SSE4.2, AVX, AVX2 বা AVX-512 সব x86-64 chip-এ থাকে না, তাই gcc নিজে থেকে সেগুলো ব্যবহার করে না — এই runner-এর CPU-তে সেগুলো থাকুক বা না থাকুক। একই ISA-র নাম “x86-64”, অথচ কোন extension থাকবে তা chip আর virtual machine-এর সেটিং-ভেদে আলাদা। তাই -march=native-এর মতো option দিয়ে বানানো বাইনারি পুরোনো chip-এ না-ও চলতে পারে, আর বাড়তি instruction ব্যবহার করতে চাইলে software চলার সময় cpuid দিয়ে যাচাই করে নেয়। RISC-V-এ একই প্রশ্নের উত্তর থাকে ISA-র নামেই (যেমন rv64imafdc), যা Linux /proc/cpuinfo-তে দেখায়।
আরও গভীরে: RISC আর CISC-এর সীমানা microarchitecture-এ আরও ঝাপসা। x86 core-গুলো সাধারণত একটি micro-op cache রাখে, যাতে একবার decode করা instruction আবার decode করতে না হয়, আর cmp + jne-এর মতো জোড়াকে এক micro-op-এ মিলিয়ে দেয় — একে বলে macro-op fusion. RISC-V-এর পক্ষে ২০১৬ সালের একটি Berkeley প্রতিবেদন (Celio ও সহকর্মীরা) যুক্তি দেয় যে সরল ISA রেখে বড় core-এ এমন fusion করলে বাড়তি জটিল instruction ছাড়াই একই লাভ পাওয়া যায়। আর Apple-এর M-series-এর মতো ARM core খুব চওড়া decoder ব্যবহার করে বলে বিশ্লেষকেরা জানান — স্থির দৈর্ঘ্যের instruction-এ পাশাপাশি অনেকগুলো decode করা সহজ, যা পরিবর্তনশীল দৈর্ঘ্যের x86-এ কঠিন।
১০ · AI-এর যুগে ISA শেখা
AI টুল RISC আর CISC-এর সংজ্ঞা সুন্দর গুছিয়ে বলে, কিন্তু নির্দিষ্ট ISA-র খুঁটিনাটি, instruction গোনা আর chip-এর তথ্যে প্রায়ই ভুল করে। প্রচলিত ভুলগুলো:
| AI-এর প্রচলিত ভুল | ঠিক কথা | কীভাবে যাচাই |
|---|---|---|
RISC-V কোডে MIPS-এর রীতি: $t0-ধাঁচের নাম, addu, branch delay slot | RISC-V-এ নাম t0/x5, addu নেই, delay slot নেই | RISC-V unprivileged spec আর একটি simulator (যেমন RARS, Spike) |
| “RISC মানে instruction সংখ্যা কম” | মূল কথা load–store, স্থির দৈর্ঘ্য, সরল encoding; AArch64-এ শত শত instruction | ISA manual-এর instruction তালিকা |
| 0/1/2/3-address কোডে instruction গোনায় ভুল, বা অস্থায়ী মান কোথায় তা না বলা | নিয়ম আগে ঠিক করে প্রতিটি instruction লেখা | হাতে stack/accumulator trace, বা এই পাতার generator |
| “x86 instruction সবসময় ১টি micro-op” বা নির্দিষ্ট সংখ্যা দাবি | Core-ভেদে আলাদা; fusion হয় | প্রস্তুতকারকের optimization manual, uops.info-এর মতো মাপজোখ |
| Chip-এর spec বানিয়ে বলা (core সংখ্যা, cache, কোন extension আছে) | শুধু datasheet বা প্রস্তুতকারকের তথ্য | Datasheet; Linux-এ /proc/cpuinfo-এর flags |
| “G = IMAFD” বলে Zicsr/Zifencei বাদ দেওয়া, বা C-কে G-এর অংশ ধরা | G = IMAFD + Zicsr + Zifencei; C আলাদা | RISC-V spec-এর নামকরণ অধ্যায় |
AI-কে প্রশ্ন করার সময় ISA, নিয়ম আর গোনার পদ্ধতি স্পষ্ট করে দিন, আর যাচাইযোগ্য trace চান:
AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- ISA আর microarchitecture-এর পার্থক্য? — ISA = programmer যা দেখেন (instruction, register, মেমরি মডেল); microarchitecture = ভেতরের নির্মাণ (pipeline, cache). একই ISA-র বহু microarchitecture হয়।
- 0-address instruction কোন যন্ত্রে? — Stack যন্ত্রে; operand stack-এর মাথায়, শুধু PUSH/POP-এ ঠিকানা।
- X = (A+B)×(C−D) 1-address কোডে কয়টি instruction? — ৭টি: LOAD C, SUB D, STORE T, LOAD A, ADD B, MUL T, STORE X.
- Load–store architecture কী? — শুধু load আর store মেমরি ছোঁয়; হিসাব সব register-এ — RISC-V, ARM.
- RISC-V-এ x0-এর বিশেষত্ব? — সবসময় ০; লেখা বাতিল হয়;
mv,nop,neg-এর মতো pseudo-instruction এর ওপর দাঁড়ানো।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১RISC যদি নকশায় “ভালো” হয়, তাহলে CISC x86 কীভাবে PC আর server-এর বাজার ধরে রাখল?
আশির দশকের শেষে অনেকেই ভেবেছিলেন x86-এর দিন শেষ। MIPS, SPARC আর পরে PowerPC chip একই সময়ের x86-এর চেয়ে অনেক কাজে দ্রুত ছিল, আর workstation-এর বাজার মূলত RISC-এর দখলে চলে গিয়েছিল। তবু নব্বইয়ের দশকে x86 শুধু টিকে থাকেনি, server-এও ঢুকে পড়ে। কারণ একটি নয়, কয়েকটি একসাথে।
প্রথম, software-এর পাহাড়। IBM PC (১৯৮১) আর তার নকলগুলোর জন্য লেখা লাখো প্রোগ্রাম ছিল x86 বাইনারি। ব্যবহারকারী একটি chip কেনেন না, কেনেন তাঁর প্রোগ্রাম চালানোর যন্ত্র। নতুন ISA মানে সব প্রোগ্রাম নতুন করে আনা — যা কোনো একক কোম্পানির হাতে ছিল না।
দ্বিতীয়, বিক্রির পরিমাণ আর অর্থ। PC-র বিশাল বাজার থেকে আসা আয় Intel-কে নতুন নকশা আর নতুন কারখানায় অন্য সবার চেয়ে বেশি বিনিয়োগ করতে দেয়। বহু বছর Intel transistor বানানোর প্রযুক্তিতে প্রতিযোগীদের চেয়ে এগিয়ে ছিল; ফলে ISA-র যে অসুবিধা, তা দ্রুততর transistor দিয়ে পুষিয়ে যেত।
তৃতীয়, ভেতরে RISC হয়ে যাওয়া। ১৯৯৫ সালের Pentium Pro দেখাল যে x86 instruction-কে decode করে micro-op-এ ভেঙে RISC-এর মতোই pipeline আর out-of-order চালানো যায়। Decoder-এর বাড়তি খরচ থাকল, কিন্তু chip-এ transistor যত বাড়ল, সেই খরচের অংশ তত ছোট হলো। RISC-এর মূল কৌশল এভাবে CISC-এর ভেতরে ঢুকে পড়ল।
কিন্তু যেখানে এই সুবিধাগুলো কাজ করেনি, সেখানে গল্প উল্টো। মোবাইল ফোনে পুরোনো x86 software-এর কোনো পাহাড় ছিল না, আর বিদ্যুৎ খরচ ছিল প্রধান বিবেচনা — সেখানে ARM প্রায় একচেটিয়া হয়ে যায়। Intel-এর Atom দিয়ে ফোনে ঢোকার চেষ্টা বড় সাফল্য পায়নি। আর ২০২০-এর দশকে Apple-এর ARM-ভিত্তিক Mac আর ARM-ভিত্তিক cloud server দেখাচ্ছে, সঠিক অনুবাদক software আর বড় বিনিয়োগ থাকলে x86-এর দুর্গেও ফাটল ধরানো যায়।
মূল উপলব্ধি: ISA-র বাজার জেতে শুধু প্রযুক্তিগত উৎকর্ষে নয় — compatibility, পরিমাণ, তৈরির প্রযুক্তি আর microarchitecture মিলিয়ে। একটি দুর্বল চুক্তিকেও যথেষ্ট বিনিয়োগে দ্রুত বানানো যায়; আর একটি ভালো চুক্তি software ছাড়া একা কিছু জেতে না।
প্র ০২আসল CPU থেকে stack যন্ত্র প্রায় হারিয়ে গেছে, অথচ JVM, WebAssembly আর Python-এর bytecode stack-ভিত্তিক। কেন?
Hardware-এ stack যন্ত্রের সমস্যা অংশ ৩-এ দেখেছেন: প্রতিটি instruction stack-এর মাথার ওপর নির্ভর করে, তাই পাশাপাশি দুটি instruction সাধারণত স্বাধীন নয় — pipeline আর একসাথে অনেক instruction চালানো কঠিন। মাথার মান ছাড়া কিছু ধরে রাখা যায় না, তাই একই মান বারবার মেমরি থেকে আনতে হয়। ষাট-সত্তরের দশকে Burroughs-এর বড় stack যন্ত্র ছিল, পরে কিছু Java-চালানো chip-ও বানানো হয়েছিল, কিন্তু register যন্ত্রের গতির সাথে পাল্লা দিতে পারেনি।
কিন্তু virtual machine-এর ক্ষেত্রে হিসাব আলাদা। Java-র bytecode-এর লক্ষ্য ছিল “একবার লিখুন, যেকোনো জায়গায় চালান” — বাইনারিটি এমন হতে হবে যে কোনো CPU-র register সংখ্যা ধরে নেয় না। Stack bytecode ঠিক তা-ই: iload_1; iload_2; iadd কোনো register-এর নাম বলে না। Compiler বানানো সহজ, কারণ expression tree-কে postfix ক্রমে লিখলেই কোড — অংশ ৪-এর 0-address generator ঠিক এই কাজটি করে। আর instruction ছোট, তাই নেটওয়ার্কে পাঠানোর ফাইলও ছোট — নব্বইয়ের দশকে applet-এর যুগে এটি গুরুত্বপূর্ণ ছিল।
গতির সমস্যাটি সমাধান হয় আরেক স্তরে: JIT compiler. প্রোগ্রাম চলার সময় JVM বা browser-এর WebAssembly ইঞ্জিন stack bytecode বিশ্লেষণ করে বুঝে নেয় কোন মান কোথা থেকে আসছে, আর তা আসল CPU-র register-ভিত্তিক machine code-এ অনুবাদ করে — register allocation তখন JIT-এর কাজ। অর্থাৎ stack একটি বিতরণের বিন্যাস, চালানোর নয়।
উল্টো উদাহরণও শিক্ষণীয়। Android-এর পুরোনো Dalvik VM ইচ্ছা করে register-ভিত্তিক bytecode বেছে নিয়েছিল — যুক্তি ছিল interpreter-এ কম instruction চালানো আর কম মেমরি-নড়াচড়া, যা দুর্বল ফোনে গুরুত্বপূর্ণ। Lua-র VM-ও register-ভিত্তিক। গবেষণায় দেখা গেছে, interpreter-এ register bytecode কম instruction চালায় কিন্তু প্রতিটি instruction বড় — অংশ ৪-এর সেই একই বিনিময়।
মূল উপলব্ধি: ISA-র নকশা নির্ভর করে কে সেটি চালাবে তার ওপর। Hardware-এর জন্য register আর load–store; এক যন্ত্র থেকে আরেক যন্ত্রে পাঠানোর জন্য ছোট, register-নিরপেক্ষ stack bytecode — আর মাঝখানে একটি compiler, যে একটিকে অন্যটিতে বদলায়।
প্র ০৩RISC-V একটি পুরো register (x0) সবসময় ০ রেখে “নষ্ট” করে। এতে লাভ কী, আর অন্য ISA কী করে?
৩২টির একটি register বাদ দেওয়া মানে compiler-এর হাতে ৩১টি — প্রায় ৩% কম। বিনিময়ে যা পাওয়া যায়, তা অনেক বেশি। সবচেয়ে বড় লাভ instruction সংখ্যা ছোট রাখা। শূন্য একটি এত প্রচলিত operand যে আলাদা instruction না বানিয়ে x0 দিয়েই অনেক কাজ হয়: register কপি mv rd, rs = addi rd, rs, 0; ধ্রুবক আনা li rd, 5 = addi rd, x0, 5; ঋণাত্মক neg rd, rs = sub rd, x0, rs; শূন্যের সাথে তুলনা beqz rs, L = beq rs, x0, L; শর্তহীন লাফ j L = jal x0, L — ফেরার ঠিকানা x0-তে লিখে ফেলে দেওয়া। আর nop = addi x0, x0, 0.
এই প্রতিটি “instruction” আসলে assembler-এর একটি সুবিধার নাম — pseudo-instruction. Hardware-কে আলাদা কিছু শিখতে হয় না, decoder-এ নতুন opcode লাগে না। এটি Patterson–Hennessy-র “simplicity favours regularity” নীতির সরাসরি প্রয়োগ।
Hardware-এর দিক থেকেও x0 সস্তা: register file-এ x0-এর জন্য আসল ঘর রাখার দরকার নেই, পড়ার পথে একটি ধ্রুবক ০ আর লেখার পথে “গন্তব্য ০ হলে লেখা বাদ” — কয়েকটি gate. Pipeline-এ forwarding-এর নিয়মেও একটি শর্ত যোগ হয়: x0-কে কখনো forward করা হয় না (পর্ব ৩৬)।
অন্য ISA-গুলো কী করে? MIPS-এ ঠিক একই ধারণা — $zero. SPARC-এ %g0. ARM-এর AArch64-এ আছে XZR/WZR, কিন্তু সেখানে একটি চতুর আপস: register নম্বর 31 কিছু instruction-এ zero register, আর কিছু instruction-এ (যেমন ঠিকানা হিসাব) stack pointer — encoding-এর একটি বিট বাঁচাতে। ৩২-বিট ARM (AArch32)-এ zero register ছিল না, বরং PC নিজেই সাধারণ register r15 — যা pipeline নকশায় ঝামেলা করত, আর AArch64-এ সেটি বাদ দেওয়া হয়। x86-এ zero register নেই; শূন্য বানাতে প্রচলিত কৌশল xor eax, eax, যা আধুনিক core বিশেষভাবে চিনে নেয় আর পূর্ববর্তী মানের ওপর নির্ভরতা ভেঙে দেয়।
মূল উপলব্ধি: x0 হলো “ভালো আপস”-এর ক্লাসিক উদাহরণ — একটি register-এর দাম দিয়ে ডজনখানেক instruction বাদ, সরল decoder আর নিয়মিত encoding. ISA নকশায় প্রায়ই সবচেয়ে বড় লাভ আসে কোনো কিছু যোগ করে নয়, একটি সাধারণ ব্যবস্থাকে বহু কাজে লাগিয়ে।
প্র ০৪RISC-V “open ISA” — এই উন্মুক্ততা আসলে কী বদলায়, আর কী বদলায় না?
প্রথমে স্পষ্ট করা দরকার কী উন্মুক্ত। RISC-V-এর spec — অর্থাৎ চুক্তি — উন্মুক্ত: যে কেউ রয়্যালটি বা license-চুক্তি ছাড়া এই ISA মেনে একটি processor বানাতে, বিক্রি করতে বা পড়াতে পারেন। এর তুলনায় ARM-এর ISA ব্যবহার করতে ARM-এর সাথে চুক্তি লাগে, আর x86-এর ISA কার্যত Intel আর AMD-এর হাতে (পারস্পরিক cross-license-এর মাধ্যমে)। কিন্তু chip-এর নকশা উন্মুক্ত হতেই হবে এমন নয়। কোনো কোম্পানি RISC-V-এর একটি দ্রুত core বানিয়ে তার microarchitecture সম্পূর্ণ গোপন রাখতে পারে, আর অনেকেই তা-ই করে। আবার কিছু core-এর নকশা সত্যিই open-source (যেমন বিভিন্ন বিশ্ববিদ্যালয় ও প্রকল্পের core). দুটি আলাদা জিনিস — উন্মুক্ত চুক্তি আর উন্মুক্ত নির্মাণ।
কী বদলায়: ছোট কোম্পানি, বিশ্ববিদ্যালয় আর গবেষকেরা কোনো অনুমতি ছাড়াই নিজের CPU বানাতে পারেন; একটি chip-এর ভেতরে নিয়ন্ত্রণের ছোট ছোট core বসাতে license-খরচ লাগে না; নিজের কাজের জন্য custom instruction যোগ করার জায়গা spec-এ রাখা আছে; শিক্ষায় পুরো CPU-র source পড়া যায়। বিভিন্ন দেশ প্রযুক্তিগত স্বনির্ভরতার কৌশলেও RISC-V-কে গুরুত্ব দিচ্ছে বলে খবর পাওয়া যায়। বাংলাদেশের মতো দেশে, যেখানে chip-নকশার শিল্প এখনো প্রাথমিক পর্যায়ে বলে মনে করা হয়, শিক্ষার্থীদের জন্য RISC-V-এ বিনামূল্যে simulator, FPGA-তে core আর পূর্ণ নথি — হাতে-কলমে শেখার একটি বড় সুযোগ।
কী বদলায় না: একটি দ্রুত chip বানানোর খরচ। Out-of-order core-এর নকশা, যাচাই আর আধুনিক কারখানায় তৈরিতে বিপুল অর্থ আর অভিজ্ঞ দল লাগে — ISA বিনামূল্যে হলেও এই খরচ কমে না। Software-এর পরিবেশ (compiler, OS, library) গড়ে উঠতে বহু বছর লাগে। আর উন্মুক্ততার একটি ঝুঁকি: প্রত্যেকে নিজের মতো extension যোগ করলে ISA টুকরো হয়ে যেতে পারে — এক chip-এর বাইনারি অন্যটিতে চলবে না। এজন্যই অংশ ৮-এর profile (RVA22, RVA23 ইত্যাদি) — একটি ন্যূনতম সাধারণ সেট, যাতে Linux distribution একটি বাইনারি দিয়ে বহু chip-এ চলতে পারে।
মূল উপলব্ধি: RISC-V উন্মুক্ত করেছে চুক্তি, নির্মাণ নয়। এতে ঢোকার দরজা খুলেছে আর উদ্ভাবন সহজ হয়েছে, কিন্তু ভালো chip বানানোর কঠিন অংশ — microarchitecture, যাচাই, তৈরি আর software — আগের মতোই কঠিন।
প্র ০৫স্থির দৈর্ঘ্যের instruction না পরিবর্তনশীল দৈর্ঘ্যের — code density আর decode-এর সরলতার মধ্যে বিনিময় কীভাবে মেটানো হয়েছে?
পরিবর্তনশীল দৈর্ঘ্যের বড় সুবিধা code density — একই প্রোগ্রাম কম byte-এ। x86-এ অনেক প্রচলিত instruction ১ থেকে কয়েক byte-এর (যেমন অংশ ৬-এর add [rbx], eax মাত্র ২ byte), আর বিরলগুলো লম্বা; তাই গড়ে কোড ছোট হয়। ছোট কোড মানে instruction cache-এ বেশি কোড ধরে, মেমরি থেকে কম আনতে হয় — যা আজও গুরুত্বপূর্ণ, আর ছোট embedded chip-এ (যেখানে flash মেমরি কম) আরও বেশি।
দাম দিতে হয় decode-এ। x86-এ একটি instruction কোথায় শেষ, তা জানতে prefix, opcode, ModRM ইত্যাদি byte পড়ে বুঝতে হয়; পরের instruction কোথায় শুরু, তা আগেরটি না বুঝে জানা যায় না। প্রতি cycle-এ চার-পাঁচটি বা আরও বেশি instruction decode করতে চাইলে hardware-কে অনেক সম্ভাব্য শুরুর বিন্দু একসাথে পরীক্ষা করতে হয় — জটিল, power-খাওয়া কাজ। এজন্যই x86 core-গুলো micro-op cache রাখে: একবার decode হলে আবার না করা। স্থির ৩২-বিট instruction-এ কাজটি তুচ্ছ — প্রতি ৪ byte-এ একটি নতুন instruction, যত চওড়াই হোক।
RISC শিবির density-র সমস্যা মেনে নিয়ে মাঝামাঝি পথ খুঁজেছে। ARM ১৯৯০-এর দশকে Thumb আনে — প্রচলিত instruction-এর ১৬-বিট রূপ, পরে Thumb-2-এ ১৬ আর ৩২ বিট মিশিয়ে। Cortex-M microcontroller শুধু Thumb চালায়। কিন্তু ৬৪-বিট AArch64-এ ARM আবার শুধু স্থির ৩২ বিটে ফিরে যায় — বড় core-এ decode-এর সরলতাকে বেশি দাম দিয়ে। RISC-V-এর C extension আরেকটি আপস: শুধু দুটি দৈর্ঘ্য (১৬ আর ৩২ বিট), আর দৈর্ঘ্য বোঝা যায় প্রথম দুটি বিট দেখেই — তাই decode এখনো সহজ, অথচ RISC-V নথি অনুযায়ী প্রচলিত কোডের বড় অংশ ১৬-বিটে লেখা যায় আর কোড উল্লেখযোগ্যভাবে ছোট হয় (সঠিক হার প্রোগ্রাম আর compiler-ভেদে আলাদা)।
লক্ষ করুন, এই বিনিময় পরীক্ষার “কম instruction বনাম ছোট instruction” প্রশ্নেরই বড় রূপ: অংশ ৪-এর 3-address মেমরি instruction (১৩ byte) আর 0-address instruction (১ byte)-এর দ্বন্দ্ব, শুধু বাস্তব chip-এর মাপে।
মূল উপলব্ধি: Code density আর decode-সরলতা একসাথে সর্বোচ্চ করা যায় না। x86 density বেছে নিয়ে decode-এর দাম hardware দিয়ে মেটায়; AArch64 সরলতা বেছে নিয়েছে; RISC-V দুটি দৈর্ঘ্যে সীমিত রেখে মাঝখানে দাঁড়িয়েছে — কোন ভারসাম্য ঠিক, তা নির্ভর করে chip কোথায় ব্যবহার হবে তার ওপর।
অনুশীলন
পরীক্ষার ধাঁচে লেখা — আগে খাতায় সমাধান করুন, তারপর উত্তর মিলিয়ে নিন।
-
কোড লিখুন আর গুনুন: Y = (A − B) ÷ (C + D × E)-এর জন্য 3-address, 2-address, 1-address আর 0-address কোড লিখুন। নিয়ম: সব operand আর অস্থায়ী মান (T1, T2 …) মেমরিতে; 2-address-এ
OP X, Sমানে X ← X op S. প্রতিটির instruction সংখ্যা বলুন, আর A = 20, B = 4, C = 3, D = 5, E = 1 দিয়ে Y যাচাই করুন।3-address (4): SUB T1,A,B; MUL T2,D,E; ADD T3,C,T2; DIV Y,T1,T3 2-address (6): MOV Y,A; SUB Y,B; MOV T1,D; MUL T1,E; ADD T1,C; DIV Y,T1 1-address (8): LOAD D; MUL E; ADD C; STORE T1; LOAD A; SUB B; DIV T1; STORE Y 0-address (10): PUSH A; PUSH B; SUB; PUSH C; PUSH D; PUSH E; MUL; ADD; DIV; POP Y Y = (20 - 4) / (3 + 5 x 1) = 16 / 8 = 2- উত্তর ৪, ৬, ৮, ১০টি instruction — পাঠ্যবইয়ের প্রচলিত উত্তরের সাথে মেলে। (অংশ ৪-এর generator-এ expression বদলে চালালে হুবহু এই কোড আসে; সেখানে মেমরি-ট্রাফিক: ৮+৪, ১০+৬, ৬+২, ৫+১ পড়া+লেখা।)
- 2-address-এ C + D×E লিখতে যোগের বিনিময়যোগ্যতা কাজে লাগানো হয়েছে: আগে T1 = D×E, তারপর T1 = T1 + C. বাঁ থেকে লিখলে (MOV T1,C; MOV T2,D; MUL T2,E; ADD T1,T2) একটি বেশি লাগত।
- 1-address-এ ভাগ বিনিময়যোগ্য নয়, তাই ভাজক (C + D×E) আগে হিসাব করে T1-এ রাখতে হয়, তারপর A − B, তারপর
DIV T1.
-
Iron law-এর হিসাব: একটি প্রোগ্রাম ISA A-তে ৬ লাখ instruction-এ চলে, গড় CPI 2.5, clock 1 GHz. একই প্রোগ্রাম ISA B-তে ১০ লাখ instruction নেয়, গড় CPI 1.2, clock 1.25 GHz. (ক) দুটির CPU time বের করুন। কোনটি কত গুণ দ্রুত? (খ) A-কে B-এর সমান দ্রুত করতে (IC আর clock একই রেখে) A-এর CPI কত হতে হবে?
A: 600,000 x 2.5 / 1e9 = 1.5e-3 s = 1.50 ms B: 1,000,000 x 1.2 / 1.25e9 = 9.6e-4 s = 0.96 ms speedup = 1.50 / 0.96 = 1.5625 (B is about 1.56x faster) CPI_A needed = 0.96e-3 x 1e9 / 600,000 = 1.6- B-তে instruction ৬৭% বেশি, তবু কম CPI আর দ্রুত clock মিলিয়ে B জেতে — RISC-এর যুক্তির সংখ্যাগত রূপ।
- (খ) A-এর CPI 2.5 থেকে 1.6-এ নামলে দুটি সমান। আধুনিক x86 micro-op আর out-of-order দিয়ে ঠিক এই কাজটিই করে — অর্থাৎ ISA-র “অসুবিধা” microarchitecture দিয়ে পুষিয়ে নেওয়া যায়।
-
আউটপুট অনুমান করুন (হাতে trace): RV32I-এ নিচের প্রোগ্রাম চলার পর x0, x3, x4, x5, x6 আর x7-এর মান দশমিকে কত? x3 আর x7 hex-এ কত?
addi x1, x0, 12 addi x2, x0, 5 sub x3, x2, x1 addi x0, x1, 100 add x4, x0, x3 and x5, x1, x2 or x6, x1, x2 sub x7, x0, x2x1 = 12, x2 = 5 x3 = 5 - 12 = -7 = 0xFFFFFFF9 x0 : write of 112 discarded -> 0 x4 = 0 + (-7) = -7 x5 = 1100 AND 0101 = 0100 = 4 x6 = 1100 OR 0101 = 1101 = 13 x7 = 0 - 5 = -5 = 0xFFFFFFFB (this is the pseudo-instruction neg x7, x2)- ফাঁদ দুটি: x0-তে লেখা (12 + 100 = 112) বাতিল, তাই পরের
add x4, x0, x3-এ x0 = 0, x4 = −7; আর বিয়োগের ক্রম —sub x3, x2, x1মানে x2 − x1, অর্থাৎ 5 − 12. - ঋণাত্মক মান register-এ two's complement-এ থাকে (পর্ব ০৫): −7 = 0xFFFFFFF9, −5 = 0xFFFFFFFB. একটি ছোট script-এ চালিয়ে এই মানগুলো যাচাই করা হয়েছে।
- ফাঁদ দুটি: x0-তে লেখা (12 + 100 = 112) বাতিল, তাই পরের
-
ISA না microarchitecture? প্রতিটি বিষয় ISA-র অংশ, নাকি microarchitecture-এর — কারণসহ বলুন: (ক) RISC-V-এ integer register ৩২টি; (খ) একটি chip-এর L1 data cache ৪৮ KiB; (গ) x86-এ
addflag লেখে; (ঘ) pipeline-এর ধাপ ১৪টি; (ঙ) মেমরি little-endian; (চ) একটি core x86 instruction-কে micro-op-এ ভাঙে; (ছ)ecallএকটি exception তোলে; (জ) গুণ ৩ cycle-এ শেষ হয়।- ISA: (ক), (গ), (ঙ), (ছ). এগুলো বদলালে সঠিক প্রোগ্রামের ফল বদলাতে পারে — compiler-কে register সংখ্যা জানতে হয়; flag না লিখলে পরের শাখা ভুল হবে; byte-ক্রম বদলালে একই byte ভিন্ন সংখ্যা;
ecall-এর আচরণ OS-এর সাথে চুক্তি। - Microarchitecture: (খ), (ঘ), (চ), (জ). এগুলো শুধু গতি (CPI, cycle time) বদলায়, ফল নয়। একই x86-64 ISA-র ভিন্ন chip-এ এগুলো আলাদা।
- সূক্ষ্ম কথা: (চ) বাইরে থেকে অদৃশ্য — micro-op ISA-র অংশ নয়, তাই নির্মাতা প্রতিটি প্রজন্মে ভাঙন বদলাতে পারেন। আর (জ)-এর মতো সময়ের তথ্য ফল বদলায় না, তবে নিরাপত্তায় (timing side channel) প্রভাব ফেলতে পারে — অংশ ১-এর শেষ অনুচ্ছেদ।
- ISA: (ক), (গ), (ঙ), (ছ). এগুলো বদলালে সঠিক প্রোগ্রামের ফল বদলাতে পারে — compiler-কে register সংখ্যা জানতে হয়; flag না লিখলে পরের শাখা ভুল হবে; byte-ক্রম বদলালে একই byte ভিন্ন সংখ্যা;
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমISA-র ইতিহাসের এই ঘটনাগুলো সময়ের ক্রমে সাজান (▲▼ চেপে সরান)
- IBM System/360 — এক ISA, বহু মডেল (১৯৬৪)
- Intel 8086 — x86-এর শুরু (১৯৭৮)
- Patterson ও Ditzel-এর “The Case for the RISC” প্রবন্ধ (১৯৮০)
- Acorn-এর ARM1 chip (১৯৮৫)
- Pentium Pro — x86 ভেতরে micro-op-এ (১৯৯৫)
- Berkeley-তে RISC-V প্রকল্পের শুরু (২০১০)
নিচের কোনটি একটি ISA-র চুক্তির অংশ নয়?
- Programmer কয়টি সাধারণ register দেখেন
- মেমরির byte-ক্রম (endianness)
- L1 cache-এর আকার
- Instruction-এর বিট-বিন্যাস (encoding)
Accumulator যন্ত্রে (1-address instruction, মধ্যবর্তী মান মেমরির T-তে) X = (A + B) × (C − D) লিখতে সবচেয়ে কম কয়টি instruction লাগে?
- ৫টি
- ৭টি
- ৮টি
- ৩টি
RV32I-এ addi x0, x5, 7 চলার পর x0-এর মান কত (x5 = 10)?
- 17
- Illegal instruction exception হয়
- 10
- 0
Load–store architecture-এর সংজ্ঞাসূচক বৈশিষ্ট্য কোনটি?
- শুধু load ও store instruction মেমরি ছোঁয়, হিসাব সব register-এ
- যেকোনো হিসাবের instruction-এ একটি operand মেমরিতে থাকতে পারে
- Operand সবসময় stack-এর মাথায় থাকে
- একটিমাত্র register-এ সব হিসাবের ফল জমা হয়
RISC-V-এর নামকরণে “G” বলতে কোন অংশগুলো বোঝায়?
- I, M, A, F, D আর C
- I, M, A আর V
- I, M, A, F, D আর Zicsr ও Zifencei
- শুধু base integer instruction
আধুনিক উচ্চগতির x86 core সম্পর্কে কোন কথাটি সঠিক?
- এগুলো আর x86 instruction চালায় না, শুধু RISC instruction
- Decoder x86 instruction-কে RISC-ধাঁচের micro-op-এ ভেঙে চালায়
- প্রতিটি x86 instruction ঠিক এক cycle-এ শেষ হয়
- সব x86-64 instruction স্থির ৪ byte-এর
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- ISA = চুক্তি: instruction ও তাদের অর্থ, register, ডেটার মাপ, মেমরি মডেল, encoding, privilege ও exception. Pipeline, cache, CPI — microarchitecture. পরীক্ষা: “বদলালে কি ফল বদলায়?”
- Compatibility-ই আয়ু: x86 ১৯৭৮ থেকে, System/360-এর বংশ ১৯৬৪ থেকে — চুক্তি স্থির রেখে ভেতরটা বারবার নতুন।
- Operand কোথায়: stack (JVM, x87), accumulator, register–memory (x86), load–store (RISC-V, ARM). C = A + B-এ ৪/৩/৩/৪টি instruction; মান পুনর্ব্যবহারে load–store-এর মেমরি-ট্রাফিক সবচেয়ে কম।
- Address সংখ্যা: X = (A+B)×(C−D)-এ 3/2/1/0-address কোডে ৩/৫/৭/৮টি instruction — কম instruction মানে লম্বা instruction; ট্রাফিকের ছবি আলাদা।
- RISC বনাম CISC আর RISC-V: দর্শন আলাদা, আজ মিলে গেছে (x86-এর micro-op, ARM-এর জটিল instruction). RISC-V: base RV32I + M, A, F, D, C, V…; G = IMAFD + Zicsr + Zifencei; x0 = 0; little-endian; ৩২-বিট (আর ১৬-বিট) instruction; U/S/M mode,
ecall.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২৬ · RISC-V-এর instruction — register, arithmetic, load আর storeপরবর্তী পর্বRV32I-এর instruction বিস্তারিত।
- পর্ব ০১ · কম্পিউটার আর্কিটেকচার কী — transistor থেকে অ্যাপ পর্যন্ত স্তরগুলোভিত্তিISA বনাম microarchitecture, fetch–decode–execute.
- পর্ব ০২ · ইতিহাস আর প্রবণতা — ENIAC থেকে আজকের chip, Moore-এর সূত্র আর power wallভিত্তিSystem/360, RISC আন্দোলন আর RISC-V-এর জন্ম।
- পর্ব ০৩ · পারফরম্যান্স মাপা — CPU time, CPI, MIPS, Amdahl-এর সূত্র আর benchmarkভিত্তিIC × CPI × T — ISA-র সিদ্ধান্ত মাপার সূত্র।
- কম্পিউটার আর্কিটেকচার ও ডিজিটাল লজিক · কোর্স হোম৬২টি পর্বলজিক গেট থেকে pipeline আর cache — পুরো রোডম্যাপ।
- C প্রোগ্রামিং · C আর assemblyপাশের ট্র্যাকC কোড machine instruction-এ কীভাবে নামে।
- অপারেটিং সিস্টেম · System callপাশের ট্র্যাকUser mode থেকে kernel-এ — ecall আর syscall-এর OS-এর দিক।