Foundationপ্রথম নীতি থেকে
LEVEL 3লেসন ১/১৭মাঝারি৫০ মিনিট

ISA — Hardware আর Software-এর মধ্যেকার চুক্তি

What Is an ISA?

ISA হলো একটা নির্দিষ্ট, লিখিত vocabulary — instruction, register, আচরণ — যেটা মেনে চললে যেকোনো compiler-লেখা প্রোগ্রাম, আর যেকোনো সঠিকভাবে বানানো CPU, একে অপরকে বোঝে; ভেতরের circuit যা-ই হোক না কেন। আজ থেকে Level 2-এর গেট-লেভেল জগৎ ছেড়ে আমরা সেই চুক্তির স্তরে উঠছি।

এই লেসন শেষে আপনি পারবেন

  • ISA-কে hardware আর software-এর মধ্যেকার একটা আনুষ্ঠানিক 'contract' হিসেবে সংজ্ঞায়িত করতে পারবেন, আর ঠিক কী কী সেই contract-এর অংশ তা তালিকাভুক্ত করতে পারবেন
  • ISA আর microarchitecture-এর পার্থক্য ব্যাখ্যা করতে পারবেন — কেন একই ISA বহু সম্পূর্ণ ভিন্ন hardware দিয়ে বাস্তবায়ন করা যায়
  • কেন এই বিভাজনটাই backward compatibility সম্ভব করে তা concrete উদাহরণ (Intel-এর ২০ বছরের পুরোনো binary চালানো) দিয়ে যুক্তি দিতে পারবেন
  • instruction-এর চারটা প্রধান শ্রেণি — data movement, arithmetic/logic, control flow, I/O — চিনতে ও নতুন instruction দেখলে শ্রেণীবদ্ধ করতে পারবেন
  • objdump/Compiler Explorer দিয়ে একটা compiled binary-র ভেতরের প্রকৃত ISA instruction দেখতে ও পড়তে পারবেন
  • গত মডিউলের toy ISA (৪টা instruction) আর বাস্তব ISA (RISC-V, x86-64)-এর মধ্যে স্কেলের পার্থক্যটা সংখ্যা দিয়ে ব্যাখ্যা করতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

গত লেসনের শেষ বাক্যটা আরেকবার পড়া যাক: একটা CPU আসলে শুধু গেট — লক্ষ লক্ষ, বিলিয়ন, কোটি কোটি গেট — একটা state machine দিয়ে সুনির্দিষ্টভাবে wire করা, আর আজকের ইন্ডাস্ট্রি সেই wiring-টা হাতে না এঁকে বর্ণনা করে কোডে।

ষোলোটা লেসন ধরে আমরা ঠিক এই কাজটাই করেছি — নিচ থেকে উপরে। Transistor থেকে গেট, গেট থেকে ALU, ALU আর register file থেকে datapath, datapath-এর সাথে একটা control-unit FSM জুড়ে একটা সৎ, কাজ-করা CPU। শেষ লেসনে সেই পুরো জিনিসটা Verilog-এ লিখলাম, always @(posedge clk)-এর একটা লাইনেই একটা flip-flop-এর জন্ম দেখলাম। আমাদের toy CPU-টা মাত্র ৪টা instruction চিনত — ADD, SUB, LOAD, STORE — প্রতিটার opcode ২ bit, পুরো ISA-টা একটা ছোট টেবিলে আঁটে।

এবার একটা প্রশ্ন করা যাক যেটা এতদিন এড়িয়ে গেছি। ধরুন আপনি ২০০৫ সালে একটা প্রোগ্রাম কিনেছিলেন — একটা .exe ফাইল, কম্পাইল করা, কোনো source code নেই। আজ, ২০ বছর পর, একটা সম্পূর্ণ নতুন CPU-তে (সম্পূর্ণ ভিন্ন internal pipeline, ভিন্ন cache আকার, ভিন্ন transistor সংখ্যা — Intel Pentium 4 বনাম Intel Core Ultra) সেই একই .exe ফাইলটা এখনও চলে। কোনো recompile লাগে না। এই দুইটা CPU-র ভেতরের circuit-এ প্রায় কোনো মিলই নেই — সম্পূর্ণ ভিন্ন transistor সংখ্যা, ভিন্ন pipeline গভীরতা, ভিন্ন branch predictor, ভিন্ন cache hierarchy। তবু বাইনারিটা চলে। কীভাবে?

উত্তরটা আমাদের toy CPU-তেই লুকিয়ে ছিল, শুধু আমরা তখন নাম দিইনি। যখন আমরা control unit-এর state table বানিয়েছিলাম — opcode 00 মানে ADD, 01 মানে LOAD — তখন আমরা আসলে একটা চুক্তি লিখেছিলাম। যে-ই এই চুক্তি মেনে instruction লিখবে (আমাদের ক্ষেত্রে, আমরা নিজেরাই হাতে), CPU-টা ঠিক সেই আচরণ দেবে। আর যে-ই এই চুক্তি মেনে CPU বানাবে (structural Verilog দিয়ে হোক বা behavioral দিয়ে, single-cycle হোক বা multi-cycle), সেই একই instruction একই ফলাফল দেবে। এই চুক্তিটার নাম Instruction Set Architecture — সংক্ষেপে ISA।

আজ থেকে এই মডিউলের বাকি প্রতিটা লেসন এই একটা ধারণার উপর দাঁড়িয়ে থাকবে, তাই সময় নিয়ে ঠিকভাবে বোঝা দরকার। আমরা দেখব ISA ঠিক কী কী জিনিস নির্দিষ্ট করে (আর কী করে না), কেন এই একটা বিভাজন — চুক্তি বনাম বাস্তবায়ন — আধুনিক কম্পিউটিং শিল্পের সবচেয়ে গুরুত্বপূর্ণ প্রকৌশল সিদ্ধান্তগুলোর একটা, আর বাস্তব ISA-গুলো (x86-64, ARM64, RISC-V) কী ধরনের instruction নিয়ে গঠিত — এই মডিউলের বাকি লেসনগুলোর জন্য একটা মানচিত্র হিসেবে।

মূল ধারণা

ISA-র আনুষ্ঠানিক সংজ্ঞা

Instruction Set Architecture (ISA) হলো একটা সম্পূর্ণ, নির্ভুল, লিখিত বর্ণনা যা বলে দেয় software আর hardware-এর মধ্যে কী কী communication সম্ভব — কিন্তু hardware সেটা কীভাবে বাস্তবায়ন করবে সে বিষয়ে সম্পূর্ণ নীরব। এটা একটা ছয়-অংশের চুক্তি:

  1. Instruction set — কোন কোন operation বৈধ (ADD, SUB, LOAD, branch, …), প্রতিটার bit-level encoding, আর প্রতিটার নির্ভুল semantics (ঠিক কী ঘটবে)।
  2. Register model — কতগুলো general-purpose register আছে, প্রতিটা কত bit চওড়া, আর কোনো বিশেষ-উদ্দেশ্য register (PC, SP) আছে কি না। (পরের লেসনে বিস্তারিত।)
  3. Memory model — address space কত বড়, byte-addressable না word-addressable, endianness কী, memory আর register-এর মধ্যে data কীভাবে আসা-যাওয়া করে।
  4. Addressing modes — একটা instruction তার operand কোথায় খুঁজে পাবে সেটা বলার কতগুলো উপায় আছে (সরাসরি register-এ, না register+offset-এ, না memory address-এ)।
  5. Exception/interrupt model — ভুল হলে (division by zero, invalid instruction) বা বাইরের ঘটনায় (timer, keyboard) কী ঘটে।
  6. I/O model — CPU বাইরের device-এর সাথে কীভাবে কথা বলে।

