Foundationপ্রথম নীতি থেকে
LEVEL 3লেসন ৪/১৭কঠিন১ ঘণ্টা

Instruction Encoding — একটা instruction কীভাবে বিটে পরিণত হয়

Instruction Encoding

একটা instruction আসলে একটা নির্দিষ্ট প্যাটার্নে সাজানো বিট মাত্র — opcode কোন কাজ, funct3/funct7 কোন variant, rd/rs1/rs2 কোন register, immediate কোন constant, সব ফিক্সড বিট-পজিশনে। RV32I-তে এই প্যাটার্ন সবসময় ৩২ বিট, ছয়টা format-এর একটায় পড়ে; x86-64-তে এই প্যাটার্ন ১ থেকে ১৫ byte পর্যন্ত বদলাতে পারে — আর এই একটা পার্থক্যই pipelining-কে সহজ বা কঠিন করে দেয়।

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

  • একটা instruction encoding-কে 'mnemonic ও operand থেকে একটা নির্দিষ্ট, নির্ধারিত বিট-প্যাটার্নে ম্যাপিং' হিসেবে সংজ্ঞায়িত করে বলতে পারবেন হার্ডওয়্যার decoder-এর কাছ থেকে এই ম্যাপিং ঠিক কী দাবি করে
  • RV32I-এর ছয়টা instruction format (R, I, S, B, U, J) চিনতে পারবেন এবং R-type ও I-type-এর প্রতিটা বিট-ফিল্ড (opcode, rd, funct3, rs1, rs2, funct7) নির্ভুলভাবে আঁকতে পারবেন
  • একটা বাস্তব ৩২-বিট RV32I machine code (hex/binary) হাতে decode করে তার mnemonic আর operand বের করতে পারবেন, এবং উল্টো দিকে একটা mnemonic হাতে encode করে হুবহু hex বের করতে পারবেন
  • কেন rs1/rs2/rd/opcode সবসময় একই বিট-পজিশনে বসে, আর কেন B/J-type-এর immediate বিট 'এলোমেলো' ক্রমে সাজানো — এই দুইটা ডিজাইন সিদ্ধান্তের হার্ডওয়্যার-যুক্তি ব্যাখ্যা করতে পারবেন
  • RISC-V-এর fixed 32-bit encoding-কে x86-64-এর variable-length encoding (১-১৫ byte, prefix, ModRM)-এর সাথে তুলনা করে দেখাতে পারবেন কেন এটা decode ও pipelining-কে সরাসরি প্রভাবিত করে — লেসন ২-এর RISC/CISC আলোচনার সরাসরি প্রয়োগ
  • এই platform-এর RV32I CPU Emulator প্রজেক্টের ভিত্তি হিসেবে একটা ন্যূনতম encoder/decoder নিজে লিখতে পারবেন

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

আগে এটা বুঝি

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-typeRegister-register operationadd, sub, and, or, xor, sll, srl, sra, slt, sltu
I-typeRegister + immediate; load; jump-registeraddi, andi, slti, lw, lb, jalr
S-typeStore (register → memory)sw, sh, sb
B-typeConditional branchbeq, bne, blt, bge, bltu, bgeu
U-type২০-বিট upper immediatelui, auipc
J-typeUnconditional jumpjal

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)│
      └────────────┴────────┴────────┴──────┴────────┴────────┘
Fig 4.1R-type ফিল্ড লেআউট — প্রতিটা ফিল্ডের bit range সঠিকভাবে RV32I spec অনুযায়ী।
ফিল্ডবিটপ্রস্থকী বহন করে
opcode[6:0]কোন broad category — R-type-এর জন্য সবসময় 0110011
rd[11:7]Destination register নম্বর — 0-31, তাই x0x31 (৫ বিট = 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)│
      └─────────────────────┴────────┴──────┴────────┴────────┘
Fig 4.2I-type ফিল্ড লেআউট — rs1/funct3/rd/opcode ঠিক R-type-এর একই বিট-পজিশনে, শুধু rs2+funct7 (12 বিট জায়গা) এখন একটা একক 12-বিট sign-extended immediate।

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)│
      └─────────────┴────────┴────────┴──────┴─────────┴────────┘
Fig 4.3S-type — 12-বিট immediate দুই টুকরায় ভাগ হয়ে গেছে, যাতে rs1/rs2/funct3/opcode তাদের চিরচেনা বিট-পজিশনেই থাকে।

