Instruction Encoding — একটা instruction কীভাবে বিটে পরিণত হয়
Instruction Encoding
একটা instruction আসলে একটা নির্দিষ্ট প্যাটার্নে সাজানো বিট মাত্র — opcode কোন কাজ, funct3/funct7 কোন variant, rd/rs1/rs2 কোন register, immediate কোন constant, সব ফিক্সড বিট-পজিশনে। RV32I-তে এই প্যাটার্ন সবসময় ৩২ বিট, ছয়টা format-এর একটায় পড়ে; x86-64-তে এই প্যাটার্ন ১ থেকে ১৫ byte পর্যন্ত বদলাতে পারে — আর এই একটা পার্থক্যই pipelining-কে সহজ বা কঠিন করে দেয়।
আগে এটা বুঝি
Digital Logic মডিউলে (Level ২) আমরা দেখেছি একটা CPU আসলে datapath আর control unit-এর সমষ্টি — register file, ALU, memory, MUX, আর একটা FSM যেটা প্রতি cycle-এ ঠিক করে কোন signal সক্রিয় হবে। আর গত দুই লেসনে (Level ৩-এর শুরুতে) আমরা শিখেছি ISA কী, RISC বনাম CISC কী, আর একটা CPU-র register file, PC, stack pointer, flag কীভাবে সাজানো থাকে।
কিন্তু একটা প্রশ্ন এখনো অমীমাংসিত — control unit যখন বলে “এটা একটা
ADD instruction, Rd ← Rs + Rt”, সেটা কীভাবে জানে? Instruction তো
মেমোরিতে বসে আছে নিছক ৩২টা 0/1-এর একটা সারি হিসেবে — কোনো লেবেল
নেই, কোনো মন্তব্য নেই, শুধু বিট। 0x00730E33 — এই একটা সংখ্যা দেখে
control unit কীভাবে বুঝবে এটা add t3, t1, t2 মানে “t3 রেজিস্টারে t1
আর t2-এর যোগফল বসাও”?
উত্তরটাই আজকের লেসনের বিষয়: instruction encoding — mnemonic আর
operand-কে একটা সুনির্দিষ্ট, পূর্ব-নির্ধারিত বিট-প্যাটার্নে রূপান্তর
করার নিয়ম। এই নিয়ম compiler/assembler এক দিকে অনুসরণ করে (মানুষের
লেখা add t3, t1, t2 থেকে বিট বানাতে), আর CPU-র decoder ঠিক উল্টো
দিকে অনুসরণ করে (বিট থেকে “কী করতে হবে” বের করতে) — দুই পক্ষই যদি একই
নিয়ম না মানে, পুরো সিস্টেম ভেঙে পড়বে।
আমরা এই লেসনে RISC-V-এর RV32I ইনস্ট্রাকশন সেট ব্যবহার করব প্রধান উদাহরণ হিসেবে — একটা পরিষ্কারভাবে ডকুমেন্টেড, fixed ৩২-বিট, আধুনিক ও open ISA, আর এই platform-এর নিজস্ব “CPU Emulator” প্রজেক্টও ঠিক এই ISA-কেই টার্গেট করবে। লেসনের শেষে x86-64-এর variable-length এনকোডিং সংক্ষেপে দেখব, যাতে আগের লেসনের RISC/CISC আলোচনাটা এখন concrete বিট দিয়ে প্রমাণিত হয়।
মূল ধারণা
Instruction encoding মানে কী, আসলে
একটা instruction encoding হলো একটা bijective mapping — প্রতিটা বৈধ (mnemonic, operand) জোড়ার জন্য ঠিক একটা বিট-প্যাটার্ন, আর প্রতিটা বৈধ বিট-প্যাটার্নের জন্য ঠিক একটা (mnemonic, operand) জোড়া। এই mapping-টা compiler/assembler এবং CPU hardware-এর মধ্যেকার contract — একই সেই ISA ধারণা যেটা আগের লেসনে প্রতিষ্ঠিত হয়েছে, শুধু এখন সেই contract-এর সবচেয়ে concrete স্তরে নেমে এসেছি: বিট।
RV32I-এর ছয়টা instruction format
RV32I-তে প্রতিটা instruction ঠিক ৩২ বিট — কোনো ব্যতিক্রম নেই। কিন্তু ৩২ বিটের ভেতরে ফিল্ড কীভাবে সাজানো থাকে তা instruction-এর ধরন অনুযায়ী ছয় রকম format-এ ভাগ হয়:
| Format | ব্যবহার | উদাহরণ instruction |
|---|---|---|
| R-type | Register-register operation | add, sub, and, or, xor, sll, srl, sra, slt, sltu |
| I-type | Register + immediate; load; jump-register | addi, andi, slti, lw, lb, jalr |
| S-type | Store (register → memory) | sw, sh, sb |
| B-type | Conditional branch | beq, bne, blt, bge, bltu, bgeu |
| U-type | ২০-বিট upper immediate | lui, auipc |
| J-type | Unconditional jump | jal |
R-type — register-register operation
R-type-এ কোনো immediate নেই — তিনটা register (দুইটা source, একটা destination) আর একটা operation নির্বাচন।
বিট: 31 25 24 20 19 15 14 12 11 7 6 0
┌────────────┬────────┬────────┬──────┬────────┬────────┐
│ funct7 │ rs2 │ rs1 │funct3│ rd │ opcode │
│ (7 bit) │(5 bit) │(5 bit) │(3bit)│ (5 bit)│ (7 bit)│
└────────────┴────────┴────────┴──────┴────────┴────────┘| ফিল্ড | বিট | প্রস্থ | কী বহন করে |
|---|---|---|---|
opcode | [6:0] | ৭ | কোন broad category — R-type-এর জন্য সবসময় 0110011 |
rd | [11:7] | ৫ | Destination register নম্বর — 0-31, তাই x0…x31 (৫ বিট = 2^5 = 32, ঠিক register file-এর আকারের সাথে মেলে) |
funct3 | [14:12] | ৩ | কোন operation (opcode-এর নিচে ৮টা variant পর্যন্ত) |
rs1 | [19:15] | ৫ | প্রথম source register |
rs2 | [24:20] | ৫ | দ্বিতীয় source register |
funct7 | [31:25] | ৭ | আরও সূক্ষ্ম variant (মূলত ADD বনাম SUB, SRL বনাম SRA আলাদা করতে) |
I-type — immediate ও load
I-type register-immediate operation (addi), load (lw), এবং
jalr-এ ব্যবহৃত হয়।
বিট: 31 20 19 15 14 12 11 7 6 0
┌─────────────────────┬────────┬──────┬────────┬────────┐
│ imm[11:0] │ rs1 │funct3│ rd │ opcode │
│ (12 bit, signed) │(5 bit) │(3bit)│ (5 bit)│ (7 bit)│
└─────────────────────┴────────┴──────┴────────┴────────┘imm[11:0] একটা 12-বিট signed সংখ্যা — CPU এটাকে ৩২ বিটে
sign-extend করে ব্যবহার করে (bit 11 কে বাকি সবগুলো উঁচু বিটে কপি
করে — Level ১-এর two’s complement sign-extension, হুবহু একই নিয়ম)।
তাই একটা I-type instruction-এর immediate range -2048 থেকে 2047
পর্যন্ত।
S-type — store (register থেকে memory)
sw Rs2, offset(Rs1) — মেমোরিতে লেখা, তাই কোনো destination register
নেই (rd ফিল্ড অনুপস্থিত), কিন্তু দুইটা source register লাগে:
একটা address-এর base (rs1), একটা যে ডেটা লেখা হবে (rs2)।
বিট: 31 25 24 20 19 15 14 12 11 7 6 0
┌─────────────┬────────┬────────┬──────┬─────────┬────────┐
│ imm[11:5] │ rs2 │ rs1 │funct3│imm[4:0] │ opcode │
│ (7 bit) │(5 bit) │(5 bit) │(3bit)│ (5 bit) │ (7 bit)│
└─────────────┴────────┴────────┴──────┴─────────┴────────┘লক্ষ্য করুন rs2 এখানে ঠিক R-type-এর rs2-এর একই পজিশনে ([24:20])
বসে আছে, আর rs1 ঠিক সেই একই [19:15]-এ। শুধু immediate-টা এখন দুই
টুকরায় ভাঙা — উঁচু ৭ বিট (imm[11:5]) একদিকে, নিচু ৫ বিট
(imm[4:0]) rd-এর পুরনো জায়গায়। কারণটা পরের অংশে (“hood”)
বিস্তারিত।
B-type, U-type, J-type — সংক্ষেপে
B-type (conditional branch) — beq Rs1, Rs2, offset — S-type-এরই
মতো গঠন, কিন্তু immediate বিটগুলো আরও অদ্ভুতভাবে সাজানো:
বিট: 31 30 25 24 20 19 15 14 12 11 8 7 6 0
┌────────┬─────────┬──────┬──────┬──────┬─────────┬──────┬────────┐
│imm[12] │imm[10:5]│ rs2 │ rs1 │funct3│imm[4:1] │imm[11]│ opcode │
└────────┴─────────┴──────┴──────┴──────┴─────────┴──────┴────────┘U-type (upper immediate) — lui Rd, imm — সবচেয়ে সরল, একটাই
বড় ফিল্ড:
বিট: 31 12 11 7 6 0
┌───────────────────────────┬─────────┬────────┐
│ imm[31:12] │ rd │ opcode │
│ (20 bit) │(5 bit) │ (7 bit)│
└───────────────────────────┴─────────┴────────┘J-type (unconditional jump) — jal Rd, offset — U-type-এর মতো
কাঠামো, কিন্তু immediate বিট B-type-এর মতোই এলোমেলো:
বিট: 31 30 21 20 19 12 11 7 6 0
┌─────────┬─────────┬──────┬────────────┬────────┬────────┐
│imm[20] │imm[10:1]│imm[11]│ imm[19:12] │ rd │ opcode │
└─────────┴─────────┴──────┴────────────┴────────┴────────┘সব format এক নজরে — opcode দিয়ে চেনা
| Opcode (binary) | Format | Instruction-এর দল |
|---|---|---|
0110011 | R | add, sub, and, or, xor, sll, srl, sra, slt, sltu |
0010011 | I | addi, andi, ori, xori, slti, sltiu, slli, srli, srai |
0000011 | I | lb, lh, lw, lbu, lhu (load) |
1100111 | I | jalr |
0100011 | S | sb, sh, sw |
1100011 | B | beq, bne, blt, bge, bltu, bgeu |
0110111 | U | lui |
0010111 | U | auipc |
1101111 | J | jal |
1110011 | — | ecall, ebreak (SYSTEM, নিজস্ব ছোট এনকোডিং) |
লক্ষ্য করুন — মাত্র ১১টা distinct opcode পুরো RV32I বেস instruction
set-কে সংজ্ঞায়িত করে (৭ বিট opcode ফিল্ডে সম্ভাব্য 128টা মানের মধ্যে
মাত্র ১১টা ব্যবহৃত — বাকিটা ভবিষ্যৎ extension-এর জন্য সংরক্ষিত)।
বাকি সূক্ষ্মতা (কোনটা ADD, কোনটা SUB; কোনটা LB, কোনটা LW)
funct3/funct7 থেকে আসে — ঠিক Level ২-এর ALU design লেসনে MIPS-এর
প্রসঙ্গে যে দুই-স্তরের control আলোচিত হয়েছিল, তারই RV32I সংস্করণ।
ভেতরে কী ঘটছে
কেন rs1/rs2/rd/opcode ফিক্সড পজিশনে — decoder-এর দৃষ্টিকোণ থেকে
এইটাই এই পুরো লেসনের কেন্দ্রীয় “কেন”। ধরুন decoder-এর প্রথম কাজ
register file-এর দুইটা read port-এ ঠিকানা পাঠানো (rs1, rs2)। যদি
rs1-এর বিট-পজিশন instruction format ভেদে বদলাতো, decoder-কে প্রথমে
opcode পড়ে বুঝতে হতো এটা কোন format, তারপরই বুঝত rs1 কোথায়
খুঁজবে — একটা sequential নির্ভরতা।
কিন্তু RV32I-তে rs1 সবসময় [19:15]-এ (যেসব format-এ rs1 লাগে —
R, I, S, B — সবগুলোতেই)। ফলে register file read সম্পূর্ণ opcode
decode হওয়ার আগেই শুরু করা যায় — একটা wire সরাসরি instruction
বিট [19:15]-কে register file-এর read-address port-এ জুড়ে দিলেই
চলে, কোনো MUX বা “আগে জানো এটা কোন instruction” ধাপ ছাড়াই। একই যুক্তি
rs2 ([24:20], R/S/B-তে), rd ([11:7], R/I/U/J-তে), এবং opcode
নিজেই ([6:0], সবসময়) — প্রতিটাই সব প্রাসঙ্গিক format জুড়ে অভিন্ন
পজিশনে।
Instruction[31:0]
│
┌────────────────┼────────────────┐
▼ ▼ ▼
opcode[6:0] rs1[19:15] rs2[24:20]
│ │ │
▼ ▼ ▼
Control unit Register file Register file
(কোন operation read port 1 read port 2
বুঝতে শুরু করে) (সমান্তরালে (সমান্তরালে
শুরু হয়) শুরু হয়)সবকিছু একই clock edge-এ সমান্তরালে শুরু হয় — কেউ কারো জন্য অপেক্ষা করে না।
এটা সরাসরি Level ২-র datapath-and-control-unit লেসনের একটা সীমাবদ্ধতা
পূরণ করে — সেই লেসনের toy ISA-তে Rd ফিল্ড ADD আর LOAD দুটোতেই একই
জায়গায় বসানো হয়েছিল ঠিক এই কারণেই (“কোনো বাড়তি MUX লাগে না”), কিন্তু
সেটা ছিল মাত্র তিনটা instruction-এর একটা হাতে-বানানো সরলীকরণ। RV32I
দেখায় এই একই নীতি একটা সম্পূর্ণ, বাস্তব, ৪০+ instruction-এর ISA জুড়ে
কীভাবে সুসংগতভাবে বজায় রাখা যায়।
কেন B/J-type-এর immediate এভাবে ভাঙা
এখন সেই “এলোমেলো” immediate বিট-ক্রমের উত্তর। দুইটা কারণ, দুটোই hardware-driven:
কারণ ১ — sign বিট সবসময় bit 31-এ। লক্ষ্য করুন প্রতিটা format-এই
(I, S, B, U, J) immediate-এর sign বিট সবসময় instruction-এর সবচেয়ে
উঁচু বিট, [31]। এর মানে sign-extension circuit-এর জন্য একটাই
common wire লাগে — instruction[31]-কে সব উঁচু বিটে কপি করলেই sign
extend হয়ে যায়, format যাই হোক না কেন। যদি sign বিট format ভেদে
আলাদা জায়গায় থাকত, প্রতিটা format-এর জন্য আলাদা sign-extension logic
লাগত।
কারণ ২ — rs1/rs2/funct3/opcode তাদের নিজস্ব ফিক্সড জায়গা ছাড়ে না।
B-type-এও rs1[19:15], rs2[24:20], funct3[14:12], opcode[6:0]
হুবহু R/I/S-type-এর একই পজিশনে থাকতে হবে (উপরের যুক্তি অনুযায়ী)। তাহলে
immediate-এর জন্য যা বাকি থাকে তা হলো বিট [31], [30:25],
[11:8], আর [7] — মোট ঠিক ১২টা বিট, কিন্তু চার টুকরায় ছড়িয়ে। এই
টুকরাগুলোকে একটা immediate-generation circuit পুনর্বিন্যস্ত করে
সঠিক ক্রমে (imm[12|11|10:5|4:1], তারপর নিচে 0 জোড়া) বসায় —
এটা একটা ছোট wiring/MUX সমস্যা মাত্র, কোনো নতুন গণনা না (শুধু তার
পুনর্বিন্যাস, কোনো গেট delay-ও প্রায় লাগে না)।
হাতে decode করা — একটা real instruction
0x00730E33 — এটা কী instruction? ধাপে ধাপে:
ধাপ ১ — hex থেকে binary।
0x00730E33 = 0000 0000 0111 0011 0000 1110 0011 0011ধাপ ২ — opcode বের করা (সবচেয়ে নিচের ৭ বিট)।
bit[6:0] = 0110011উপরের টেবিল অনুযায়ী 0110011 = R-type।
ধাপ ৩ — R-type ফিল্ড অনুযায়ী কাটা।
বিট: 31-25 24-20 19-15 14-12 11-7 6-0
0000000 00111 00110 000 11100 0110011
funct7 rs2 rs1 funct3 rd opcodeধাপ ৪ — প্রতিটা ফিল্ডের মান বের করা।
| ফিল্ড | binary | দশমিক | রেজিস্টার নাম (ABI) |
|---|---|---|---|
funct7 | 0000000 | ০ | — |
rs2 | 00111 | ৭ | x7 = t2 |
rs1 | 00110 | ৬ | x6 = t1 |
funct3 | 000 | ০ | — |
rd | 11100 | ২৮ | x28 = t3 |
opcode | 0110011 | — | R-type (OP) |
ধাপ ৫ — funct3=000, funct7=0000000 → operation লুকআপ।
R-type-এর জন্য funct3=000 আর funct7=0000000 নির্দিষ্টভাবে ADD
চিহ্নিত করে (নিচের টেবিল দেখুন)।
উত্তর: add t3, t1, t2 — অর্থাৎ x28 ← x6 + x7।
R-type funct3/funct7 সম্পূর্ণ টেবিল
funct3 | funct7=0000000 | funct7=0100000 |
|---|---|---|
000 | ADD | SUB |
001 | SLL (shift left logical) | — |
010 | SLT (set less than, signed) | — |
011 | SLTU (set less than, unsigned) | — |
100 | XOR | — |
101 | SRL (shift right logical) | SRA (shift right arithmetic) |
110 | OR | — |
111 | AND | — |
লক্ষ্য করুন — শুধু ADD/SUB আর SRL/SRA জোড়াই funct7-এর
পার্থক্য ব্যবহার করে (bit 30-এ, funct7 = 0000000 বনাম 0100000)।
বাকি ছয়টা operation funct7=0000000-এই বসে, funct3-ই যথেষ্ট তাদের
আলাদা করতে। এটা হুবহু Level ২-র ALU-design লেসনের অবজারভেশনের প্রতিধ্বনি
— “opcode-এর বিট বরাদ্দ প্রায়ই সচেতনভাবে বেছে নেওয়া হয় যাতে control
signal সরাসরি বিট থেকে বেরিয়ে আসে।”
হাতে encode করা — mnemonic থেকে hex
উল্টো দিকে: addi t0, zero, 5 — এই instruction-এর hex কী?
ধাপ ১ — format ও opcode বের করা। addi একটা I-type, immediate ALU
operation, opcode = 0010011।
ধাপ ২ — register নম্বর। t0 = x5, zero = x0।
ধাপ ৩ — funct3। I-type ALU operation-এ ADDI-এর funct3 = 000
(R-type-এর ADD-এর সাথে অভিন্ন কোড, যুক্তিসঙ্গত — একই operation,
শুধু একটা operand register-এর বদলে immediate)।
ধাপ ৪ — immediate। 5 দশমিকে = 00000000101 — ১২ বিটে সাইন-এক্সটেন্ড
করে 000000000101।
ধাপ ৫ — ফিল্ড সাজানো।
imm[11:0] rs1 funct3 rd opcode
000000000101 00000 000 00101 0010011ধাপ ৬ — বিট জোড়া লাগিয়ে nibble-এ ভাগ করা।
000000000101 00000 000 00101 0010011
= 0000 0000 0101 0000 0000 0010 1001 0011
= 0 0 5 0 0 2 9 3উত্তর: 0x00500293।
- addi t0, zero, 5Assembly mnemonic — মানুষের পড়ার জন্য
- Format = I-type, opcode = 0010011ADDI immediate ALU operation
- rd=00101 (x5), rs1=00000 (x0), funct3=000register ফিল্ড ও operation-selector
- imm[11:0] = 0000000001015 কে 12-বিট signed-এ প্রকাশ
- 32-bit bit string জোড়া লাগানোimm + rs1 + funct3 + rd + opcode = 32 বিট
- হেক্স = 0x00500293এটাই instruction memory-তে যা সংরক্ষিত থাকবে
হাতে encode করা — B-type, যেখানে immediate সত্যিই ভাঙে
উপরের দুইটা উদাহরণই R-type/I-type — সহজ, কারণ immediate (I-type-এ) একটানা এক জায়গায়। এবার সেই “এলোমেলো” B-type immediate সত্যিই হাতে এনকোড করে দেখি, যাতে “hood” অংশের দাবিটা শুধু কথায় না থেকে সংখ্যায় প্রমাণিত হয়।
beq t1, t2, 8 — যদি t1 == t2, PC ← PC + 8 (এখনকার instruction
থেকে ৮ byte সামনে, অর্থাৎ দুইটা instruction টপকে যাওয়া)।
ধাপ ১ — format ও opcode। B-type, opcode = 1100011।
ধাপ ২ — register ও funct3। rs1 = t1 = x6 = 00110,
rs2 = t2 = x7 = 00111, funct3 = 000 (BEQ)।
ধাপ ৩ — offset-কে ১৩-বিট signed-এ লেখা। 8 দশমিকে, ১৩ বিটে:
0 0000000 0100 0 — অর্থাৎ imm[12]=0, imm[11]=0, imm[10:5]=000000,
imm[4:1]=0100, imm[0]=0 (এই শেষ বিটটা কখনো encode হয় না, সবসময়
0 ধরে নেওয়া হয় — branch offset সবসময় জোড় সংখ্যা)।
ধাপ ৪ — B-type ফরম্যাট অনুযায়ী বিট সাজানো (bit 31 থেকে bit 0):
imm[12] imm[10:5] rs2 rs1 funct3 imm[4:1] imm[11] opcode
0 000000 00111 00110 000 0100 0 1100011ধাপ ৫ — জোড়া লাগিয়ে nibble-ভাগ।
0 000000 00111 00110 000 0100 0 1100011
= 0000 0000 0111 0011 0000 0100 0110 0011
= 0 0 7 3 0 4 6 3উত্তর: 0x00730463।
উল্টো দিকে decode করে যাচাই — 0x00730463 থেকে ফিরে beq t1, t2, 8:
0x00730463 = 0000 0000 0111 0011 0000 0100 0110 0011
opcode[6:0] = 1100011 → B-type, BRANCH
funct3[14:12] = 000 → BEQ
rs1[19:15] = 00110 = x6 = t1
rs2[24:20] = 00111 = x7 = t2
imm[12] = bit31 = 0
imm[11] = bit7 = 0
imm[10:5]= bits30-25 = 000000
imm[4:1] = bits11-8 = 0100
imm[0] = (সবসময় ধরে নেওয়া) = 0
পুনর্গঠিত offset = imm[12]imm[11]imm[10:5]imm[4:1]imm[0]
= 0 0 000000 0100 0 = 0000000001000₂ = 8মিলে গেছে — beq t1, t2, 8।
উদাহরণ
আরও দুইটা instruction — decode অনুশীলন
0xFF842303 কী?
0xFF842303 = 1111 1111 1000 0100 0010 0011 0000 0011
opcode[6:0] = 0000011 → I-type, LOAD
rd[11:7] = 00110 → x6 = t1
funct3[14:12]= 010 → LW (load word)
rs1[19:15] = 01000 → x8 = s0
imm[31:20] = 111111111000 → -8 (12-bit signed)উত্তর: lw t1, -8(s0) — s0 রেজিস্টারের মান থেকে 8 বিয়োগ করে
পাওয়া ঠিকানা থেকে একটা word পড়ে t1-এ বসাও। (Addressing mode-এর
ভাষায় একে বলে “base + displacement” — পরের লেসনের বিষয়, এখানে শুধু
এনকোডিং-টা লক্ষ্য করুন।)
0xFFC42E23 কী?
0xFFC42E23 = 1111 1111 1100 0100 0010 1110 0010 0011
opcode[6:0] = 0100011 → S-type, STORE
funct3[14:12] = 010 → SW (store word)
rs1[19:15] = 01000 → x8 = s0
rs2[24:20] = 11100 → x28 = t3
imm[11:5]+imm[4:0] = 1111111 + 11100 → পুনর্গঠিত: 111111111100 = -4উত্তর: sw t3, -4(s0) — t3-এর মান লেখো s0-4 ঠিকানায়।
Instruction density — একটা বাস্তব পরিণতি
RV32I fixed ৩২-বিট মানে প্রতিটা instruction ঠিক ৪ byte নেয়, এমনকি
addi t0, zero, 1-এর মতো তুচ্ছ কাজও। তুলনায় x86-64-তে একটা equivalent
“immediate-কে register-এ বসাও” instruction মাত্র ২-৩ byte-এ ফিট করতে
পারে। এর মানে সমান কাজের জন্য RV32I কোড প্রায়ই x86-64 কোডের চেয়ে বড়
হয় (বেশি instruction memory bandwidth লাগে) — এটাই fixed-length
এনকোডিং-এর একটা সত্যিকার খরচ, যেটার বিনিময়ে decode সরলতা কেনা হয়।
(RISC-V-এর “C” extension এই সমস্যার আংশিক সমাধান — সাধারণ instruction-কে
১৬ বিটে কমপ্রেস করে, নিচে “বাস্তব সিস্টেমে” অংশে ফিরব।)
নিজে চালিয়ে দেখুন
Compiler Explorer-এ RV32I কোড দেখুন ও নিজে decode করুন
১. godbolt.org-এ যান, একটা নতুন C source panel খুলুন, নিচের ছোট
ফাংশনটা লিখুন:
int add3(int a, int b, int c) {
return a + b + c;
}২. Compiler নির্বাচন করুন — সার্চ বক্সে “RISC-V” লিখলে
riscv32-unknown-elf-gcc বা similar একটা target পাবেন। Optimization
flag -O0 দিন (যাতে সরল, unoptimized assembly পাওয়া যায়)।
3. ডান পাশে emitted assembly দেখুন — কয়েকটা add/lw/sw instruction
থাকবে।
4. একই compiler-এ -S output-এর বদলে “Binary” বা “Compile to binary
object” mode-এ hex dump দেখার অপশন খুঁজুন (বা locally
riscv32-unknown-elf-objdump -d চালান যদি toolchain ইনস্টল থাকে)।
5. একটা add instruction-এর hex বেছে নিন, এই লেসনের ধাপগুলো অনুসরণ
করে হাতে decode করুন — opcode, funct3, funct7, rd, rs1, rs2 বের
করুন।
6. যাচাই করুন আপনার decode করা register নম্বরগুলো disassembly-তে
দেখানো register নামের সাথে মেলে কি না (x নম্বর ↔ ABI নাম ম্যাপিং
এই লেসনের টেবিল থেকে)।
Compiler-এর তৈরি machine code hex আপনার হাতে-করা bit-field decode-এর সাথে হুবহু মেলে — এনকোডিং তত্ত্ব নয়, বাস্তবে-চলা নিয়ম।
Python দিয়ে decode/encode যাচাই — reference model
def decode_r_type(word):
opcode = word & 0x7F
rd = (word >> 7) & 0x1F
funct3 = (word >> 12) & 0x7
rs1 = (word >> 15) & 0x1F
rs2 = (word >> 20) & 0x1F
funct7 = (word >> 25) & 0x7F
return dict(opcode=bin(opcode), rd=rd, funct3=bin(funct3),
rs1=rs1, rs2=rs2, funct7=bin(funct7))
print(decode_r_type(0x00730E33))
# {'opcode': '0b110011', 'rd': 28, 'funct3': '0b0', 'rs1': 6, 'rs2': 7, 'funct7': '0b0'}লক্ষ্য করুন rd=28 (t3), rs1=6 (t1), rs2=7 (t2) — ঠিক হাতে করা
decode-এর সাথে মিলছে। এবার একটা I-type decoder লিখুন (sign-extension
সহ — Python-এ if imm & 0x800: imm -= 0x1000 দিয়ে ১২-বিট sign
extend করা যায়) আর 0xFF842303 দিয়ে যাচাই করুন rs1=8, rd=6, imm=-8 পাচ্ছেন কি না।
হাতে করা bit-slicing আর প্রোগ্রামের bit-slicing হুবহু একই ফলাফল দেয় — এই লেসনের পদ্ধতিটা যান্ত্রিকভাবে সঠিক, কোনো hand-wave নেই।
নিজে বানান
একটা ন্যূনতম RV32I encoder/decoder — এই platform-এর CPU Emulator প্রজেক্টের প্রথম ইট
- একটা decode(word) ফাংশন লিখুন যেটা opcode বের করে format (R/I/S/B/U/J) নির্ধারণ করে
- R-type-এর জন্য funct7/rs2/rs1/funct3/rd সব বের করে একটা dict/struct রিটার্ন করুন
- I-type-এর জন্য rs1/funct3/rd/immediate (sign-extended) বের করুন, LOAD ও ADDI দুটোই টেস্ট করুন
- S-type-এর জন্য immediate-এর দুই টুকরা (imm[11:5], imm[4:0]) সঠিক ক্রমে জোড়া লাগান
- প্রতিটা format-এর জন্য একটা mnemonic টেবিল বানান (opcode+funct3+funct7 → নাম) আর decode(word)-কে human-readable "add t3, t1, t2" স্ট্রিং-এ রূপান্তর করুন
- উল্টো দিকে একটা encode(mnemonic, operands) ফাংশন লিখুন — অন্তত ADD, ADDI, LW, SW সমর্থন করুক
- এই লেসনের চারটা hex (0x00730E33, 0xFF842303, 0xFF442383, 0xFFC42E23) দিয়ে decoder যাচাই করুন
এই ছোট script-টাই এই module-এর “CPU Emulator” প্রজেক্টের ভিত্তি — পরের লেসনে (fetch-decode-execute) আমরা এই decoder-এর উপর একটা আসল execute loop বসাব, যাতে এটা শুধু bits ব্যাখ্যা না করে সত্যিই একটা program চালাতে পারে।
একটা গুরুত্বপূর্ণ ডিজাইন সিদ্ধান্ত — format প্রথমে বের করুন, তারপর
বিস্তারিত। ঠিক hardware decoder যেভাবে কাজ করে (এই লেসনের “hood”
অংশ), আপনার কোডেও প্রথমে শুধু opcode-এর ৭ বিট দেখে format ঠিক করুন,
তারপরই format-নির্দিষ্ট বাকি ফিল্ড বের করুন। একটা একক
if/elif opcode == ... চেইন এটা স্বাভাবিকভাবেই প্রতিফলিত করবে।
Sign-extension একটা সাধারণ bug-এর উৎস। ১২-বিট immediate-কে Python
int-এ রূপান্তর করার সময় bit 11 (sign বিট) চেক করে negative হলে
- 4096 (অর্থাৎ 2^12) বিয়োগ করতে ভুলবেন না — নাহলে -8-এর বদলে 4088
পাবেন, যা সম্পূর্ণ ভুল ঠিকানায় নিয়ে যাবে।
বাস্তব সিস্টেমে
Encoding regularity বাস্তব সিস্টেমে
RISC-V-এর “C” (compressed) extension। সাধারণ instruction-গুলো
(addi, lw, add ছোট immediate/register দিয়ে) প্রায়ই ১৬ বিটেই
এনকোড করা যায় — RV32IC তে এই compressed ফর্ম মিশ্রিত থাকে সাধারণ
৩২-বিট instruction-এর সাথে, opcode-এর নিচের দুই বিট দিয়ে (এই লেসনের
শুরুতে উল্লিখিত) আলাদা করা হয়। ফলাফল — code size প্রায় x86-64-এর
কাছাকাছি নেমে আসে, decode সরলতা বজায় রেখেই।
ARM64 (AArch64)-ও fixed ৩২-বিট। RISC-V-এর মতোই ARM64 প্রতিটা instruction ঠিক ৪ byte রাখে — একই decode-simplicity যুক্তি। ARM-এর পুরনো ৩২-বিট (AArch32) আর তার Thumb (১৬-বিট) mode-ও একই dual-width কৌশল ব্যবহার করত যা RISC-V-এর “C” extension অনুসরণ করে ডিজাইন হয়েছে।
objdump -d ও disassembler। যেকোনো compiled ELF binary-তে
objdump -d file.o চালালে প্রতিটা instruction-এর hex ও mnemonic
পাশাপাশি দেখা যায় — এই লেসনে হাতে করা decode ঠিক এই টুলটাই স্বয়ংক্রিয়ভাবে
করে, একই bit-field regularity-র উপর নির্ভর করে।
GCC/LLVM backend-এর assembler pass। Compiler নিজে assembly জেনারেট
করার পর একটা assembler pass সেই mnemonic-গুলোকে ঠিক এই লেসনের নিয়ম
অনুযায়ী bit-এ রূপান্তর করে — .o ফাইলের ভেতরে যা থাকে তা এই লেসনের
bit-field diagram-এর সরাসরি প্রয়োগ।
Ripes, RARS, QEMU — RV32I simulator। এই platform-এর পরের লেসনগুলোর experiment-এ ব্যবহৃত হবে এমন টুল — এরা প্রতিটা fetch করা instruction-কে ঠিক এই লেসনের নিয়মে decode করে, তারপর সেই decode অনুযায়ী register/memory বদলায়। এই লেসনের decode logic হুবহু তাদের ভেতরে চলছে, শুধু C/Rust/Java-তে লেখা।
Historical CISC — x86 এনকোডিং-এর জটিলতা (নিচে বিস্তারিত)। RISC-V-এর নিয়মিততা বোঝার সবচেয়ে ভালো উপায় হলো তার বিপরীত চরমটা দেখা — x86-64।
x86-64 — variable-length এনকোডিং, আর কেন এটা কঠিন
RISC-V-এর প্রতিটা instruction ঠিক ৪ byte। x86-64-এ একটা instruction ১ থেকে ১৫ byte পর্যন্ত হতে পারে — একই decoder-কে দুটোই সামলাতে হয়।
একটা x86-64 instruction-এর সাধারণ গঠন (সবগুলো অংশ ঐচ্ছিক, উপস্থিতি নির্ভর করে ঠিক কী instruction):
[prefix bytes] [REX prefix] [opcode (1-3 byte)] [ModRM] [SIB] [displacement] [immediate]
0-4 byte 0-1 byte 1-3 byte 0-1 byte 0-1 byte 0/1/2/4 byte 0/1/2/4 byteএকটা concrete উদাহরণ। add rax, rbx (৬৪-বিট register-register
add, RV32I-র add t3,t1,t2-এর সমতুল্য) এনকোড হয়:
48 01 D8
│ │ └── ModRM byte: mod=11, reg=RBX(011), rm=RAX(000)
│ └───── opcode: 01 (ADD, register→register/memory, 32/64-bit)
└──────── REX prefix: 0100 1000 — REX.W=1 (৬৪-বিট operand size)মাত্র ৩ byte — RV32I-র ৪ byte-এর চেয়ে ছোট। কিন্তু এই compactness-এর দাম আছে।
কেন এটা pipelining-কে কঠিন করে — লেসন ২-এর RISC/CISC আলোচনার সরাসরি প্রয়োগ:
- সমান্তরাল fetch কঠিন। একটা superscalar CPU একসাথে একাধিক
instruction fetch/decode করতে চায় (Level ৩-র পরের topics — superscalar
execution)। RV32I-তে এটা সহজ: instruction
Nযদি ঠিকানাA-তে শুরু হয়, instructionN+1নিশ্চিতভাবেA+4-এ — decode শুরুর আগেই সব instruction-এর ঠিকানা জানা যায়। x86-64-তে instructionN+1-এর ঠিকানা জানতে instructionN-এর length সম্পূর্ণভাবে বের করতে হয় — একটা সিরিয়াল নির্ভরতা যা সমান্তরাল fetch-কে জটিল করে তোলে। - Predecode/length-decode হার্ডওয়্যার লাগে। আধুনিক x86-64 CPU (Intel/AMD) একটা আলাদা “length decoder” বা “predecode” স্তেজ রাখে, যার একমাত্র কাজ instruction boundary খুঁজে বের করা, আসল decode শুরুর আগেই — এটা বাড়তি সিলিকন এরিয়া আর pipeline stage, যা RISC-V-এর দরকারই হয় না।
- Micro-op cache একটা বাস্তব সমাধান। এই সমস্যা এতটাই ব্যয়বহুল যে আধুনিক x86-64 প্রসেসর (Intel Skylake+, AMD Zen) ইতিমধ্যে-decode-করা instruction-কে (fixed-width “micro-op” আকারে — RISC-স্টাইল!) একটা ক্যাশে রেখে দেয়, যাতে বারবার loop চালানোর সময় একই costly variable-length decode আবার করতে না হয়। এটা Level ২-র datapath-and-control-unit লেসনের “x86 internally RISC-এ অনুবাদ” পয়েন্টেরই একটা নতুন কোণ — আধুনিক x86-64 তার নিজের এনকোডিং সমস্যা এড়াতে ভেতরে ভেতরে RISC-V-এর মতো একটা fixed-width জগৎ তৈরি করে নিয়েছে।
| RISC-V RV32I | x86-64 | |
|---|---|---|
| Instruction length | সবসময় ৪ byte (৩২ বিট) | ১-১৫ byte, variable |
| পরের instruction-এর ঠিকানা | তাৎক্ষণিক (PC+4) | Current instruction পুরো (partially) decode করতে হয় |
| সমান্তরাল multi-instruction fetch | সরল — সব ঠিকানা আগে থেকেই জানা | জটিল — সিরিয়াল length-decode নির্ভরতা |
| Code density | তুলনামূলক কম ঘন (compressed extension ছাড়া) | তুলনামূলক ঘন |
| Decoder hardware area | ছোট | বড়, একাধিক predecode stage |
যে ভুলগুলো সবাই করে
“Opcode একাই যথেষ্ট instruction চেনার জন্য — funct3/funct7 বাড়তি জটিলতা মাত্র।”
উল্টো — funct3/funct7 হলো precisely সেই মেকানিজম যা RV32I-কে মাত্র ১১টা opcode দিয়ে ৪০+ instruction সমর্থন করতে দেয়। যদি প্রতিটা instruction-এর নিজস্ব unique opcode লাগত, ৭-বিট opcode ফিল্ড (সর্বোচ্চ ১২৮টা মান) দ্রুত ফুরিয়ে যেত, বা ফিল্ডটা বড় করতে হতো (immediate/register ফিল্ডের জায়গা কেড়ে)। Funct3/funct7 opcode-এর “নিচে” আরও তথ্য এনকোড করার একটা জায়গা — precisely Level ২-র ALU-design লেসনের দুই-স্তরের control ধারণার এনকোডিং-স্তরের প্রতিফলন।
“Machine code hex দেখতে র্যান্ডম, তার কোনো readable structure নেই।”
এই পুরো লেসনটাই এর বিপরীত প্রমাণ। 0x00730E33 “র্যান্ডম” মনে হতে
পারে যতক্ষণ না আপনি জানেন কোথায় কাটতে হবে — কিন্তু একবার বিট-ফিল্ড
জানা থাকলে, এটা precisely যতটা structured একটা struct-এর byte
layout (Level ১-এর serialization লেসন মনে করুন)। পার্থক্য শুধু এই যে
এখানে “schema”-টা CPU-র ISA manual-এ লেখা, প্রোগ্রামের সোর্স কোডে না।
“Variable-length encoding (x86) স্রেফ খারাপ ডিজাইন, ভুল সিদ্ধান্ত।”
এটা একটা trade-off, ভুল না। Variable-length এনকোডিং প্রায়ই ছোট
কোড দেয় (উপরের add rax,rbx উদাহরণ — মাত্র ৩ byte, RV32I-র ৪ byte-এর
চেয়ে কম) — কম instruction memory bandwidth, কম instruction cache
miss। x86 ১৯৭৮ সালে ডিজাইন হয়েছিল যখন মেমোরি অত্যন্ত দামি ছিল আর
compact কোড একটা বাস্তব অগ্রাধিকার ছিল। RISC-V-এর মতো আধুনিক ISA
ভিন্ন যুগে, ভিন্ন constraint নিয়ে ডিজাইন হয়েছে (transistor বাজেট
এখন decode simplicity/pipelining-এ খরচ করা লাভজনক) — উভয় সিদ্ধান্তই
তাদের নিজ নিজ সময়ে যুক্তিসঙ্গত।
“Immediate সবসময় unsigned একটা সংখ্যা, sign-extend করার দরকার নেই।”
ভুল, আর এটা একটা বাস্তব bug-এর উৎস। RV32I-র বেশিরভাগ immediate
(I-type, S-type, B-type) signed — negative offset (যেমন এই লেসনের
lw t1, -8(s0)) খুবই সাধারণ, বিশেষত stack-relative addressing-এ
(পরের লেসনের বিষয়)। Sign-extension ভুল করলে (যেমন উপরের উদাহরণে
-8-এর বদলে 4088 পড়া) সম্পূর্ণ ভুল মেমোরি ঠিকানায় গিয়ে ভুল ডেটা
পড়বে বা প্রোগ্রাম crash করবে — একটা lesson-এর “Build It” অংশে
উল্লেখিত ঠিক এই bug।
বুঝেছেন কি না দেখুন
10x00F30313 — এই instruction-টা decode করুন। (হিন্ট: opcode বের করে format ঠিক করুন প্রথমে।)প্রয়োগ
Binary: 0000 0000 1111 0011 0000 0011 0001 0011
opcode[6:0] = 0010011 → I-type, immediate ALU operation।
imm[31:20] = 000000001111 = 15
rs1[19:15] = 00110 = x6 = t1
funct3[14:12] = 000 → ADDI
rd[11:7] = 00110 = x6 = t1
opcode = 0010011উত্তর: addi t1, t1, 15 — t1-এর মানের সাথে 15 যোগ করে আবার
t1-এই বসাও (t1 ← t1 + 15)।
2R-type আর I-type-এ opcode, rd, funct3, rs1 কেন হুবহু একই bit position-এ রাখা হয়েছে? এটা যদি না রাখা হতো, তাহলে হার্ডওয়্যারে ঠিক কী সমস্যা হতো?যুক্তি
কারণ এটা decoder-কে সমান্তরাল কাজ করতে দেয় — register file read
port-এ rs1/rs2 পাঠানো, আর control unit-এ opcode পাঠানো, দুটোই
একই clock edge-এ শুরু হতে পারে, কেউ কারো decode শেষ হওয়ার জন্য অপেক্ষা
না করে (এই লেসনের Fig 4.4)।
যদি এটা না রাখা হতো — ধরুন I-type-এ rs1 অন্য জায়গায় থাকত — তাহলে
হার্ডওয়্যারকে প্রথমে opcode দেখে format নির্ধারণ করতে হতো, তারপর
সেই format অনুযায়ী সঠিক বিট-পজিশন থেকে rs1 পড়তে হতো — একটা
sequential নির্ভরতা যোগ হতো critical path-এ, প্রতিটা instruction-এ,
প্রতিটা cycle-এ। এটা ছোট শোনালেও, বিলিয়ন instruction-এ প্রতিটাতে
কয়েক গেট-delay বাড়তি খরচ পুরো CPU-র clock speed কমিয়ে দিতে পারে।
3B-type ও J-type-এর immediate বিট কেন “এলোমেলো” ক্রমে সাজানো, সরল ক্রমে (যেমন U-type) নয়?যুক্তি
দুইটা কারণ। প্রথমত, sign বিট (সবসময় বৃহত্তম offset-এর MSB) সব
format জুড়েই bit 31-এ রাখতে হবে, যাতে একটাই common sign-extension
circuit ব্যবহার করা যায় — format-নির্দিষ্ট কোনো আলাদা sign-extend
logic না লাগে। দ্বিতীয়ত, rs1, rs2, funct3, opcode-এর
ফিক্সড পজিশন (উপরের প্রশ্নের যুক্তি) বজায় রাখতে হবে B-type-এও —
ফলে immediate-এর জন্য যে বিটগুলো “বাকি” থাকে সেগুলো বিক্ষিপ্তভাবে
ছড়িয়ে থাকে instruction জুড়ে, একটানা নয়। U-type-এ কোনো rs1/rs2
নেই (শুধু rd), তাই পুরো উঁচু ২০ বিট অবাধে immediate-এর জন্য পাওয়া
যায় — কোনো স্ক্র্যাম্বলিং লাগে না।
4sub t0, t1, t2 (x5 ← x6 - x7) হাতে এনকোড করুন। এনকোডিং-এর কোন একটা মাত্র বিট add t0, t1, t2-এর এনকোডিং থেকে বদলাবে?প্রয়োগ
sub-ও R-type, একই opcode (0110011), একই funct3 (000) — শুধু
funct7 বদলায়: ADD-এ 0000000, SUB-এ 0100000।
funct7 rs2 rs1 funct3 rd opcode
0100000 00111 00110 000 00101 0110011= 0100 0000 1110 0110 0000 0010 1011 0011 = 0x40662B3।
তুলনায় add t0,t1,t2: funct7=0000000 → 0x006302B3।
লক্ষ্য করুন শুধু bit 30 (funct7-এর MSB, যেটা কার্যত ALU-এর “mode”
বিট) বদলেছে — বাকি সবকিছু অভিন্ন। এটা Level ২-এর ALU-design লেসনের
adder/subtractor সার্কিটের সেই একই “mode বিট M” যা ADD/SUB
নির্বাচন করে — এখানে ISA-স্তরে সেটাই funct7-এর একটা বিট হয়ে
এনকোড হয়েছে।
5ধরুন একটা নতুন ISA ডিজাইন করছেন যেখানে instruction ১৬ বিট লম্বা (RV32I-এর অর্ধেক), আর ১৬টা register (৪ বিট প্রতি ফিল্ড) সমর্থন করতে হবে। একটা R-type-সদৃশ format (opcode + rd + rs1 + rs2) ডিজাইন করুন — কয় বিট opcode-এর জন্য বাকি থাকে, আর তার মানে কী সীমাবদ্ধতা?ডিজাইন
৩টা register ফিল্ড (rd, rs1, rs2), প্রতিটা ৪ বিট (2^4=16
register এনকোড করতে) = মোট 12 বিট। ১৬-বিট instruction-এ বাকি থাকে
মাত্র 16 - 12 = 4 বিট opcode-এর জন্য।
৪ বিট opcode মানে সর্বোচ্চ 2^4 = 16টা distinct operation — কোনো
funct3/funct7-সদৃশ বাড়তি ফিল্ডের জায়গাই নেই। এই ISA-তে ADD,
SUB, AND, OR, XOR -এর মতো সাধারণ কয়েকটা operation রাখা যাবে,
কিন্তু RV32I-র ১০+ R-type variant, বা কোনো I-type/S-type/B-type
format একই সাথে রাখার জায়গা নেই — হয় register সংখ্যা কমাতে হবে, নয়
instruction length বাড়াতে হবে, নয় একাধিক format-এ operation ভাগ
করে দিতে হবে (যেমন কিছু opcode শুধু ২-operand ব্যবহার করে immediate-এর
জন্য জায়গা ছাড়ে)। এটাই ISA ডিজাইনের একটা মৌলিক bit-budget ট্রেড-অফ
— register width, opcode space, আর immediate range — এই তিনটা সবসময়
একই সীমিত বিট-বাজেট থেকে প্রতিযোগিতা করে।
60x00730463 (এই লেসনে হাতে-এনকোড করা beq t1, t2, 8) যদি একটা backward branch হতো — অর্থাৎ offset ধনাত্মকের বদলে ঋণাত্মক (পেছনের দিকে, একটা loop-এর মতো) — তাহলে bit 31 (imm[12], sign বিট)-এর মান কী হতো, আর কেন?প্রয়োগ
imm[12] = 1। B-type-এর ১৩-বিট signed offset-এ sign বিট সবসময়
সবচেয়ে উঁচু বিট, instruction[31] — ঠিক I/S/U/J-type-এর মতোই
(এই লেসনের “কারণ ১” — একটাই common sign-extension wire, format
নির্বিশেষে)। যদি offset ঋণাত্মক হতো (যেমন -8, একটা loop-ব্যাক
branch), পুরো ১৩-বিট immediate two’s complement-এ negative হতো,
আর সেই representation-এর সবচেয়ে উঁচু বিট (sign বিট) নিশ্চিতভাবে
1 হতো — যা সরাসরি instruction[31] = 1-এ বসত। Decode করার সময়
control unit শুধু instruction[31]-কে বাকি সব উঁচু বিটে কপি করেই
(sign-extend) সঠিক ঋণাত্মক মান পুনর্গঠন করতে পারবে, কোনো
format-নির্দিষ্ট বিশেষ ব্যবস্থা ছাড়াই।
এরপর কী
এই লেসনে আমরা শিখেছি একটা instruction-এর বিট কী — কোন ফিল্ড কোথায়,
কোন বিট রেজিস্টার নম্বর, কোনটা immediate, কোনটা operation-selector।
কিন্তু একটা প্রশ্ন এখনো বাকি — যখন একটা ফিল্ড বলে “রেজিস্টার s0-এর
সাথে -8 যোগ করো,” সেই ফলাফলটা দিয়ে কী করা হয়? সরাসরি সেই মানই
কি ব্যবহৃত হবে, নাকি সেটা একটা মেমোরি ঠিকানা যেখান থেকে আরেকটা মান
পড়তে হবে?
এই প্রশ্নের উত্তর — অপারেন্ডের বিট থেকে তার প্রকৃত মান পর্যন্ত
পথ — পরের লেসনের বিষয়: addressing modes। সেখানে আমরা দেখব কেন
lw t1, -8(s0)-এর -8(s0) অংশটা আসলে একটা সম্পূর্ণ ঠিকানা-গণনার
রেসিপি, আর কেন RISC-V ইচ্ছাকৃতভাবে এরকম মাত্র কয়েকটা রেসিপিই সমর্থন
করে, x86-এর মতো ডজনখানেক না।
আরও পড়ুন
- The RISC-V Instruction Set Manual, Volume I: Unprivileged ISA, Chapter 2 — RV32I Base Integer Instruction Set — RISC-V International · RV32I-এর প্রতিটা instruction format আর এনকোডিং টেবিলের প্রামাণ্য উৎস — এই লেসনের সব বিট-ফিল্ড এখান থেকে যাচাই করা
- The RISC-V Reader: An Open Architecture Atlas — David Patterson, Andrew Waterman · কেন RISC-V-এর ফিল্ড লেআউট এভাবে ডিজাইন করা হয়েছে তার ইঞ্জিনিয়ারিং-যুক্তি সহ আলোচনা
- Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 2 — Instruction Format — Intel Corporation · x86-64-এর prefix/opcode/ModRM/SIB/displacement/immediate এনকোডিং-এর প্রামাণ্য বিবরণ
- Computer Organization and Design (RISC-V Edition), Chapter 2.5 — RISC-V Addressing for 32-bit Immediates — David A. Patterson, John L. Hennessy · Instruction format ডিজাইনের পেছনের hardware-decode যুক্তির টেক্সটবুক-মানের ব্যাখ্যা