লক্ষ্য করুন — এই ছয়টার একটাতেও কোনো গেট, কোনো wire, কোনো transistor উল্লেখ নেই। ISA হলো একটা software-এর দৃষ্টিকোণ থেকে CPU-র সম্পূর্ণ বর্ণনা — যেন CPU-টা একটা কালো বাক্স, আর আপনি শুধু তার বাইরের আচরণ জানেন।

ISA বনাম Microarchitecture — এই মডিউলের সবচেয়ে গুরুত্বপূর্ণ বিভাজন

এই দুইটা শব্দ গুলিয়ে ফেলাটাই সবচেয়ে সাধারণ ভুল, তাই এখনই স্পষ্ট সংজ্ঞা দেওয়া যাক।

ISAMicroarchitecture
প্রশ্নকী করা যায়?কীভাবে করা হয়?
দৃষ্টিকোণSoftware/compiler-এর দৃষ্টিতেHardware ডিজাইনারের দৃষ্টিতে
উদাহরণADD instruction দুইটা register যোগ করে তৃতীয়টায় রাখে”“সেই যোগটা single-cycle-এ হবে, না ৫-stage pipeline-এ, না out-of-order-এ”
বদলায় কত ঘন ঘনদশকে হয়তো একবার (major বাড়ে extension দিয়ে)প্রতি ১–২ বছরে (নতুন CPU generation)
উদাহরণ (x86-64)x86-64 ISA — ২০০৩ থেকে মূলত স্থিতিশীলPentium 4, Core 2, Sandy Bridge, Skylake, Alder Lake, … — প্রতিটা আলাদা microarchitecture, একই ISA
আমাদের toy CPU-তেopcode 00=ADD টেবিল, register semanticssingle-cycle datapath + ৫-state control FSM — এটাই microarchitecture

আমাদের toy CPU-র নিজের উদাহরণ দিয়েই এটা সবচেয়ে স্পষ্ট হয়। গত লেসনের control_unit module-এ opcode 00 মানে ADD — এটা ISA-র অংশ। কিন্তু সেই ADD operation ঠিক কোন state-এ, কতগুলো clock cycle-এ ঘটবে (single-cycle না multi-cycle), datapath-এ কোন bus কোন MUX দিয়ে route হবে — এসব microarchitecture-এর অংশ। আমরা ইচ্ছা করলে ঠিক একই ISA (একই ৪টা instruction, একই semantics) সম্পূর্ণ ভিন্ন একটা multi-cycle বা pipelined datapath দিয়ে বাস্তবায়ন করতে পারতাম — আর কোনো প্রোগ্রাম (আমাদের হাতে-লেখা instruction sequence) এক bit-ও না বদলে দুই ক্ষেত্রেই একই ফলাফল দিত।

এইটাই সেই জাদু যেটা আপনার ২০ বছরের পুরোনো .exe-কে আজকের CPU-তে চালায়। Intel প্রতি বছর microarchitecture সম্পূর্ণ নতুন করে ডিজাইন করে — pipeline গভীরতা বদলায়, branch predictor বদলায়, cache আকার বদলায়, এমনকি ভেতরে instruction-কে কীভাবে ভাঙা হয় (আগামী লেসনে দেখবেন) সেটাও বদলায়। কিন্তু x86-64 ISA-টা মোটামুটি স্থির থাকে — সেটাই সেই কালো-বাক্সের বাইরের চুক্তি, যেটার উপর কম্পাইলার আর প্রোগ্রামাররা নির্ভর করে।

একই ISA — বহু সম্ভাব্য microarchitecture, সব একই প্রোগ্রামকে একই ফলাফল দেয়
  1. প্রোগ্রাম (compiled binary)একটা নির্দিষ্ট instruction sequence, ISA অনুযায়ী লেখা
  2. ISA — যা এই instruction-গুলোর মানে নির্ধারণ করেfixed contract — opcode, register model, semantics
  3. Microarchitecture A — single-cycleগত মডিউলের toy CPU-র মতো — সরল, ধীর, কম hardware
  4. Microarchitecture B — pipelinedএকাধিক instruction একসাথে বিভিন্ন stage-এ — দ্রুত, বেশি hardware (লেসন ১০)
  5. Microarchitecture C — superscalar/out-of-orderএকসাথে একাধিক instruction issue, ক্রম পুনর্বিন্যাস — আরও দ্রুত, আরও জটিল (লেসন ১৩+)
  6. তিনটা microarchitecture-ই — একই final register/memory stateISA-র নিশ্চয়তা: ফলাফল একই, শুধু গতি ভিন্ন

Instruction-এর চারটা প্রধান শ্রেণি — এই মডিউলের মানচিত্র

যেকোনো বাস্তব ISA-র শত শত instruction থাকলেও, প্রায় সবগুলোই চারটা মৌলিক শ্রেণির একটাতে পড়ে। এই শ্রেণিবিভাগটা মনে রাখলে নতুন কোনো ISA দেখলেও দ্রুত orient করতে পারবেন — এই মডিউলের পরের লেসনগুলোতে বারবার এই চারটা নামই ফিরে আসবে।

১. Data movement instruction — register আর memory-র মধ্যে, বা register-থেকে-register data সরানো। কোনো গণনা হয় না, শুধু data-র অবস্থান বদলায়। উদাহরণ: x86-এর MOV, RISC-V-এর LW/SW (load word/store word), ARM-এর LDR/STR। আমাদের toy CPU-র LOAD/STORE এই শ্রেণির।

২. Arithmetic/logic instruction — ALU দিয়ে গণনা: যোগ, বিয়োগ, AND, OR, shift, comparison। লেসন ৪-৮-এর (Level 2) ALU-টাই এখানে সরাসরি কাজে লাগে। উদাহরণ: ADD, SUB, AND, SHL। আমাদের toy CPU-র ADD/SUB এখানে।

৩. Control flow instruction — পরবর্তী instruction কোনটা হবে সেটা normal sequential ক্রম (PC+1) থেকে বদলে দেয়: unconditional jump, conditional branch (if/while-এর ভিত্তি), function call/return। পরের লেসনে PC-র বিস্তারিত আলোচনায় এই শ্রেণিই কেন্দ্রে থাকবে। আমাদের toy CPU-তে এই শ্রেণির কোনো instruction ছিল না — সেটাই কারণ কেন সেটা একটা “সত্যিকারের প্রোগ্রামযোগ্য কম্পিউটার” বলা ঠিক না, শুধু একটা fixed-sequence calculator। branch ছাড়া loop/if সম্ভব না।

৪. I/O instruction — CPU আর বাইরের জগতের (keyboard, disk, network card) মধ্যে communication। কিছু ISA-তে (x86) আলাদা IN/OUT instruction আছে; অন্যদের (RISC-V, ARM) কোনো আলাদা I/O instruction নেই — memory-mapped I/O ব্যবহার করে, মানে device-কে memory address-এর মতো address করে সাধারণ LOAD/STORE দিয়েই access করা হয় (Level 4-এ device driver আলোচনায় বিস্তারিত)।