লক্ষ্য করুন 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)FormatInstruction-এর দল
0110011Radd, sub, and, or, xor, sll, srl, sra, slt, sltu
0010011Iaddi, andi, ori, xori, slti, sltiu, slli, srli, srai
0000011Ilb, lh, lw, lbu, lhu (load)
1100111Ijalr
0100011Ssb, sh, sw
1100011Bbeq, bne, blt, bge, bltu, bgeu
0110111Ului
0010111Uauipc
1101111Jjal
1110011ecall, 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-এ সমান্তরালে শুরু হয় — কেউ কারো জন্য অপেক্ষা করে না।

Fig 4.4Fixed field position-এর ফলাফল — register file read আর opcode decode সমান্তরালে শুরু হতে পারে, sequential না।

এটা সরাসরি 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)
funct70000000
rs200111x7 = t2
rs100110x6 = t1
funct3000
rd11100২৮x28 = t3
opcode0110011R-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 সম্পূর্ণ টেবিল

funct3funct7=0000000funct7=0100000
000ADDSUB
001SLL (shift left logical)
010SLT (set less than, signed)
011SLTU (set less than, unsigned)
100XOR
101SRL (shift right logical)SRA (shift right arithmetic)
110OR
111AND

লক্ষ্য করুন — শুধু 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, 5 → 0x00500293 — encode-এর প্রতিটা ধাপ
  1. addi t0, zero, 5Assembly mnemonic — মানুষের পড়ার জন্য
  2. Format = I-type, opcode = 0010011ADDI immediate ALU operation
  3. rd=00101 (x5), rs1=00000 (x0), funct3=000register ফিল্ড ও operation-selector
  4. imm[11:0] = 0000000001015 কে 12-বিট signed-এ প্রকাশ
  5. 32-bit bit string জোড়া লাগানোimm + rs1 + funct3 + rd + opcode = 32 বিট
  6. হেক্স = 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-কে ১৬ বিটে কমপ্রেস করে, নিচে “বাস্তব সিস্টেমে” অংশে ফিরব।)

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

EXPERIMENT

Compiler Explorer-এ RV32I কোড দেখুন ও নিজে decode করুন

ব্রাউজার — godbolt.org· ২০ মিনিট

১. 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-এর সাথে হুবহু মেলে — এনকোডিং তত্ত্ব নয়, বাস্তবে-চলা নিয়ম।

EXPERIMENT

Python দিয়ে decode/encode যাচাই — reference model

Python 3· ১৫ মিনিট
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 নেই।

নিজে বানান

BUILD IT

একটা ন্যূনতম RV32I encoder/decoder — এই platform-এর CPU Emulator প্রজেক্টের প্রথম ইট

Python (বা যেকোনো ভাষা) · ●●●○○
  1. একটা decode(word) ফাংশন লিখুন যেটা opcode বের করে format (R/I/S/B/U/J) নির্ধারণ করে
  2. R-type-এর জন্য funct7/rs2/rs1/funct3/rd সব বের করে একটা dict/struct রিটার্ন করুন
  3. I-type-এর জন্য rs1/funct3/rd/immediate (sign-extended) বের করুন, LOAD ও ADDI দুটোই টেস্ট করুন
  4. S-type-এর জন্য immediate-এর দুই টুকরা (imm[11:5], imm[4:0]) সঠিক ক্রমে জোড়া লাগান
  5. প্রতিটা format-এর জন্য একটা mnemonic টেবিল বানান (opcode+funct3+funct7 → নাম) আর decode(word)-কে human-readable "add t3, t1, t2" স্ট্রিং-এ রূপান্তর করুন
  6. উল্টো দিকে একটা encode(mnemonic, operands) ফাংশন লিখুন — অন্তত ADD, ADDI, LW, SW সমর্থন করুক
  7. এই লেসনের চারটা 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-তে শুরু হয়, instruction N+1 নিশ্চিতভাবে A+4-এ — decode শুরুর আগেই সব instruction-এর ঠিকানা জানা যায়। x86-64-তে instruction N+1-এর ঠিকানা জানতে instruction N-এর 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 RV32Ix86-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, 15t1-এর মানের সাথে 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=00000000x006302B3

লক্ষ্য করুন শুধু 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 যুক্তির টেক্সটবুক-মানের ব্যাখ্যা