শ্রেণিtoy CPU-তে ছিল?বাস্তব ISA-তে উদাহরণএই মডিউলে বিস্তারিত
Data movementহ্যাঁ (LOAD, STORE)MOV, LW/SW, LDR/STRলেসন ৩ (registers/PC), addressing modes লেসন
Arithmetic/logicহ্যাঁ (ADD, SUB)ADD, AND, SHL, CMPলেসন ৩-এর flags অংশ
Control flowনাJMP, BEQ, CALL, RETলেসন ৩ (PC), fetch-decode-execute লেসন
I/OনাIN/OUT, memory-mapped I/Oপরবর্তী লেসনে সংক্ষেপে, বিস্তারিত Level 4
চারটা instruction শ্রেণি — আমাদের toy CPU কোনগুলো cover করেছিল, বাকিগুলো এই মডিউলে কোথায় আসবে।

গত মডিউলের toy ISA বনাম বাস্তব ISA — স্কেলটা অনুভব করা

সংখ্যাটা একটু দেখা যাক, কারণ এই মডিউল জুড়ে “toy” আর “real” ISA-র মধ্যে তুলনা বারবার আসবে।

Toy CPU (Level 2)RISC-V RV32I (base)x86-64 (base + সব extension)
Instruction সংখ্যা৪৭কয়েকশো base, extension (SSE/AVX/AVX-512) সহ কয়েক হাজার opcode
Opcode বিট২ bit৭ bit (base opcode field)১ থেকে বহু byte, prefix-নির্ভর (পরের লেসনে বিস্তারিত)
Register সংখ্যা(উদাহরণভেদে) ২–৪টা৩২টা (x0x31), ৩২-বিট১৬টা, ৬৪-বিট (পরের লেসনে বিস্তারিত)
Control flow instructionনেই৬টা branch + jumpডজনখানেক conditional jump variant
স্পেসিফিকেশনের আকারএক পাতার টেবিল~১৪৫ পাতা (Unprivileged spec)কয়েক হাজার পাতা (Intel SDM, বহু ভলিউম)

এই টেবিলটাই বলে দেয় কেন এই মডিউল দরকার। toy CPU-টা শেখানোর জন্য যথেষ্ট ছোট আর সম্পূর্ণ ছিল — একদিনে পুরোটা বোঝা যায়। কিন্তু একটা বাস্তব ISA বোঝার জন্য আলাদা vocabulary, আলাদা design trade-off, আর আলাদা ইতিহাস জানা লাগে — সেটাই সামনের লেসনগুলোর কাজ।

ভেতরে কী ঘটছে

ISA স্পেসিফিকেশন থেকে সিলিকন পর্যন্ত — একটা instruction-এর সম্পূর্ণ যাত্রা

ISA একটা document — কোনো circuit না। তাহলে সেই document থেকে কীভাবে একটা কাজ-করা CPU বেরিয়ে আসে? এটাই এই সেকশনের বিষয়, আর এখানে গত মডিউলের প্রতিটা লেসন এক জায়গায় জড়ো হয়।

ISA spec থেকে transistor পর্যন্ত — একটা 'ADD' instruction-এর জন্ম
  1. ISA specification (document)"ADD Rd, Rs, Rt: Rd ← Rs + Rt" — শুধু ইংরেজি/pseudocode, কোনো circuit না
  2. Microarchitecture designইঞ্জিনিয়ার ঠিক করেন কতগুলো pipeline stage, কী ধরনের ALU, কোন state machine — গত মডিউলের datapath-and-control-unit লেসনের কাজ
  3. RTL — Register Transfer Level (HDL কোড)সেই ডিজাইন Verilog-এ লেখা — গত লেসনের control_unit/alu module-গুলোর মতো
  4. SynthesisYosys/Vivado — RTL থেকে gate-level netlist (গত লেসনের LayerTrace, এখানে পুনরাবৃত্তি)
  5. Standard cell / transistorপ্রতিটা gate কয়েক ডজন transistor — Level 2-এর একদম শুরুর বিষয়
  6. Fabricationসিলিকনে খোদাই — এই পুরো curriculum-এর বাইরের একটা সম্পূর্ণ শিল্প

লক্ষ্য করুন — উপরের প্রতিটা স্তর আলাদা মানুষ, আলাদা দল, এমনকি আলাদা কোম্পানি সামলাতে পারে। ISA লেখেন architect (RISC-V Foundation-এর মতো একটা কমিটি, বা ARM-এর ইন-হাউস টিম, বা Intel-এর ইন-হাউস টিম)। Microarchitecture ডিজাইন করেন CPU design team (Intel, AMD, Apple, Qualcomm — প্রত্যেকে নিজের নিজের team, এমনকি একই ISA-র জন্য)। RTL লেখেন hardware engineer। Synthesis/fabrication সামলায় আরেকটা সম্পূর্ণ শিল্প (TSMC, Samsung Foundry, Intel Foundry)। ISA-টাই সেই একমাত্র জিনিস যা এই সবাইকে সংযুক্ত রাখে — architect যা লেখেন, বাকি সবাই তা মেনে চলেন, কিন্তু কেউ কারো ভেতরের সিদ্ধান্তে হস্তক্ষেপ করেন না।

কেন এই বিভাজনটাই compiler-কে সম্ভব করে

Compiler (বা আপনি নিজে যখন assembly লেখেন) শুধু ISA স্পেক পড়েই সঠিক প্রোগ্রাম লিখতে পারেন — কোনো microarchitecture জ্ঞান ছাড়াই। এটা একটা বিশাল practical সুবিধা।

ধরুন একটা কম্পাইলার একটা add instruction emit করছে। কম্পাইলারকে জানতে হবে না:

  • ALU-টা কত bit চওড়া internal-এ (carry-lookahead না ripple-carry)
  • কতগুলো pipeline stage আছে
  • branch predictor কীভাবে কাজ করে
  • cache-এর associativity কত

কম্পাইলারকে শুধু জানতে হবে: ADD opcode কী, কোন bit-এ কোন operand encode হয়, আর ফলাফল destination register-এ ঠিক কী মান বসবে। এইটুকু জানলেই একটা সঠিক প্রোগ্রাম লেখা সম্ভব — সেটা যেই CPU-তেই চলুক না কেন, যতক্ষণ সেই CPU একই ISA implement করে।

উদাহরণ

গত মডিউলের toy ISA — একটা আনুষ্ঠানিক পাঠ

গত লেসনের control_unit আর alu module থেকে আমরা toy ISA-টা পুনর্গঠন করতে পারি — এবার একটা প্রকৃত ISA স্পেকের ভাষায়:

Toy ISA — Instruction Format (16-bit)
┌──────────┬────────────┬────────────┬────────────┐
│ opcode   │  Rd (2 bit)│  Rs (2 bit)│  Rt (2 bit) │
│ (2 bit)  │            │            │  বা immediate │
└──────────┴────────────┴────────────┴────────────┘

opcode  mnemonic  semantics
00      ADD       Rd ← Rs + Rt
01      LOAD      Rd ← Mem[Rs + offset]
10      STORE     Mem[Rs + offset] ← Rt
11      SUB       Rd ← Rs - Rt

এইটুকুই — পুরো ISA একটা স্ক্রিনে আঁটে। এখন RISC-V RV32I-র একটা ছোট অংশ পাশে রাখা যাক, যাতে স্কেলটা concrete মনে হয়:

RISC-V RV32I — একটা ছোট নমুনা (৪৭টার মধ্যে ৬টা)
opcode      mnemonic     semantics
0110011     ADD          rd ← rs1 + rs2
0110011     SUB          rd ← rs1 - rs2      (funct7 দিয়ে ADD থেকে আলাদা)
0000011     LW           rd ← Mem[rs1 + imm]
0100011     SW           Mem[rs1 + imm] ← rs2
1100011     BEQ          if (rs1 == rs2) PC ← PC + imm
1101111     JAL          rd ← PC+4; PC ← PC + imm   (function call)

লক্ষ্য করুন — RISC-V-র প্রথম চারটা লাইন গঠনগতভাবে আমাদের toy ISA-র সাথে প্রায় হুবহু মেলে (ADD, SUB, LWLOAD, SWSTORE)। পার্থক্য শুধু encoding-এর বিস্তারিত (কোন bit কোথায়) আর register সংখ্যা (৩২ বনাম আমাদের ২–৪টা)। কিন্তু শেষ দুইটা লাইন — BEQ (conditional branch) আর JAL (function call) — এমন কিছু যা আমাদের toy ISA-তে ছিলই না। এই control-flow শ্রেণিটাই পরের লেসনে PC-র আলোচনায় কেন্দ্রে থাকবে।

একটা প্রোগ্রাম, দুইটা ISA — একই কাজ, ভিন্ন instruction

একই সহজ কাজ — a = b + c — কল্পনা করুন দুইটা সম্পূর্ণ ভিন্ন ISA-তে কম্পাইল হচ্ছে। কম্পাইলার একই source code পড়ছে, কিন্তু ISA অনুযায়ী সম্পূর্ণ ভিন্ন instruction sequence emit করছে:

RISC-V (load-store architecture — memory-তে সরাসরি arithmetic করা যায় না)
    lw   t0, 0(b_addr)     # t0 ← b
    lw   t1, 0(c_addr)     # t1 ← c
    add  t2, t0, t1        # t2 ← t0 + t1
    sw   t2, 0(a_addr)     # a ← t2

x86-64 (memory operand সরাসরি arithmetic-এ ব্যবহার করা যায়)
    mov  eax, [b_addr]     # eax ← b
    add  eax, [c_addr]     # eax ← eax + c   (একই instruction-এ memory read + add!)
    mov  [a_addr], eax     # a ← eax

RISC-V-তে ৪টা instruction লাগল, x86-64-তে ৩টা — কারণ x86-64-র ADD instruction সরাসরি memory থেকে একটা operand পড়তে পারে, RISC-V-র পারে না (RISC-V-কে আগে LOAD দিয়ে register-এ আনতে হয়)। এই পার্থক্যটাই পরের লেসনের মূল বিষয় — RISC বনাম CISC দর্শন। এখন শুধু লক্ষ্য করুন: একই উচ্চ-স্তরের বাক্য, সম্পূর্ণ ভিন্ন instruction — কারণ প্রতিটা ISA তার নিজস্ব vocabulary নির্ধারণ করে, আর কম্পাইলার সেই vocabulary মেনেই কথা বলতে বাধ্য।

নিজে চালিয়ে দেখুন

EXPERIMENT

আপনার নিজের কম্পাইলার আসলে কোন ISA-তে কথা বলছে দেখুন

Linux/macOS/WSL (objdump), অথবা যেকোনো OS-এ ব্রাউজারে (Compiler Explorer)· ২০ মিনিট

অংশ ১ — নিজের মেশিনে (Linux/macOS/WSL, GCC বা Clang লাগবে):

একটা ছোট C ফাইল লিখুন:

// add.c
int add(int b, int c) {
    return b + c;
}

দুইটা ভিন্ন optimization level দিয়ে কম্পাইল করুন, তারপর disassemble করুন:

gcc -O0 -c add.c -o add_O0.o
gcc -O2 -c add.c -o add_O2.o
objdump -d --no-show-raw-insn add_O0.o
objdump -d --no-show-raw-insn add_O2.o

সাধারণ আউটপুট (-O0, optimization বন্ধ — বেশি, “আক্ষরিক” instruction):

0000000000000000 <add>:
   0: push   %rbp
   1: mov    %rsp,%rbp
   4: mov    %edi,-0x4(%rbp)
   7: mov    %esi,-0x8(%rbp)
   a: mov    -0x4(%rbp),%edx
   d: mov    -0x8(%rbp),%eax
  10: add    %edx,%eax
  12: pop    %rbp
  13: ret

আর -O2 (optimization চালু):

0000000000000000 <add>:
   0: lea    (%rdi,%rsi,1),%eax
   3: ret

দুইটাই একই C ফাংশনের সঠিক অনুবাদ, দুইটাই বৈধ x86-64 instruction — কিন্তু সম্পূর্ণ ভিন্ন instruction sequence। -O0 প্রতিটা variable-কে stack-এ রাখে, ধাপে ধাপে load/store/add করে। -O2 কম্পাইলার বুঝে গেছে পুরো কাজটাই একটা lea (load effective address — সাধারণত address গণনার জন্য, কিন্তু এখানে চতুরভাবে addition-এর জন্য ব্যবহৃত) দিয়ে এক লাইনে করা যায়। কম্পাইলার instruction বাছাইয়ের স্বাধীনতা পেয়েছে — কিন্তু ISA-র সীমার ভেতরে থেকেই। কোনো optimization level কখনো এমন কোনো instruction emit করবে না যা x86-64 ISA-তে নেই।

নিজে যাচাই করুন: objdump -d -এর আউটপুটে প্রতিটা instruction mnemonic (mov, add, push, pop, lea, ret) কে এই লেসনের চারটা শ্রেণির (data movement / arithmetic / control flow / I/O) কোনটায় ফেলবেন লিখুন। (ret — control flow; mov, lea, push, pop — data movement; add — arithmetic।)

অংশ ২ — কোনো ইনস্টল ছাড়াই (Compiler Explorer):

godbolt.org খুলুন, একই add ফাংশন পেস্ট করুন বাম প্যানে। ডান প্যানে compiler dropdown থেকে একে একে বেছে দেখুন:

  1. x86-64 gcc — উপরের মতো x86-64 instruction
  2. RISC-V (32-bit) gcc — সম্পূর্ণ ভিন্ন mnemonic (add a0, a0, a1; ret)
  3. ARM64 gcc — আবার ভিন্ন (add w0, w0, w1; ret)

একই C source code, একই semantics, তিনটা সম্পূর্ণ ভিন্ন ISA-তে অনুবাদ। এটাই এই লেসনের কেন্দ্রীয় দাবির প্রত্যক্ষ প্রমাণ: কম্পাইলার একটা নির্দিষ্ট ISA-কে target করে, আর সেই target বদলালে আউটপুট সম্পূর্ণ বদলে যায় — যদিও input অভিন্ন। লক্ষ্য করুন RISC-V আর ARM64-র আউটপুট এত ছোট আর একরকম দেখতে (add, ret — মাত্র দুইটা instruction, কোনো lea trick নেই) — এটাই পরের লেসনের preview।

এটা কী প্রমাণ করে

Compiler optimization level বা এমনকি সম্পূর্ণ ভিন্ন compiler (GCC বনাম Clang) ব্যবহার করলেও, উৎপন্ন instruction সবসময় একই ISA-র (x86-64) বৈধ instruction — কারণ ISA-টাই সেই স্থির target, compiler-এর নিজস্ব সিদ্ধান্ত (কোন instruction বাছবে) না।

নিজে বানান

BUILD IT

Toy ISA-র একটা আনুষ্ঠানিক স্পেসিফিকেশন লিখুন

Markdown / pseudocode · ●●○○○
  1. গত মডিউলের toy CPU (control_unit + alu, ৪টা instruction) থেকে প্রতিটা instruction-এর সম্পূর্ণ তালিকা বের করুন
  2. প্রতিটা instruction-এর জন্য একটা encoding diagram আঁকুন — কোন bit-এ opcode, কোন bit-এ কোন register
  3. প্রতিটা instruction-এর semantics pseudocode-এ লিখুন (register-transfer notation ব্যবহার করে, যেমন Rd ← Rs + Rt)
  4. একটা register model section লিখুন — কতগুলো register, কত bit চওড়া
  5. কমপক্ষে একটা নতুন instruction প্রস্তাব করুন (যেমন AND বা একটা conditional branch) আর তার জন্যও সম্পূর্ণ entry লিখুন, ঠিক বাকিগুলোর মতো ফরম্যাটে

বাস্তব ISA manual (RISC-V spec, Intel SDM, ARM ARM) সবগুলোই মূলত একই তিনটা জিনিস প্রতিটা instruction-এর জন্য বলে: encoding (bit-এ কোথায় কী), syntax (assembly-তে কীভাবে লেখা হয়), আর semantics (কী ঘটে)। আপনার কাজ toy CPU-র জন্য ঠিক এই ধরনের একটা মিনি-manual লেখা — এটাই একটা ISA স্পেসিফিকেশন লেখার প্রথম বাস্তব অভিজ্ঞতা।

Encoding diagram-এর ফরম্যাট (RISC-V spec-এর style অনুসরণ করে — এটাই ইন্ডাস্ট্রি-স্ট্যান্ডার্ড উপস্থাপনা):

 15                    14 13        12 11        10 9          8 7            0
┌───────────────────────┬─────────────┬─────────────┬──────────────────────────┐
│        opcode          │     Rd      │     Rs      │           Rt / imm       │
│        (2 bit)         │   (2 bit)   │   (2 bit)   │         (varies)         │
└───────────────────────┴─────────────┴─────────────┴──────────────────────────┘

একটা সম্পূর্ণ entry-র নমুনা (ADD-এর জন্য, আপনি বাকি তিনটার জন্য একই প্যাটার্ন অনুসরণ করবেন):

── ADD ──────────────────────────────────────────────
Encoding : opcode = 00
Syntax   : ADD Rd, Rs, Rt
Semantics: Rd ← Rs + Rt
Flags    : কোনটা প্রভাবিত হয় না (toy ISA-তে flag register নেই)
Notes    : Overflow silently wrap করে — কোনো detection নেই এই toy ISA-তে

নতুন instruction প্রস্তাব করার সময় ভাবুন:

  • আপনার নতুন instruction-এর জন্য কি opcode field যথেষ্ট বড়? (২ bit দিয়ে সর্বোচ্চ ৪টা opcode encode করা যায় — আপনার ৪টা ইতিমধ্যেই ব্যবহৃত! একটা নতুন instruction যোগ করতে হলে opcode field-টাই বড় করতে হবে — এটাই বাস্তব ISA-তেও একটা চিরস্থায়ী সমস্যা, “realworld” অংশে দেখবেন।)
  • এই instruction কি existing datapath দিয়েই বাস্তবায়ন করা যায়, নাকি নতুন hardware লাগবে? (হদ্‌ল-verilog-intro লেসনের SUB-যোগের Q3 উত্তরটা আরেকবার দেখুন — এটাই ঠিক সেই বিশ্লেষণ, এবার conditional branch-এর জন্য করুন।)

নিজে বাড়ান: আপনার লেখা স্পেকটা একজন সহপাঠীকে দিন (বা কয়েকদিন পর নিজেই আবার পড়ুন) — শুধু সেই ডকুমেন্ট পড়ে, কোনো ব্যাখ্যা ছাড়া, তিনি কি সঠিকভাবে হাতে একটা প্রোগ্রাম “চালাতে” (simulate করতে) পারবেন? যদি পারেন, আপনার spec যথেষ্ট নির্ভুল — এটাই আসল পরীক্ষা যেটা দিয়ে RISC-V Foundation তাদের নিজস্ব spec যাচাই করে (conformance test suite)।

বাস্তব সিস্টেমে

ISA যেখানে চুক্তি হিসেবে কাজ করে

x86-64-এর ২০ বছরের backward compatibility। ২০০৩ সালে AMD প্রথম x86-64 (তখনকার নাম AMD64) চালু করে। আজকের ২০২৬ সালের একটা CPU-ও সেই একই ২০০৩-এর ISA-তে লেখা বাইনারি চালাতে পারে — এমনকি তার চেয়েও পুরোনো, ১৯৭৮ সালের 8086-এর ১৬-বিট real-mode ISA-র একটা subset-ও (boot process-এর শুরুতে আজও ব্যবহৃত হয়)। মাঝে মাইক্রোআর্কিটেকচার বদলেছে বহুবার — কিন্তু ISA layer-টা প্রায় অপরিবর্তিত।

Apple-এর x86 থেকে ARM64-এ স্থানান্তর (২০২০)। এখানে উল্টো ঘটনা — Apple সম্পূর্ণ ISA বদলে ফেলেছিল (Intel x86-64 থেকে নিজস্ব ARM64-ভিত্তিক Apple Silicon-এ)। পুরোনো x86-64 বাইনারি সরাসরি চলত না, কারণ ISA-টাই আলাদা। সমাধান ছিল Rosetta 2 — একটা binary translator, যেটা পুরোনো x86-64 instruction-কে নতুন ARM64 instruction-এ অনুবাদ করে (মূলত install-এর সময়, ahead-of-time)। এটা প্রমাণ করে ISA বদলানো কতটা costly — পুরো software ecosystem-এর জন্য একটা translation layer লাগে।

RISC-V-র open ISA মডেল। x86-64 (Intel/AMD মালিকানাধীন) আর ARM (ARM Holdings-এর license প্রয়োজন) থেকে ভিন্ন, RISC-V সম্পূর্ণ open, রয়্যালটি-ফ্রি ISA স্পেসিফিকেশন — যে কেউ বিনামূল্যে নিজের CPU বানাতে পারে যেটা RISC-V ISA implement করে, কোনো license fee ছাড়াই। এই কারণেই এই platform-এরই পরের প্রজেক্ট (CPU Emulator) RISC-V RV32I বেছে নিয়েছে — spec সম্পূর্ণ পাবলিক, ছোট, আর শেখার জন্য ডিজাইন করা।

WebAssembly — একটা “virtual” ISA। ISA ধারণাটা শুধু physical সিলিকনেই সীমাবদ্ধ না। WebAssembly (Wasm) একটা ISA-র মতোই একটা instruction set define করে — কিন্তু কোনো physical CPU সরাসরি এটা execute করে না। ব্রাউজার একটা JIT compiler দিয়ে Wasm instruction-কে আসল host CPU-র (x86-64/ARM64) ISA-তে অনুবাদ করে, রানটাইমে। এটাই “portable compile target” ধারণা — এক জায়গায় কম্পাইল করে যেকোনো ISA-তে চালানো।

Java bytecode / JVM — আরেকটা virtual ISA-র উদাহরণ। JVM bytecode-এরও একটা নির্দিষ্ট instruction set, encoding, semantics আছে — ঠিক physical ISA-র মতোই একটা চুক্তি, শুধু বাস্তবায়নকারীটা hardware না, একটা software interpreter/JIT (Level 5-এ compiler/runtime আলোচনায় বিস্তারিত)।

Game console generation-এ ISA পরিবর্তনের ঝক্কি। PlayStation 3-এর Cell processor-এর PowerPC-ভিত্তিক ISA থেকে PlayStation 4-এর x86-64-এ স্থানান্তরের সময় Sony-কে backward compatibility নিয়ে ভুগতে হয়েছিল — পুরোনো গেম সরাসরি চলত না, emulation লাগত। এটা দেখায় কনজিউমার প্রোডাক্টেও ISA পরিবর্তনের বাস্তব খরচ কতটা।

GPU-র ISA — সম্পূর্ণ ভিন্ন execution model, তবু একই নীতি। NVIDIA-র PTX (Parallel Thread Execution) একটা GPU-র জন্য ISA-র মতো একটা intermediate representation — nvcc কম্পাইলার CUDA কোডকে PTX-এ কম্পাইল করে, তারপর driver PTX-কে নির্দিষ্ট GPU generation-এর আসল hardware ISA-তে (SASS) অনুবাদ করে রানটাইমে। এখানেও সেই একই বিভাজন — একটা স্থিতিশীল intermediate contract, যার নিচে hardware generation-ভেদে বাস্তবায়ন সম্পূর্ণ বদলাতে পারে। Level 11-এ GPU architecture-এ বিস্তারিত।

যে ভুলগুলো সবাই করে

“ISA-তে যত বেশি instruction, CPU তত দ্রুত।”

Instruction সংখ্যা কোনোভাবেই speed-এর সরাসরি নির্দেশক না — এটা এই লেসনের সবচেয়ে গুরুত্বপূর্ণ সংশোধন, কারণ পরের লেসন (RISC vs CISC) পুরোপুরি এই ভুল ধারণা ভাঙার উপর দাঁড়িয়ে।

ISA শুধু বলে দেয় কী কী operation সম্ভব — কতটা দ্রুত সেটা সম্পূর্ণ নির্ভর করে microarchitecture-এর উপর: pipeline গভীরতা, cache আকার, branch prediction accuracy, clock frequency, transistor প্রযুক্তি (৭nm বনাম ৩nm)। দুইটা CPU একই ISA implement করতে পারে অথচ speed-এ ১০ গুণ পার্থক্য থাকতে পারে — কারণ একটা ভালো, একটা খারাপ microarchitecture।

উল্টোটাও সত্য: একটা ISA-তে কম instruction থাকা মানেই সেটা “দুর্বল” না — পরের লেসনে দেখবেন RISC দর্শনটাই আসলে কম instruction দিয়ে দ্রুততর hardware বানানোর একটা সচেতন কৌশল।

সঠিক নিয়ম: ISA নির্ধারণ করে সম্ভাবনার সীমা (কী কী প্রকাশ করা যায়), microarchitecture নির্ধারণ করে গতি। এই দুইটা প্রশ্ন সম্পূর্ণ স্বাধীন।

“একই ISA implement করা মানেই দুইটা CPU ভেতরে একই রকম।”

Intel আর AMD দুজনেই x86-64 ISA implement করে — একই instruction set, একই register model, একই বাইনারি দুই কোম্পানির CPU-তেই চলে। কিন্তু ভেতরে তাদের microarchitecture সম্পূর্ণ ভিন্ন প্রকৌশল দল, ভিন্ন ডিজাইন সিদ্ধান্ত, ভিন্ন patent-এর অধীনে তৈরি — pipeline গঠন ভিন্ন, cache hierarchy ভিন্ন, branch predictor ভিন্ন।

এটা এই লেসনের মূল দাবিরই একটা corollary: ISA compatibility মানে শুধু “একই সফটওয়্যার দুটোতেই চলবে” — এটা কোনো নিশ্চয়তা দেয় না যে ভেতরের hardware ডিজাইন একই রকম, বা এমনকি একই কোম্পানি থেকে এসেছে।

একটা সরাসরি প্রমাণ: RISC-V ISA implement করা কমপক্ষে ডজনখানেক সম্পূর্ণ ভিন্ন কোম্পানির CPU আজ বাজারে আছে (SiFive, Western Digital, Alibaba T-Head, Espressif, আরও অনেক) — প্রত্যেকের নিজস্ব, স্বাধীনভাবে ডিজাইন করা microarchitecture, কিন্তু সবাই একই ISA স্পেক মেনে চলে বলেই একই বাইনারি চালাতে পারে।

“নতুন CPU generation মানে নতুন ISA শিখতে হবে।”

বেশিরভাগ ক্ষেত্রেই না। Intel/AMD প্রতি বছর নতুন microarchitecture বের করে (Skylake → Ice Lake → Alder Lake → …), কিন্তু x86-64 ISA-র মূল অংশ প্রতিবার বদলায় না — নতুন generation মানে সাধারণত শুধু নতুন optional extension যোগ হওয়া (যেমন নতুন SIMD instruction, AVX-512 প্রজন্মে প্রজন্মে বিস্তৃত হয়েছে), পুরোনো instruction-গুলো একইভাবে কাজ করতে থাকে।

এই extension-গুলো সবসময় backward-compatible ভাবে opt-in — পুরোনো software এই নতুন instruction ব্যবহার না করলে, নতুন CPU-তেও ঠিক পুরোনো মতোই চলে। CPU-র CPUID instruction (x86-64) বা RISC-V-র extension-discovery mechanism দিয়ে software runtime-এ চেক করতে পারে কোন কোন নতুন feature উপলব্ধ, তারপর ঐচ্ছিকভাবে ব্যবহার করে।

যে বিরল ক্ষেত্রে সত্যিই নতুন ISA শিখতে হয়: Apple-এর x86→ARM64 উদাহরণের মতো, যখন পুরো company একটা সম্পূর্ণ ভিন্ন ISA পরিবার-এ স্থানান্তরিত হয়। এটা বিরল, ব্যয়বহুল, আর সাধারণত একটা বড় business সিদ্ধান্তের ফল, “স্বাভাবিক প্রজন্ম-পরিবর্তন” না।

“Compiler-কে CPU-র ভেতরের circuit ঠিক কীভাবে কাজ করে সেটা জানতে হয় সঠিক কোড তৈরি করতে।”

না — এটাই এই লেসনের কেন্দ্রীয় দাবির সরাসরি বিপরীত। Correctness-এর জন্য কম্পাইলারকে শুধু ISA স্পেক (instruction encoding, semantics) জানলেই চলে। কম্পাইলার কখনো জানে না, জানার দরকারও নেই, যে ADD instruction-টা ৫-stage pipeline-এ implement হয়েছে না ১৫-stage-এ, single-cycle ALU দিয়ে না carry-lookahead দিয়ে।

এইজন্যই একটা কম্পাইলার একবার x86-64 target-এর জন্য লেখা হলে, সেটা Intel-এর CPU-তেও কাজ করে, AMD-র CPU-তেও কাজ করে, এমনকি এমন এক ভবিষ্যতের CPU-তেও কাজ করবে যেটা আজ ডিজাইন হয়নি — যতক্ষণ সেই CPU x86-64 ISA স্পেক মেনে চলে।

এই লেসনের আগের একটা Callout-এ যেমন বলা হয়েছে — performance-tuning-এর জন্য (-march=native-এর মতো) microarchitecture-নির্দিষ্ট জ্ঞান কাজে লাগে, কিন্তু সেটা একটা ঐচ্ছিক optimization, মৌলিক correctness-এর শর্ত না।

বুঝেছেন কি না দেখুন

1

“ISA” আর “microarchitecture” — একটা বাক্যে পার্থক্যটা লিখুন, তারপর গত মডিউলের toy CPU থেকে একটা নির্দিষ্ট উদাহরণ দিয়ে দেখান কোনটা কোন শ্রেণিতে পড়ে।

যুক্তি

এক বাক্যে: ISA বলে কী করা যায় (instruction, register, semantics — একটা contract), microarchitecture বলে কীভাবে করা হয় (pipeline, state machine, datapath — একটা বাস্তবায়ন)।

toy CPU-র উদাহরণ:

  • ISA-র অংশ: “opcode 00 মানে ADD, আর ADD Rd, Rs, Rt-এর semantics হলো Rd ← Rs + Rt।” — এটা একটা bit-pattern-থেকে-আচরণ mapping, hardware-এর কোনো বিস্তারিত উল্লেখ নেই।
  • Microarchitecture-এর অংশ: “এই ADD operation single-cycle datapath-এ, একটা নির্দিষ্ট state-এ (S_ADD_EXEC), একটা নির্দিষ্ট ALU circuit দিয়ে, একটা নির্দিষ্ট bus-এর মাধ্যমে ঘটে।” — এটা বলে দেয় ঠিক কীভাবে সেই semantics বাস্তবায়িত হচ্ছে, কতগুলো clock cycle লাগছে, কোন wire দিয়ে data যাচ্ছে।

পরীক্ষা: যদি আমরা একই ISA (একই 00=ADD mapping) রেখে datapath-টা multi-cycle-এ redesign করি (গত লেসনের SUB-যোগের আলোচনায় যেমন ইঙ্গিত ছিল, ভিন্ন state count সম্ভব), ISA-র কোনো অংশ বদলাবে না — শুধু microarchitecture বদলাবে, আর হাতে-লেখা প্রোগ্রাম (instruction sequence) অপরিবর্তিত থেকেও একই ফলাফল দেবে, শুধু হয়তো ভিন্ন গতিতে।

2

Compiler Explorer experiment-এ দেখলেন add.c-এর -O0 কম্পাইলে ৯টা instruction, -O2-এ মাত্র ২টা instruction বেরোয় — দুটোই x86-64। এই দুইটা ভিন্ন instruction sequence কি ভিন্ন ISA ব্যবহার করছে? ব্যাখ্যা করুন।

প্রয়োগ

না, দুইটাই একই ISA (x86-64) ব্যবহার করছে। ISA একটা vocabulary — সেই vocabulary থেকে কোন শব্দ (instruction) বাছবে, কতগুলো বাছবে, কীভাবে সাজাবে, সেটা compiler-এর নিজস্ব সিদ্ধান্ত, ঠিক যেমন একই ভাষায় একই কথা ছোট বাক্যে বা লম্বা বাক্যে বলা যায়।

-O0-এ কম্পাইলার optimization বন্ধ রেখে “আক্ষরিক”, ধাপে-ধাপে অনুবাদ করে — প্রতিটা variable স্বতন্ত্রভাবে stack-এ রাখা, প্রতিটা ধাপ আলাদা instruction। -O2-এ কম্পাইলার বুঝে ফেলে পুরো কাজটা একটা lea instruction দিয়েই সম্পন্ন করা যায় (x86-64 ISA-র lea-র সংজ্ঞা যথেষ্ট নমনীয় যে সেটা একটা addition-এর মতোও ব্যবহার করা যায় — একটা ISA-র “trick”, নতুন instruction না)।

মূল পয়েন্ট: instruction সংখ্যা বা বাছাই বদলাচ্ছে, কিন্তু instruction সেট (কোন vocabulary থেকে বাছা হচ্ছে) বদলাচ্ছে না। উভয় ক্ষেত্রেই কম্পাইলার x86-64 ISA-র নিয়ম মেনে চলছে — শুধু ভিন্ন কৌশল প্রয়োগ করছে।

3

Apple-এর x86-64 থেকে ARM64-এ স্থানান্তরের সময় Rosetta 2 নামের একটা translator লাগল, কিন্তু Intel যখন Skylake থেকে Alder Lake microarchitecture-এ যায়, তখন কোনো translator লাগে না — পুরোনো সফটওয়্যার সরাসরি চলে। পার্থক্যটা কেন?

যুক্তি

পার্থক্যটা ঠিক এই লেসনের কেন্দ্রীয় বিভাজন — ISA বদলেছে কি না।

Skylake → Alder Lake: microarchitecture বদলেছে, ISA বদলায়নি। দুটোই x86-64 ISA implement করে (Alder Lake হয়তো কিছু নতুন optional extension যোগ করেছে, কিন্তু পুরোনো instruction-এর semantics অভিন্ন)। পুরোনো বাইনারি x86-64 instruction দিয়ে লেখা — নতুন CPU সেই একই instruction বোঝে, শুধু হয়তো দ্রুত execute করে (ভালো pipeline, ভালো branch predictor)। কোনো translation দরকার নেই কারণ vocabulary-টাই একই।

x86-64 → ARM64 (Apple-এর transition): সম্পূর্ণ ভিন্ন ISA। ARM64 CPU x86-64 instruction বোঝেই না — এটা একটা সম্পূর্ণ ভিন্ন vocabulary, ভিন্ন opcode encoding, ভিন্ন register model। একটা x86-64 বাইনারি ARM64 CPU-তে সরাসরি চালানো ঠিক তেমন অসম্ভব যেমন একটা বাংলা বই কেউ পড়তে পারবে না যে শুধু ইংরেজি জানে — টেক্সট (bits) হয়তো ভৌত অর্থে “পড়া” যায়, কিন্তু অর্থ (semantics) নেই। তাই একটা translator (Rosetta 2) লাগে, যেটা x86-64 instruction-কে ARM64 instruction-এ রূপান্তর করে — কার্যত একটা compiler, কিন্তু source থেকে না, একটা ISA-র বাইনারি থেকে আরেকটা ISA-র বাইনারিতে।

4

আপনার toy ISA-র opcode field মাত্র ২ bit — তাই সর্বোচ্চ ৪টা instruction encode করা যায়, আর সবগুলো ইতিমধ্যেই ব্যবহৃত (ADD, SUB, LOAD, STORE)। এখন একটা conditional branch instruction (BEQ) যোগ করতে চান। ঠিক কী কী পরিবর্তন লাগবে — শুধু ISA-স্তরে চিন্তা করুন, microarchitecture না।

প্রয়োগ

ISA-স্তরের পরিবর্তন (এই প্রশ্নের সীমা):

১. Opcode field বড় করতে হবে। ২ bit দিয়ে সর্বোচ্চ ৪টা encoding সম্ভব, সবগুলোই দখল। BEQ যোগ করতে opcode field অন্তত ৩ bit করতে হবে (৮টা সম্ভাব্য encoding, ৫টা এখনও ফাঁকা থাকবে ভবিষ্যতের জন্য) — এটাই বাস্তব ISA design-এ একটা চিরস্থায়ী চাপ: opcode space সীমিত, নতুন instruction মানেই encoding-এ জায়গা বের করা।

২. নতুন instruction format দরকার হতে পারে। ADD Rd, Rs, Rt-এর মতো তিনটা register-field দরকার হয় না BEQ-তে — তার বদলে দুইটা register (তুলনার জন্য) আর একটা branch target/offset (কোথায় jump করতে হবে) লাগে। এই offset-টা register field-এর চেয়ে বড় হতে পারে, তাই instruction-এর bit layout-ই আলাদা হতে পারে — বাস্তব ISA-তে (RISC-V, MIPS) একাধিক “instruction format” থাকার এটাই কারণ (R-type, I-type, B-type ইত্যাদি — পরের লেসনগুলোয় বিস্তারিত)।

৩. Semantics সংজ্ঞায়িত করতে হবে PC-র ভাষায়। BEQ Rs, Rt, offset-এর semantics হলো, মোটামুটি: “if (Rs == Rt) PC ← PC + offset, নাহলে PC স্বাভাবিকভাবে বাড়ে।” এখানে প্রথমবার PC কে explicit ভাবে instruction semantics-এর অংশ হতে হচ্ছে — toy ISA-র বাকি চারটা instruction PC নিয়ে কিছু বলেনি (সবসময় sequential ধরে নেওয়া হয়েছে)। এটাই পরের লেসনের বিষয়ের সরাসরি প্রয়োজনীয়তা তৈরি করছে।

(Microarchitecture-স্তরের পরিবর্তন — datapath-এ নতুন adder/mux, control unit-এ নতুন state — এই প্রশ্নের বাইরে, কিন্তু হদ্‌ল-verilog-intro লেসনের Q3-এর একই পদ্ধতিতে বিশ্লেষণযোগ্য।)

5

একজন সহপাঠী দাবি করছে: “RISC-V একটা ভালো ISA কারণ এটাতে মাত্র ৪৭টা instruction — যত কম instruction, ততই ভালো ISA।” এই যুক্তিতে কী সমস্যা আছে?

যুক্তি

এই যুক্তিটা একটা category error করছে — instruction সংখ্যা-কে ISA-র “ভালো”-র মাপকাঠি বানাচ্ছে, যেখানে আসল প্রশ্নটা হওয়া উচিত সেই instruction set দিয়ে কী কী design goal অর্জন করা যাচ্ছে।

RISC-V-র ৪৭টা base instruction একটা সচেতন ডিজাইন সিদ্ধান্তের ফল — সরল, uniform instruction pipeline-friendly, decode করা সহজ, hardware কম জটিল (পরের লেসনে RISC দর্শনের বিস্তারিত)। কিন্তু এর মানে এই না যে “কম instruction = intrinsically ভালো”। একটা ISA-তে যদি প্রয়োজনীয় কোনো operation-এর জন্য কোনো instruction না থাকে, প্রোগ্রামকে অনেকগুলো ছোট instruction দিয়ে সেই কাজ ঘুরপথে করতে হয় — code দীর্ঘ হয়, execute করতে বেশি cycle লাগে (যদিও প্রতিটা cycle দ্রুত হতে পারে)।

x86-64-এর হাজার হাজার opcode থাকার পেছনেও একটা কারণ আছে (কোড ঘনত্ব, ঐতিহাসিক backward compatibility, SIMD-এর মতো বিশেষায়িত কাজ) — পরের লেসনে দেখবেন এটাও একটা বৈধ, ভিন্ন design trade-off, “খারাপ” ISA না।

সঠিক প্রশ্ন: “এই ISA-র design goal কী ছিল, আর সেই goal-এ এটা কতটা সফল?” — শুধু instruction গণনা করা কোনো অর্থবহ তুলনা না। মিসকনসেপশন সেকশনের প্রথম দাবিটাই (instruction সংখ্যা = speed) এই একই ভুলের আরেকটা রূপ।

এরপর কী

পরের প্রশ্ন — একই লক্ষ্য, দুইটা প্রতিদ্বন্দ্বী দর্শন

এই লেসনে আমরা দেখলাম ISA একটা vocabulary — কী কী শব্দ (instruction) আছে, তাদের অর্থ কী। কিন্তু একটা প্রশ্ন এখনও উত্তরহীন রয়ে গেছে, আর সেটাই “ISA বনাম x86-64 উদাহরণে” বারবার উঁকি দিয়েছে: একই কাজ (a = b + c) করার জন্য RISC-V-তে ৪টা instruction লাগল, x86-64-তে ৩টা — কেন এই পার্থক্য? কোনটা “ভালো” ডিজাইন?

এই প্রশ্নের পেছনে আছে computer architecture-এর ইতিহাসের সবচেয়ে বিখ্যাত বিতর্ক — ১৯৭০-এর memory-সীমিত জগৎ, ১৯৮০-র দশকের একাডেমিক গবেষণা যা দেখাল compiler আসলে জটিল instruction ব্যবহারই করে না, আর সেখান থেকে জন্ম নেওয়া দুইটা সম্পূর্ণ ভিন্ন দর্শন — CISC (Complex Instruction Set Computer) আর RISC (Reduced Instruction Set Computer)।

পরের লেসনে আমরা দেখব কেন x86 CISC হিসেবে শুরু হয়েছিল, কেন RISC গবেষণা সেটাকে চ্যালেঞ্জ করেছিল, আর সবচেয়ে চমকপ্রদ মোড়টা — কীভাবে আজকের “CISC” x86-64 চিপ ভেতরে আসলে RISC-এর মতো micro-op-এ ভেঙে কাজ করে, যেন দুই দর্শনই শেষমেশ একে অপরের দিকে হাঁটতে শুরু করেছে।

আরও পড়ুন

  • The RISC-V Instruction Set Manual, Volume I: Unprivileged ISA · একটা আধুনিক ISA স্পেসিফিকেশন আসলে দেখতে কেমন — এই লেসনের 'ISA স্পেক' ধারণার সরাসরি বাস্তব উদাহরণ
  • Computer Organization and Design: RISC-V Edition — David A. Patterson, John L. Hennessy · ISA-কে hardware/software interface হিসেবে দেখা এই বইয়ের কেন্দ্রীয় থিম — এই পুরো মডিউলের spirit এখান থেকে আসছে
  • Intel 64 and IA-32 Architectures Software Developer's Manual · x86-64-এর প্রকৃত, বিশাল ISA spec — 'কয়েক হাজার পাতা'-র claim যাচাই করতে চাইলে এখানেই
  • Compiler Explorer (godbolt.org) · এই লেসনের experiment-এর দ্বিতীয় অংশে ব্যবহৃত — কোনো ইনস্টল ছাড়াই একই C কোড বিভিন্ন ISA-তে compile করে দেখার টুল