সরাসরি পর্বে যান
পর্ব ১৩ · ৬২-এর মধ্যে · মডিউল ২

ALU নকশা — যোগ, বিয়োগ, লজিক আর flag

ALU Design — 1-bit Slices, Subtraction, Set-on-Less-Than, Flags and the Barrel Shifter

Taught in Bangla, this lesson builds an ALU from 1-bit slices (AND, OR, full adder, mux), gets subtraction free with Binvert and CarryIn, adds NOR and set-on-less-than with its overflow trap fixed by SLT = N XOR V and sltu, joins 32 slices under an ALU control table, generates Z, N, C and V flags, contrasts x86/ARM condition codes with RISC-V branches, designs a barrel shifter, and verifies everything in simulators and C.

এই পর্বে যা শিখবেন

  • CPU-তে ALU-র কাজ আর তার interface — ইনপুট, control আর আউটপুট
  • AND, OR, full adder আর mux দিয়ে ১-বিট ALU slice; Ainvert আর Binvert দিয়ে বিয়োগ ও NOR
  • Set-on-less-than (slt) কীভাবে কাজ করে, overflow হলে কেন ভুল হয় আর SLT = N ⊕ V কেন ঠিক — প্রমাণসহ
  • ৩২টি slice জুড়ে ripple ALU, ALU control-এর টেবিল, আর Z, N, C, V flag তৈরির circuit
  • x86/ARM-এর condition code বনাম RISC-V-এর flag-হীন শাখা; barrel shifter-এর নকশা; বাস্তবের ALU আর 74181-এর ইতিহাস

১ · ALU কী করে — CPU-র ক্যালকুলেটর আর তার interface

পর্ব ০১-এ (fetch–decode–execute চক্র) দেখেছেন, CPU বারবার তিনটি কাজ করে: মেমরি থেকে instruction আনে, সেটি বোঝে, তারপর কাজটি করে। এই “কাজটি করা” ধাপের বেশিরভাগ হিসাব যে অংশে হয়, তার নাম ALU — Arithmetic Logic Unit. add, sub, and, or, তুলনা — সবই ALU-র কাজ। এমনকি load বা store instruction-এও মেমরির ঠিকানা (base + offset) যোগ করে ALU.

দোকানের একটি সাধারণ ক্যালকুলেটরের কথা ভাবুন। আপনি দুটি সংখ্যা টাইপ করেন, তারপর একটি বোতাম চাপেন — +, −, × বা ÷. ক্যালকুলেটরের ভেতরের circuit একই, শুধু বোতাম ঠিক করে দেয় কোন কাজটি হবে। পর্দায় ফল আসে, আর কোণে ছোট্ট একটি “E” বা “−” চিহ্ন জ্বলে ওঠে। ALU ঠিক এমন: দুটি সংখ্যা (A, B), একটি “বোতাম” (operation select), একটি ফল, আর কয়েকটি ছোট্ট সংকেত-বাতি — যাদের বলে flag.

ALU-র interface (বাইরের সংযোগ) মনে রাখুন এভাবে:

সংযোগদিকপ্রস্থ (৩২-বিট ALU)কাজ
A, Bইনপুট৩২ বিট করেদুটি operand — সাধারণত register file থেকে, বা B-র জায়গায় instruction-এর immediate
ALU controlইনপুটকয়েক বিট (যেমন ৪)কোন কাজ হবে — AND, OR, add, sub, slt…; আসে control unit থেকে
Resultআউটপুট৩২ বিটফল — register-এ লেখা হয় বা মেমরির ঠিকানা হিসেবে যায়
Flag (Z, N, C, V)আউটপুট১ বিট করেফল সম্পর্কে তথ্য: শূন্য কি না, ঋণাত্মক কি না, carry বেরোল কি না, signed overflow হলো কি না

একটি গুরুত্বপূর্ণ কথা: ALU একটি combinational circuit — এতে কোনো মেমরি নেই, ঘড়ির (clock) অপেক্ষাও নেই। ইনপুট বদলালে কিছুক্ষণ পরে (gate-গুলোর delay পেরিয়ে) আউটপুট আপনা থেকে বদলে যায়। ফল ধরে রাখার কাজ register-এর, যা মডিউল ৩-এ আসবে। আর পুরো CPU-তে ALU কোথায় বসে, register file আর মেমরির সাথে কীভাবে জোড়া থাকে — সেই datapath দেখবেন পর্ব ৩২ আর ৩৩-এ।

মূল ধারণা

ALU সব কাজ একসাথে করে, তারপর একটি বেছে নেয়। ভেতরে AND, OR আর adder একই সময়ে চলে; শেষে একটি multiplexer (mux) control সংকেত দেখে যেকোনো একটির ফল বাইরে পাঠায়। Hardware-এ “আগে দেখি কোন কাজ, তারপর করি” নয় — “সব করি, দরকারিটা রাখি।”

২ · ১-বিটের ALU slice — AND, OR আর full adder, mux দিয়ে বাছাই

৩২-বিটের ALU সরাসরি আঁকা কঠিন। Patterson ও Hennessy-র পাঠ্যবইয়ের বিখ্যাত পদ্ধতি হলো আগে এক বিটের একটি ALU বানানো — একে বলে ALU slice বা bit-slice — তারপর সেটির ৩২টি কপি পাশাপাশি বসানো। গার্মেন্টস কারখানার লাইনে যেমন একই রকম অনেকগুলো মেশিন পাশাপাশি থাকে, প্রতিটি একটি করে অংশ সেলাই করে, এখানেও প্রতিটি slice একটি করে বিট সামলায়।

বিট i-এর slice-এ ইনপুট ai, bi আর ডান পাশের slice থেকে আসা CarryIn. ভেতরে তিনটি অংশ:

  • AND gate: ai · bi
  • OR gate: ai + bi (এই পর্বে Boolean-এ + মানে OR, ⊕ মানে XOR, আর A′ মানে NOT A; কোডে ~, |, ^)
  • Full adder: sum = a ⊕ b ⊕ cin, CarryOut = a·b + a·cin + b·cin — এর gate-স্তরের নকশা পর্ব ১১-এ (Adder)

তিনটির আউটপুট যায় একটি mux-এ। Mux-এর select লাইন হলো ২-বিটের Operation সংকেত: 00 হলে AND, 01 হলে OR, 10 হলে adder-এর sum. (Mux কীভাবে বানানো হয়, তা পর্ব ১২-এ — Multiplexer, decoder আর encoder.) পুরো slice-টি, পরের দুই অংশের বাড়তি অংশসহ, এক ছবিতে:

১-বিট ALU slice (Patterson–Hennessy ধাঁচে)

Ainvert, Binvert, AND, OR, full adder, Less — and a 4-to-1 mux chooses the result

 Ainvert ─┐                      Operation (2 bits)
          ▼                               │
 a ──►[ a or a' ]──┬──►[ AND ]──► 0 ┐     ▼
                   │                ├──[ 4:1 ]──► Result
 b ──►[ b or b' ]──┼──►[ OR  ]──► 1 ┤  [ MUX ]
          ▲        │                │
 Binvert ─┘        └──►[ FULL ]──► 2 ┤
                CarryIn ─►[ADDER]   │
                           │  Less ► 3 ┘
                           ▼
                        CarryOut
ছবি ১৩.১ — Operation = 00 → AND, 01 → OR, 10 → sum, 11 → Less. “a or a′” আর “b or b′” আসলে একটি করে XOR gate (বা ২:১ mux).

এখনো এই slice শুধু তিনটি কাজ জানে: AND, OR আর যোগ। পরের দুটি অংশে মাত্র তিনটি ছোট সংযোজনে আমরা পাব বিয়োগ, NOR আর তুলনা।

৩ · বিয়োগ বিনা খরচে — Binvert আর CarryIn = 1

পর্ব ০৫-এ (two's complement) দেখেছেন, −B = B′ + 1, অর্থাৎ সব বিট উল্টে ১ যোগ করলে B-এর ঋণাত্মক পাওয়া যায়। তাহলে

A − B = A + (−B) = A + B′ + 1

এটি hardware-এ কতটা সস্তা, দেখুন। B′ পেতে প্রতিটি slice-এর b ইনপুটে একটি XOR gate বসানো হয়: b ⊕ Binvert. Binvert = 0 হলে b যেমন আছে তেমন যায়, Binvert = 1 হলে উল্টে যায়। আর “+ 1”? Adder-এর একদম ডানের slice (বিট ০)-এর একটি CarryIn ইনপুট আছে, যা যোগের সময় এমনিতেই ০ থাকে। সেখানে ১ দিলেই +1 হয়ে গেল — একটি বাড়তি adder-ও লাগল না।

তাই Patterson–Hennessy-র নকশায় একটিই সংকেত দুই জায়গায় যায়: Bnegate = সব slice-এর Binvert + বিট ০-এর CarryIn. Bnegate = 1 মানে “B-কে negate করা হবে”। বীজগণিতে যাচাই (n বিটে, mod 2n):

A + B′ + 1 = A + (2n − 1 − B) + 1 = 2n + (A − B) ≡ A − B (mod 2n)

এখান থেকে একটি বাড়তি তথ্যও বেরোয়, যা অংশ ৬-এ খুব কাজে লাগবে: যোগফল 2n + (A − B). A ≥ B (unsigned) হলে এটি 2n-এর সমান বা বেশি, তাই MSB থেকে carry বেরোয় (CarryOut = 1). A < B হলে যোগফল 2n-এর কম, carry বেরোয় না (CarryOut = 0) — অর্থাৎ বিয়োগে borrow লেগেছে। বিয়োগের পর CarryOut = NOT borrow.

নিচের stepper-এ ৪-বিটের ALU-তে 5 − 3 ধাপে ধাপে চলছে। “পরে” চাপুন:

Controlsub = Ainvert 0, Bnegate 1, Operation 10
A        = 0101   (5)
B        = 0011   (3)
Ainvert  = 0
Bnegate  = 1   -> every Binvert = 1, CarryIn(bit 0) = 1
Operation= 10  -> mux picks the adder's sum
XORপ্রতিটি slice-এ b ⊕ 1
bit        3 2 1 0
b          0 0 1 1
b xor 1    1 1 0 0   = B' (one's complement of 3)
CarryIn(0) = 1
Slice 01 + 0 + 1
bit        3 2 1 0
carry-in   . . . 1
a          0 1 0 1
b'         1 1 0 0
result     . . . 0   carry to bit 1 = 1
Slice 10 + 0 + 1
bit        3 2 1 0
carry-in   . . 1 1
a          0 1 0 1
b'         1 1 0 0
result     . . 1 0   carry to bit 2 = 0
Slice 21 + 1 + 0
bit        3 2 1 0
carry-in   . 0 1 1
a          0 1 0 1
b'         1 1 0 0
result     . 0 1 0   carry to bit 3 = 1
Slice 30 + 1 + 1, তারপর flag
bit        3 2 1 0
carry-in   1 0 1 1
a          0 1 0 1
b'         1 1 0 0
result     0 0 1 0   = 2     CarryOut = 1
V = Cin(bit3) xor Cout = 1 xor 1 = 0
  1. Control সংকেত বসানো। বিয়োগের জন্য control unit পাঠায় Ainvert = 0, Bnegate = 1, Operation = 10. Bnegate একসাথে দুটি কাজ করে: সব slice-এর B উল্টায়, আর বিট ০-এর CarryIn-এ ১ দেয়।
  2. B উল্টে গেল। প্রতিটি slice-এর XOR gate b ⊕ 1 = b′ দেয়। 0011 হয়ে গেল 1100. এর সাথে CarryIn-এর ১ মিলে আসলে যোগ হবে 1100 + 1 = 1101, যা ৪-বিটে −3.
  3. Slice 0. a0 = 1, b′0 = 0, CarryIn = 1. 1 + 0 + 1 = 10 (binary), তাই ফলের বিট ০ = 0, আর বিট ১-এ carry যায় ১.
  4. Slice 1. a1 = 0, b′1 = 0, carry 1. 0 + 0 + 1 = 1 — ফলের বিট ১ = 1, পরের slice-এ carry 0. Carry ডান থেকে বাঁয়ে এক slice করে এগোচ্ছে — এটাই ripple.
  5. Slice 2. a2 = 1, b′2 = 1, carry 0. 1 + 1 + 0 = 10 — ফলের বিট ২ = 0, আর বিট ৩-এ carry ১.
  6. Slice 3 আর ফল। 0 + 1 + 1 = 10 — ফলের বিট ৩ = 0, CarryOut = 1. পুরো ফল 0010 = 2, অর্থাৎ 5 − 3 = 2. CarryOut = 1 মানে borrow লাগেনি (unsigned-এ 5 ≥ 3), আর MSB-তে ঢোকা ও বেরোনো carry দুটিই ১, তাই V = 0 — signed অর্থেও ফল ঠিক।

এজন্যই বলা হয় two's complement বিয়োগকে “বিনা খরচে” দেয়। Sign-magnitude-এ বিয়োগের জন্য আলাদা subtractor আর চিহ্ন তুলনার circuit লাগত (পর্ব ০৫-এর অংশ ৩)। Two's complement-এ লাগে প্রতি বিটে একটি XOR আর একটি তার (CarryIn) — বাকিটা পুরোনো adder-ই করে দেয়। পর্ব ২০-এ একই চিন্তাকে software আর ISA-র দিক থেকে (adder-subtractor, status flag) আবার দেখবেন।

৪ · Ainvert, NOR আর set-on-less-than — overflow-এর সূক্ষ্ম ফাঁদ

Ainvert দিয়ে NOR

B-এর মতো A-এর ইনপুটেও একটি XOR (বা ২:১ mux) বসানো হয়, যার সংকেতের নাম Ainvert. কেন? De Morgan-এর সূত্র (পর্ব ০৭) মনে করুন: (A + B)′ = A′ · B′. অর্থাৎ A আর B দুটিকেই উল্টে AND করলে পাওয়া যায় NOR. তাই Ainvert = 1, Bnegate = 1 আর Operation = 00 (AND) দিলে ALU NOR করে — নতুন কোনো gate ছাড়াই। একই কৌশলে Ainvert = 1, Binvert = 1, Operation = 01 (OR) দিলে পাওয়া যায় A′ + B′ = (A·B)′, অর্থাৎ NAND. (Bnegate একই সাথে CarryIn-এও ১ দেয়, কিন্তু AND বা OR বেছে নিলে adder-এর ফল mux-এ বাদ পড়ে, তাই এতে ক্ষতি নেই।)

Set-on-less-than — তুলনার ফল একটি সংখ্যা হিসেবে

MIPS আর RISC-V-তে একটি instruction আছে slt rd, rs1, rs2 — set-on-less-than: rs1 < rs2 (signed) হলে rd = 1, নাহলে rd = 0. এর ফল সবসময় 0000…0001 বা 0000…0000. অর্থাৎ উপরের ৩১টি বিট সবসময় ০, শুধু বিট ০-এ তুলনার উত্তর।

এটি ALU-তে বানানোর কৌশল চমৎকার। Mux-এর চতুর্থ ইনপুট (Operation = 11) হলো Less. বিট ১ থেকে ৩১-এর slice-এ Less-এ সরাসরি ০ বাঁধা থাকে। বিট ০-এর Less আসে MSB-র slice থেকে একটি তারে, যার নাম Set. আর ALU সেই সময় ভেতরে A − B হিসাব করে (Bnegate = 1). ভাবনাটা সরল: A < B হলে A − B ঋণাত্মক, তাই ফলের চিহ্ন-বিট (MSB) ১ — এটাই Set. সুতরাং slt-এর control: Ainvert 0, Bnegate 1, Operation 11 → 0111.

কিন্তু overflow হলে চিহ্ন-বিট মিথ্যা বলে। ৮ বিটে A = −128, B = 1 নিন। আসল A − B = −129, যা ৮ বিটের সীমার (−128 … 127) বাইরে। Adder wrap করে দেয় 0111 1111 = +127 — চিহ্ন-বিট ০! সরল নিয়মে slt বলবে “−128 < 1 মিথ্যা”, যা স্পষ্টতই ভুল। পাঠ্যবইয়ের প্রথম সরল ছবিতে Set সরাসরি adder-এর MSB থেকে নেওয়া হয়; সঠিক নকশায় এর সাথে overflow মেলাতে হয়।

সঠিক নিয়ম: SLT = N ⊕ V — প্রমাণ

ধরুন n বিটের adder A − B-এর যে ফল দেয় তার চিহ্ন-বিট N, আর signed overflow flag V. আসল (অসীম নির্ভুল) পার্থক্য D = A − B. আমরা চাই: D < 0 হলে ১.

  • V = 0 হলে: D সীমার ভেতরে, তাই adder-এর ফলই D-এর সঠিক two's complement রূপ। তখন D < 0 ⇔ N = 1. অর্থাৎ উত্তর = N = N ⊕ 0.
  • V = 1 হলে: D সীমার বাইরে। পর্ব ০৫-এ দেখেছেন, overflow হয় কেবল তখনই যখন ফল ঠিক এক “পাক” (2n) ঘুরে উল্টো চিহ্নের অর্ধেকে পড়ে — তাই ফলের চিহ্ন আসল চিহ্নের ঠিক উল্টো। উত্তর = N′ = N ⊕ 1.

দুই ক্ষেত্র মিলিয়ে Less = N ⊕ V. Hardware-এ খরচ মাত্র একটি XOR gate: MSB slice-এর Set = sumn−1 ⊕ V, আর V আসে V = cn ⊕ cn−1 থেকে (cn = MSB থেকে বেরোনো carry, cn−1 = MSB-তে ঢোকা carry). ARM আর x86-এর “signed less than” শর্তও ঠিক এটাই: N ≠ V (x86-এ SF ≠ OF) — অংশ ৭-এ আবার আসবে।

ইনপুটcontrol 0111 (slt)
A  = 1000 0000   (-128)
B  = 0000 0001   (+1)
ALU computes A + B' + 1 inside
ভেতরের বিয়োগA + B′ + 1
   1000 0000     A
 + 1111 1110     B'
 +         1     CarryIn
 -----------
 1 0111 1111     sum = +127 (wrapped), CarryOut = 1
Carry গোনাMSB-তে ঢোকা আর বেরোনো
carry into bit 7  c7 = 0
carry out of bit 7 c8 = 1
V = c8 xor c7 = 1     (overflow!)
N = sum bit 7  = 0
Set বিটসরল বনাম সঠিক
naive  Set = N       = 0   -> result 0000 0000  (WRONG)
correct Set = N xor V = 0 xor 1 = 1
result = 0000 0001   (-128 < 1 is true)
  1. দুটি operand. A = −128 (৮ বিটের সবচেয়ে ছোট signed সংখ্যা), B = +1. Control 0111: ALU ভেতরে A − B হিসাব করবে, আর mux বেছে নেবে Less ইনপুট।
  2. ভেতরের বিয়োগ। B′ = 1111 1110, CarryIn = 1. যোগফল 1 0111 1111. ৮ বিট রাখলে ফল 0111 1111 = +127, অথচ আসল উত্তর −129 — ৮ বিটে ধরে না, তাই wrap হয়েছে।
  3. Carry দুটি দেখা। বিট ০ থেকে ৬ পর্যন্ত কোনো carry ওঠে না (বিট ০-এ 0 + 0 + 1 = 1, তারপর প্রতি ঘরে 0 + 1 + 0 = 1), তাই MSB-তে ঢোকা carry c7 = 0. MSB-তে 1 + 1 + 0 = 10, তাই বেরোনো carry c8 = 1. V = 1 ⊕ 0 = 1 — signed overflow; আর N = 0.
  4. Set বিট। সরল নিয়মে Set = N = 0, ফল 0 — ভুল। সঠিক নিয়মে Set = N ⊕ V = 1, ফল 0000 0001 — −128 সত্যিই 1-এর চেয়ে ছোট। উল্টো ফাঁদও আছে: 127 slt −1-এ পার্থক্য +128 wrap করে −128 হয়, N = 1, V = 1, সঠিক উত্তর 0.

Unsigned তুলনা — sltu

RISC-V-তে sltu (set-on-less-than unsigned) দুটি সংখ্যাকে unsigned হিসেবে তুলনা করে। অংশ ৩-এর হিসাব মনে করুন: A + B′ + 1-এর CarryOut = 1 ঠিক তখনই, যখন A ≥ B (unsigned). তাই sltu-এর ফল = NOT CarryOut — অর্থাৎ borrow লেগেছে কি না। চিহ্ন-বিট বা V এখানে অপ্রাসঙ্গিক। একই বিট-প্যাটার্নে দুটি তুলনা ভিন্ন উত্তর দিতে পারে: ৮ বিটে 1111 1111 আর 0000 0001 — signed-এ −1 < 1 সত্য, unsigned-এ 255 < 1 মিথ্যা।

৫ · ৩২টি slice জুড়ে ৩২-বিট ALU আর ALU control-এর টেবিল

এবার slice-গুলো জোড়া যাক। বিট i-এর CarryOut যায় বিট i + 1-এর CarryIn-এ, ঠিক পর্ব ১১-এর ripple carry adder-এর মতো। সব slice-এর Ainvert একটি তারে, সব Binvert আরেকটি তারে (যা বিট ০-এর CarryIn-ও), আর Operation-এর দুটি তার সব slice-এ একসাথে যায়। শেষ slice (বিট ৩১) একটু বিশেষ: এতে বাড়তি থাকে Set আউটপুট আর overflow নির্ণয়ের gate.

Ainvert  Bnegate  Operation   (each goes to all 32 slices)
   │        │        │
 a0,b0 ─►[ ALU0  ]◄─ CarryIn = Bnegate     Less0 ◄── Set (from ALU31)
            │ c1                           ──► Result0
 a1,b1 ─►[ ALU1  ]    Less1 = 0            ──► Result1
            │ c2
   ...      ...       (carry ripples upward)
            │ c31
 a31,b31►[ ALU31 ]    Less31 = 0           ──► Result31
            │ c32       Set = sum31 xor V,  Overflow V = c32 xor c31
            ▼
         CarryOut
 Zero = NOR(Result31 ... Result0)

সমস্যা একটিই: গতি। CarryIn থেকে ফল পেতে carry-কে ৩২টি slice পেরোতে হয়। প্রতিটি full adder-এর carry পথে মোটামুটি দুই স্তর gate থাকলে সবচেয়ে খারাপ ক্ষেত্রে প্রায় ৬৪ স্তরের delay, তারপর mux আর Set-এর পথ। বাস্তব CPU তাই ripple ব্যবহার করে না; ব্যবহার করে carry-lookahead বা parallel-prefix adder, যেখানে carry প্রায় log2 n স্তরে হিসাব হয় — বিস্তারিত পর্ব ১১-এ। ALU-র বাকি গঠন একই থাকে; শুধু adder অংশটি দ্রুত adder দিয়ে বদলানো হয়।

ALU control-এর টেবিল

চারটি control বিট (Ainvert, Bnegate, Operation1, Operation0) একসাথে একটি ৪-বিটের কোড। Patterson–Hennessy-র MIPS সংস্করণে এই কোডগুলোই ব্যবহার হয়, আর বিশ্ববিদ্যালয় ও চাকরির পরীক্ষায় এই টেবিল বারবার আসে:

ALU controlAinvertBnegateOperationকাজকেন
00000000ANDa · b
00010001ORa + b
00100010adda + b + 0
01100110subtracta + b′ + 1
01110111set-on-less-thanA − B-এর N ⊕ V বিট ০-এ
11001100NORa′ · b′ = (a + b)′

RISC-V-তে কী? RV32I-এর ALU-কে মূলত এই কাজগুলো করতে হয়: add, sub, and, or, xor, sll, srl, sra, slt, sltu (আর এদের immediate রূপ, যেমন addi, slti). লক্ষ করুন, RISC-V-তে nor instruction নেই (MIPS-এ ছিল), কিন্তু xor আর shift আছে — তাই RISC-V-এর ALU-তে XOR আর shifter-এর পথ লাগে। আর ALU control-এর ৪-বিট কোড ISA-র অংশ নয়; এটি CPU-র ভেতরের নকশাকারের পছন্দ (implementation-specific). পাঠ্যবইয়ের RISC-V সংস্করণ (আর এই কোর্সের পর্ব ৩২–৩৩) প্রথম চারটি কোড একই রাখে — 0000 AND, 0001 OR, 0010 add, 0110 sub; বাস্তব কোনো chip-এর কোড আলাদা হতে পারে।

নিচের simulator এই পুরো নকশাটি ৮ বিটে চালায়: প্রতিটি slice-এ দুটি XOR, AND, OR, full adder আর mux; প্রথমে carry chain চলে, তারপর MSB থেকে Set = sum7 ⊕ V নিয়ে বিট ০-এর Less-এ দেওয়া হয়। তিন জোড়া operand-এ ছয়টি কাজ — Run চাপুন, তারপর নিজের সংখ্যা বসিয়ে দেখুন:

// 8-bit ripple ALU built from 1-bit slices (Patterson-Hennessy style).
// control = Ainvert, Bnegate, Operation (00 AND, 01 OR, 10 add, 11 less)
const OPS = { AND: "0000", OR: "0001", add: "0010", sub: "0110", slt: "0111", NOR: "1100" };
function slice(a, b, cin, less, ainv, bneg, op) {
  const x = a ^ ainv, y = b ^ bneg;               // the two inverters (XOR)
  const sum = x ^ y ^ cin, cout = (x & y) | (x & cin) | (y & cin);
  return { r: [x & y, x | y, sum, less][op], sum, cout };
}
function alu8(A, B, ctrl) {
  const ainv = +ctrl[0], bneg = +ctrl[1], op = parseInt(ctrl.slice(2), 2);
  let c = bneg, cins = [], sum7 = 0, res = [];   // CarryIn of bit 0 = Bnegate
  for (let i = 0; i < 8; i++) {                   // pass 1: the carry chain
    cins.push(c);
    const s = slice((A >> i) & 1, (B >> i) & 1, c, 0, ainv, bneg, op);
    sum7 = s.sum; c = s.cout;
  }
  const V = cins[7] ^ c, set = sum7 ^ V;          // corrected SLT bit
  for (let i = 0; i < 8; i++)                     // pass 2: mux picks result
    res.push(slice((A >> i) & 1, (B >> i) & 1, cins[i], i === 0 ? set : 0,
                   ainv, bneg, op).r);
  const R = res.reduce((v, bit, i) => v | (bit << i), 0);
  return { R, Z: +(R === 0), N: R >> 7, C: c, V, naive: sum7 };
}
const s8 = p => (p & 0x80 ? p - 256 : p), b8 = p => p.toString(2).padStart(8, "0");
for (const [x, y] of [[100, 50], [-128, 1], [5, 5]]) {
  console.log(`A=${x} (${b8(x & 255)})  B=${y} (${b8(y & 255)})`);
  for (const [name, ctrl] of Object.entries(OPS)) {
    const { R, Z, N, C, V, naive } = alu8(x & 255, y & 255, ctrl);
    const cv = ctrl[2] === "1" ? `C=${C} V=${V}` : "C=- V=-";  // adder ops only
    const note = name === "slt" ? `  naive sign bit=${naive}` : "";
    console.log(`  ${name.padEnd(3)} ${ctrl}  ${b8(R)} = ${String(s8(R)).padStart(4)}  Z=${Z} N=${N} ${cv}${note}`);
  }
}

আউটপুট (নির্ধারিত — প্রতিবার একই):

A=100 (01100100)  B=50 (00110010)
  AND 0000  00100000 =   32  Z=0 N=0 C=- V=-
  OR  0001  01110110 =  118  Z=0 N=0 C=- V=-
  add 0010  10010110 = -106  Z=0 N=1 C=0 V=1
  sub 0110  00110010 =   50  Z=0 N=0 C=1 V=0
  slt 0111  00000000 =    0  Z=1 N=0 C=1 V=0  naive sign bit=0
  NOR 1100  10001001 = -119  Z=0 N=1 C=- V=-
A=-128 (10000000)  B=1 (00000001)
  AND 0000  00000000 =    0  Z=1 N=0 C=- V=-
  OR  0001  10000001 = -127  Z=0 N=1 C=- V=-
  add 0010  10000001 = -127  Z=0 N=1 C=0 V=0
  sub 0110  01111111 =  127  Z=0 N=0 C=1 V=1
  slt 0111  00000001 =    1  Z=0 N=0 C=1 V=1  naive sign bit=0
  NOR 1100  01111110 =  126  Z=0 N=0 C=- V=-
A=5 (00000101)  B=5 (00000101)
  AND 0000  00000101 =    5  Z=0 N=0 C=- V=-
  OR  0001  00000101 =    5  Z=0 N=0 C=- V=-
  add 0010  00001010 =   10  Z=0 N=0 C=0 V=0
  sub 0110  00000000 =    0  Z=1 N=0 C=1 V=0
  slt 0111  00000000 =    0  Z=1 N=0 C=1 V=0  naive sign bit=0
  NOR 1100  11111010 =   -6  Z=0 N=1 C=- V=-

প্রথম জোড়ায় add দেখায় 100 + 50 = −106 সঙ্গে V=1 — আসল 150 ৮-বিট signed-এ ধরে না; unsigned চোখে 150 ঠিক, তাই C=0. দ্বিতীয় জোড়াটিই এই অংশের মূল কথা: sub দেয় 127 সঙ্গে V=1 (−129 সীমার বাইরে), তাই slt-এ সরল চিহ্ন-বিট 0 হলেও সংশোধিত ফল 00000001 — সঠিক। তৃতীয় জোড়ায় 5 − 5 = 0, তাই Z=1; এখানে C=1 মানে borrow লাগেনি। খেয়াল করুন, slt সারির Z আর N এসেছে slt-এর ০/১ ফল থেকে, আর C ও V ভেতরের বিয়োগ থেকে; বাস্তবের RISC-V-তে slt কোনো flag রাখেই না। আর NOR-এর ফল 10001001 হলো OR-এর ফল 01110110-এর ঠিক উল্টো।

৬ · Flag — Zero, Negative, Carry আর Overflow

ফলের পাশাপাশি ALU চারটি ১-বিটের তথ্য দেয়। ARM এদের একসাথে বলে NZCV; x86-এ নাম ZF, SF, CF, OF. এরা CPU-র একটি status flag register-এ জমা হতে পারে (x86, ARM), অথবা একদম না-ও জমতে পারে (RISC-V — অংশ ৭)। প্রতিটির circuit:

FlagমানেCircuitকোন চশমায় কাজের
Z (Zero)ফল পুরোপুরি ০সব ফল-বিটের NOR: Z = (r31 + … + r0)′ — বাস্তবে OR-এর গাছ, শেষে NOTদুটিতেই (সমান কি না)
N (Negative)ফল ঋণাত্মকশুধু ফলের MSB-র তার: N = rn−1Signed
C (Carry)যোগে carry বেরোল / বিয়োগে borrow-এর তথ্যMSB slice-এর CarryOut (x86-এ বিয়োগে উল্টো করে রাখা হয় — নিচে দেখুন)Unsigned
V (Overflow)signed ফল সীমার বাইরেV = cn ⊕ cn−1 — একটি XORSigned

বিয়োগের পর C — দুই ঘরানা। দুটি বড় আর্কিটেকচার বিয়োগের পর carry flag-কে উল্টো অর্থে রাখে, আর এটি পরীক্ষা ও ইন্টারভিউর প্রিয় ফাঁদ। ARM (আর পুরোনো 6502-এর মতো কিছু নকশা) adder-এর CarryOut যেমন আছে তেমন রাখে: SUBS/CMP-এর পর C = 1 মানে borrow হয়নি (unsigned A ≥ B). x86 উল্টো করে রাখে: SUB/CMP-এর পর CF = 1 মানে borrow হয়েছে (unsigned A < B). Hardware-এ পার্থক্য শুধু একটি NOT gate; কিন্তু assembly পড়ার সময় কোন CPU, তা না জানলে C-এর অর্থ উল্টে যায়। যোগের বেলায় দুটিতেই C = CarryOut. (Logic instruction বা shift-এর পর C আর V কী হয়, তা আবার প্রতিটি ISA-তে আলাদা — manual দেখে নিতে হয়।)

Z flag-এর circuit নিয়ে একটি কথা: ৩২টি বিটের NOR একটি বিশাল gate হিসেবে বানানো যায় না (CMOS-এ বেশি ইনপুটের gate ধীর, পর্ব ০৮)। তাই ২ বা ৪ ইনপুটের OR gate-এর একটি গাছ বানানো হয় — ৩২ বিটে ২-ইনপুট gate দিয়ে log2 32 = ৫ স্তর — শেষে একটি NOT. এবার একটি বিয়োগে চারটি flag একটি একটি করে তৈরি হতে দেখুন:

বিয়োগA + B′ + 1
A  = 0111 0000   0x70  (unsigned 112, signed +112)
B  = 1001 0000   0x90  (unsigned 144, signed -112)
B' = 0110 1111
   0111 0000 + 0110 1111 + 1 = 1110 0000  (0xE0)
carries: c7 (into bit 7) = 1, c8 (out) = 0
Zসব বিটের NOR
result  1110 0000
OR of all bits = 1
Z = NOT 1 = 0      (result is not zero)
NMSB
result  1110 0000
N = bit 7 = 1     (as signed: -32)
CCarryOut = c8
CarryOut c8 = 0
ARM  C  = 0   (0 = a borrow happened)
x86  CF = 1   (1 = a borrow happened)
unsigned check: 112 < 144, so a borrow is right
Vc8 ⊕ c7
V = c8 xor c7 = 0 xor 1 = 1
signed check: 112 - (-112) = 224 > 127   -> overflow
result -32 is wrong as a signed answer
ব্যবহারদুই তুলনা
signed   A < B ?  N xor V = 1 xor 1 = 0  -> no  (112 > -112)
unsigned A < B ?  borrow (ARM C = 0)  = yes (112 < 144)
  1. বিয়োগটি চালানো। A = 0x70, B = 0x90. ALU যোগ করে A + B′ + 1 = 0111 0000 + 0110 1111 + 1 = 1110 0000 (0xE0). নিচের চার বিট থেকে ওঠা carry বিট ৪, ৫, ৬ হয়ে MSB-তে ঢোকে (c7 = 1), কিন্তু MSB-তে 0 + 0 + 1 = 1, তাই বাইরে carry বেরোয় না (c8 = 0).
  2. Z flag. ফলের সব বিট OR করলে ১ (কয়েকটি বিট ১), NOT করলে Z = 0 — ফল শূন্য নয়, অর্থাৎ A ≠ B.
  3. N flag. ফলের MSB ১, তাই N = 1. Signed হিসেবে 0xE0 = −32.
  4. C flag — দুই ঘরানা। CarryOut = 0. ARM এটিকেই C হিসেবে রাখে (C = 0 মানে borrow হয়েছে); x86 উল্টে রাখে CF = 1 (borrow হয়েছে)। দুটির অর্থ একই: unsigned 112 < 144.
  5. V flag. V = c8 ⊕ c7 = 0 ⊕ 1 = 1. আসল signed পার্থক্য 112 − (−112) = 224, যা ৮ বিটের সীমা 127 ছাড়িয়েছে — তাই −32 signed অর্থে ভুল ফল।
  6. Flag থেকে সিদ্ধান্ত। Signed প্রশ্ন “A < B?” — N ⊕ V = 0, তাই না; সত্যিই +112 > −112. Unsigned প্রশ্ন — borrow হয়েছে, তাই হ্যাঁ; সত্যিই 112 < 144. একই বিয়োগ, একই চার flag, দুই প্রশ্নের দুই সঠিক উত্তর।

৭ · Flag দিয়ে শাখা — x86/ARM-এর condition code বনাম RISC-V-এর compare-and-branch

if (a < b) লিখলে CPU-কে দুটি কাজ করতে হয়: তুলনা আর শাখা (branch). x86 আর ARM-এ এই দুটি আলাদা instruction. প্রথমে cmp a, b — যা ভেতরে a − b হিসাব করে, ফল ফেলে দেয়, শুধু flag রাখে। তারপর একটি শর্তসাপেক্ষ লাফ, যা flag পড়ে সিদ্ধান্ত নেয়। শর্তগুলোকে বলে condition code:

প্রশ্ন (cmp a, b-এর পর)ARMx86Flag-এর শর্ত (ARM ধাঁচে)
a = bEQjeZ = 1
a ≠ bNEjneZ = 0
a < b (signed)LTjlN ≠ V (x86: SF ≠ OF)
a > b (signed)GTjgZ = 0 এবং N = V
a < b (unsigned)LO (= CC)jbARM: C = 0; x86: CF = 1
a > b (unsigned)HIjaARM: C = 1 এবং Z = 0; x86: CF = 0 এবং ZF = 0

লক্ষ করুন x86-এর নামকরণ: signed তুলনায় “less/greater” (jl, jg), unsigned-এ “below/above” (jb, ja). C কোডে int আর unsigned তুলনার জন্য compiler ঠিক এই দুই পরিবার থেকে instruction বাছে। নিচের simulator চারটি CMP চালিয়ে ছয়টি শর্ত হিসাব করে — দেখুন কোথায় signed আর unsigned উত্তর ভিন্ন হয়:

// CMP a, b = compute a - b, keep only the flags (ARM-style C = NOT borrow).
function cmp8(a, b) {
  const raw = a + ((~b) & 0xFF) + 1;              // a + ~b + 1
  const r = raw & 0xFF, C = raw >> 8;             // C = carry out = no borrow
  const sa = a >> 7, sb = b >> 7, sr = r >> 7;
  const V = +(sa !== sb && sr !== sa);            // signs differ, result flips
  return { r, Z: +(r === 0), N: sr, C, V };
}
const cond = {                                    // ARM name (x86 jump)
  "EQ (je)": f => f.Z === 1,
  "NE (jne)": f => f.Z === 0,
  "LT (jl)": f => f.N !== f.V,                    // signed less than
  "LO (jb)": f => f.C === 0,                      // unsigned lower = borrow
  "GT (jg)": f => f.Z === 0 && f.N === f.V,
  "HI (ja)": f => f.C === 1 && f.Z === 0,
};
const hex = v => "0x" + v.toString(16).toUpperCase().padStart(2, "0");
const pairs = [[0xFF, 0x01], [0x01, 0xFF], [0x80, 0x7F], [0x05, 0x05]];
for (const [a, b] of pairs) {
  const f = cmp8(a, b), sa = a > 127 ? a - 256 : a, sb = b > 127 ? b - 256 : b;
  console.log(`CMP ${hex(a)}, ${hex(b)}  (signed ${sa} vs ${sb}, unsigned ${a} vs ${b})`);
  console.log(`  a-b=${hex(f.r)}  Z=${f.Z} N=${f.N} C=${f.C} V=${f.V}  x86 CF=${1 - f.C}`);
  console.log("  " + Object.entries(cond).map(([k, t]) => `${k}:${t(f) ? "T" : "F"}`).join("  "));
}

আউটপুট:

CMP 0xFF, 0x01  (signed -1 vs 1, unsigned 255 vs 1)
  a-b=0xFE  Z=0 N=1 C=1 V=0  x86 CF=0
  EQ (je):F  NE (jne):T  LT (jl):T  LO (jb):F  GT (jg):F  HI (ja):T
CMP 0x01, 0xFF  (signed 1 vs -1, unsigned 1 vs 255)
  a-b=0x02  Z=0 N=0 C=0 V=0  x86 CF=1
  EQ (je):F  NE (jne):T  LT (jl):F  LO (jb):T  GT (jg):T  HI (ja):F
CMP 0x80, 0x7F  (signed -128 vs 127, unsigned 128 vs 127)
  a-b=0x01  Z=0 N=0 C=1 V=1  x86 CF=0
  EQ (je):F  NE (jne):T  LT (jl):T  LO (jb):F  GT (jg):F  HI (ja):T
CMP 0x05, 0x05  (signed 5 vs 5, unsigned 5 vs 5)
  a-b=0x00  Z=1 N=0 C=1 V=0  x86 CF=0
  EQ (je):T  NE (jne):F  LT (jl):F  LO (jb):F  GT (jg):F  HI (ja):F

প্রথম সারিতেই মূল শিক্ষা: 0xFF আর 0x01-এর তুলনায় LT:T (signed −1 < 1) অথচ LO:F আর HI:T (unsigned 255 > 1) — একই বিট, একই একটি বিয়োগ, শুধু ভিন্ন flag পড়ে ভিন্ন উত্তর। তৃতীয় সারিতে V=1: −128 − 127 wrap করে 0x01 দিয়েছে, N=0; কিন্তু N ≠ V, তাই LT ঠিকই T — চিহ্ন-বিট একা দেখলে ভুল হতো। আর ARM-এর C আর x86-এর CF প্রতিটি সারিতে ঠিক উল্টো, যেমন অংশ ৬-এ বলা হয়েছে।

RISC-V — flag ছাড়াই শাখা

RISC-V (আর তার আগে MIPS) ইচ্ছা করেই কোনো flag register রাখেনি। তুলনা আর শাখা একটি instruction-এ: beq, bne, blt, bge, bltu, bgeu — দুটি register সরাসরি তুলনা করে লাফ দেয়। এটাই compare-and-branch. আর তুলনার ফল সংখ্যা হিসেবে দরকার হলে slt/sltu, যার ফল একটি সাধারণ register-এ যায়।

উদাহরণ (হাতে লেখা, তুলনার জন্য; a ও b ৩২-বিট int, x86-64-এ Intel syntax-এ edi/esi, ARM64-এ w0/w1, RISC-V-তে x10/x11):

# if (a < b) goto L;
# x86-64              # ARM64              # RISC-V (RV32I)
cmp   edi, esi        cmp   w0, w1         blt   x10, x11, L
jl    L               b.lt  L
# unsigned a < b:
cmp   edi, esi        cmp   w0, w1         bltu  x10, x11, L
jb    L               b.lo  L

কেন flag বাদ? এতে প্রতিটি instruction-এর ফল শুধু তার গন্তব্য register-এ যায়, লুকোনো কোনো পার্শ্ব-ফল থাকে না — pipeline আর out-of-order CPU-তে নির্ভরতা খোঁজা সহজ হয় (পর্ব ৩৬, ৪০)। খরচও আছে: signed overflow জানতে বা বহু-শব্দের (multi-word) যোগে carry বের করতে বাড়তি instruction লাগে, যেমন যোগের পর sltu দিয়ে carry বের করা। শাখা ও লুপ assembly-তে কেমন দেখায়, তা পর্ব ২৯-এ বিস্তারিত।

৮ · Shifter — logical, arithmetic, rotate আর barrel shifter

বিট সরানো (shift) CPU-র সবচেয়ে বেশি ব্যবহৃত কাজগুলোর একটি: ২-এর ঘাতে গুণ-ভাগ, bitmask বানানো, field বের করা, আর পর্ব ২১-এর shift-add গুণে। তিন ধরনের shift, সাথে rotate:

  • Left shift (sll): সব বিট বাঁয়ে, ডানে ০ ঢোকে; k ঘর মানে 2k দিয়ে গুণ (বাঁয়ের বিট হারানো পর্যন্ত)।
  • Logical right shift (srl): ডানে সরে, বাঁয়ে ০ ঢোকে — unsigned সংখ্যার 2k দিয়ে ভাগ।
  • Arithmetic right shift (sra): ডানে সরে, বাঁয়ে sign-বিটের কপি ঢোকে — signed সংখ্যার 2k দিয়ে ভাগ, তবে নিচের দিকে গোল করে (floor): −74 >> 3 = −10, −9 নয়।
  • Rotate: এক প্রান্ত দিয়ে বেরোনো বিট অন্য প্রান্তে ঢোকে, কিছু হারায় না। x86-এ rol/ror, ARM-এ ror; RV32I-এ নেই, তবে ঐচ্ছিক bit-manipulation extension (Zbb)-এ rol/ror আছে।

Barrel shifter — log2 n স্তরের mux

সবচেয়ে সরল shifter প্রতি clock-এ এক ঘর সরায় (পর্ব ১৬-এর shift register) — ২৯ ঘর সরাতে ২৯ cycle. দ্রুত CPU-তে দরকার এক ধাপেই যেকোনো দূরত্ব। একটি উপায়: প্রতিটি আউটপুট বিটের জন্য একটি n:1 mux — n বিটে n টি বড় mux, তারের জঙ্গল। চতুর উপায় হলো barrel shifter: shift-এর পরিমাণকে binary-তে ভাঙুন। ৫ = 1012 = 4 + 1. তাহলে “১ ঘর সরানো হবে কি না”, “২ ঘর সরানো হবে কি না”, “৪ ঘর সরানো হবে কি না” — তিনটি স্তর, প্রতিটি স্তরে n টি ২:১ mux, আর স্তর k-এর select হলো shift পরিমাণের বিট k.

স্তর = log2 n · ২:১ mux = n · log2 n · ৮ বিটে ৩ স্তর, ২৪টি mux · ৩২ বিটে ৫ স্তর, ১৬০টি mux

Delay-ও মাত্র log2 n স্তরের mux — ৩২ বিটে ৫টি। RV32I-এর sll/srl/sra shift পরিমাণের নিচের ৫টি বিটই শুধু দেখে (rs2-এর বিট ৪…০) — ঠিক পাঁচটি স্তরের select; x86-ও ৩২-বিট shift-এ count-কে ৫ বিটে mask করে। Arithmetic আর logical-এর পার্থক্য শুধু বাঁয়ে ঢোকা বিটে (sign না ০), আর left shift-এর জন্য হয় আলাদা স্তর, নয়তো ইনপুট-আউটপুটের বিটক্রম উল্টে একই right shifter ব্যবহার — দুটি কৌশলই প্রচলিত। নিচে ৮-বিটের sra 0xB6 by 5 তিন স্তরে:

ইনপুটshift amount 5 = 101
x       = 1011 0110   (0xB6, signed -74)
shamt   = 5 = 101 (binary)
stage 1 (by 1): select = bit0 = 1
stage 2 (by 2): select = bit1 = 0
stage 3 (by 4): select = bit2 = 1
স্তর ১১ ঘর ডানে, sign ঢোকে
in      1011 0110
out     1101 1011   (shift by 1, fill = sign 1)
স্তর ২select 0 — সোজা পার
in      1101 1011
out     1101 1011   (pass: every mux picks its own bit)
স্তর ৩৪ ঘর ডানে
in      1101 1011
out     1111 1101   (shift by 4, four sign bits enter)
ফলযাচাই
result  1111 1101 = 0xFD = -3
check   -74 / 32 = -2.3125, floor = -3
srl 5 would give 0000 0101 = 5   (182 / 32 = 5.69 -> 5)
  1. Shift পরিমাণকে বিটে ভাঙা। 5 = 101 (binary). স্তর ১-এর select = বিট ০ = 1, স্তর ২-এর = বিট ১ = 0, স্তর ৩-এর = বিট ২ = 1. অর্থাৎ ১ ঘর + ৪ ঘর = ৫ ঘর।
  2. স্তর ১: ১ ঘর। প্রতিটি আউটপুট বিট i নেয় ইনপুটের বিট i + 1; সবচেয়ে বাঁয়ের বিটে ঢোকে sign-বিট ১ (sra). 1011 0110 → 1101 1011.
  3. স্তর ২: সোজা পার। Select 0, তাই প্রতিটি mux নিজের বিটটিই পাঠায়; মান বদলায় না — 1101 1011.
  4. স্তর ৩: ৪ ঘর। আউটপুট বিট i নেয় বিট i + 4; বাঁয়ের চার ঘরে sign-বিট ১. 1101 1011 → 1111 1101.
  5. ফল যাচাই। 1111 1101 = −3. −74 ÷ 32 = −2.3125, আর floor করলে −3 — arithmetic shift নিচের দিকে গোল করে। একই ইনপুটে srl 5 দিত 0000 0101 = 5 (182 ÷ 32-এর পূর্ণ অংশ)। যেকোনো পরিমাণেই মাত্র তিন স্তর পেরোতে হয়।

নিচের simulator তিন রকম shift (srl, sra, sll) ১, ৩ আর ৫ ঘরে চালায় এবং প্রতিটি স্তরের পরের মান ছাপে:

// 8-bit barrel shifter: 3 stages of 2:1 muxes (shift by 1, 2, 4).
// Stage k looks at bit k of the shift amount: 0 = pass, 1 = shift by 2^k.
const N = 8, b8 = v => v.toString(2).padStart(N, "0").replace(/(....)(....)/, "$1 $2");
function stage(x, dist, kind) {                   // one layer = 8 muxes
  let out = 0;
  for (let i = 0; i < N; i++) {                   // output bit i
    let bit;
    if (kind === "sll") bit = i - dist >= 0 ? (x >> (i - dist)) & 1 : 0;
    else if (i + dist < N) bit = (x >> (i + dist)) & 1;
    else bit = kind === "sra" ? (x >> (N - 1)) & 1 : 0;   // fill: sign or 0
    out |= bit << i;
  }
  return out;
}
function barrel(x, sh, kind) {
  let v = x; const trace = [];
  for (let k = 0; k < 3; k++) {                   // log2(8) = 3 stages
    const on = (sh >> k) & 1;
    if (on) v = stage(v, 1 << k, kind);
    trace.push(`s${1 << k}:${on ? "shift" : "pass "} ${b8(v)}`);
  }
  return { v, trace };
}
const x = 0xB6;                                   // 1011 0110 = 182 or -74
console.log(`input ${b8(x)} (unsigned ${x}, signed ${x - 256})`);
for (const kind of ["srl", "sra", "sll"])
  for (const sh of [1, 3, 5]) {
    const { v, trace } = barrel(x, sh, kind);
    const sv = v & 0x80 ? v - 256 : v;
    console.log(`${kind} ${sh} (${sh.toString(2).padStart(3, "0")}): ` +
                `${trace.join(" | ")} -> ${v} / ${sv}`);
  }

আউটপুট (শেষে unsigned / signed মান):

input 1011 0110 (unsigned 182, signed -74)
srl 1 (001): s1:shift 0101 1011 | s2:pass  0101 1011 | s4:pass  0101 1011 -> 91 / 91
srl 3 (011): s1:shift 0101 1011 | s2:shift 0001 0110 | s4:pass  0001 0110 -> 22 / 22
srl 5 (101): s1:shift 0101 1011 | s2:pass  0101 1011 | s4:shift 0000 0101 -> 5 / 5
sra 1 (001): s1:shift 1101 1011 | s2:pass  1101 1011 | s4:pass  1101 1011 -> 219 / -37
sra 3 (011): s1:shift 1101 1011 | s2:shift 1111 0110 | s4:pass  1111 0110 -> 246 / -10
sra 5 (101): s1:shift 1101 1011 | s2:pass  1101 1011 | s4:shift 1111 1101 -> 253 / -3
sll 1 (001): s1:shift 0110 1100 | s2:pass  0110 1100 | s4:pass  0110 1100 -> 108 / 108
sll 3 (011): s1:shift 0110 1100 | s2:shift 1011 0000 | s4:pass  1011 0000 -> 176 / -80
sll 5 (101): s1:shift 0110 1100 | s2:pass  0110 1100 | s4:shift 1100 0000 -> 192 / -64

প্রতিটি সারিতে shift পরিমাণের binary রূপ (যেমন 101) ঠিক বলে দেয় কোন স্তর “shift” আর কোনটি “pass”. srl-এ 182 → 91, 22, 5 (182-কে 2, 8, 32 দিয়ে ভাগের পূর্ণ অংশ); sra-তে −74 → −37, −10, −3 (floor ভাগ)। sll-এ বাঁয়ের বিট হারিয়ে যায়: 182 × 8 = 1456, mod 256 = 176. পর্ব ২১-এ এই shifter-ই গুণের প্রতিটি ধাপে কাজে লাগবে।

৯ · তুলনা, যাচাই আর বাস্তবের ALU

সমান কি না — দুই পথ

A = B কি না জানার প্রথম পথ: A − B করে Z flag দেখা। এটি সহজ, কারণ ALU তো আছেই — কিন্তু পুরো carry chain পেরোতে হয়। দ্বিতীয় পথ দ্রুততর: প্রতিটি বিটে XNOR — (ai ⊕ bi)′, যা দুটি বিট সমান হলে ১ — তারপর সবগুলোর AND-এর গাছ। কোনো carry নেই, শুধু log2 n স্তর। অনেক pipeline CPU-তে beq/bne-এর জন্য এমন আলাদা equality comparator রাখা হয়, যাতে শাখার সিদ্ধান্ত আগেভাগে নেওয়া যায় (পর্ব ৩৬–৩৭)।

প্রশ্নALU থেকে কীভাবেRISC-V
A = BA − B-এর Z = 1, বা XNOR-AND গাছbeq; সংখ্যা হিসেবে sub + seqz
A < B (signed)A − B-এর N ⊕ Vslt, blt
A < B (unsigned)A − B-এ borrow, অর্থাৎ CarryOut = 0sltu, bltu

পুরো নকশাটি C দিয়ে যাচাই — ৬৫,৫৩৬টি জোড়া

একটি নকশা কয়েকটি উদাহরণে ঠিক কাজ করলেই নিশ্চিত হওয়া যায় না। ৮ বিটে মোট জোড়া মাত্র 256 × 256 = ৬৫,৫৩৬টি, তাই সবগুলো পরীক্ষা করা যায় — একে বলে exhaustive testing. নিচের C প্রোগ্রাম gate-স্তরের ripple মডেলটিকে C-র নিজস্ব গণিতের সাথে মেলায়: ফল, C, V, আর slt (N ⊕ V) ও sltu (NOT C).

#include <stdio.h>
/* gate-level 8-bit ripple ALU: add when bneg = 0, sub when bneg = 1 */
static unsigned alu8(unsigned a, unsigned b, unsigned bneg, unsigned *C, unsigned *V) {
    unsigned c = bneg, r = 0, cin7 = 0;
    for (int i = 0; i < 8; i++) {
        unsigned x = (a >> i) & 1u, y = ((b >> i) & 1u) ^ bneg;
        if (i == 7) cin7 = c;
        r |= (x ^ y ^ c) << i;
        c = (x & y) | (x & c) | (y & c);
    }
    *C = c; *V = cin7 ^ c; return r;
}
static int s8(unsigned p) { return (int)(p ^ 0x80u) - 128; }   /* no UB */
static unsigned out8(int t) { return (unsigned)(t < -128 || t > 127); }

int main(void) {
    long bad_add = 0, bad_sub = 0, bad_slt = 0, bad_sltu = 0, naive_wrong = 0;
    for (unsigned a = 0; a < 256; a++) for (unsigned b = 0; b < 256; b++) {
        unsigned C, V, r = alu8(a, b, 0, &C, &V);
        if (r != ((a + b) & 0xFFu) || C != (unsigned)(a + b > 255u)
            || V != out8(s8(a) + s8(b))) bad_add++;
        r = alu8(a, b, 1, &C, &V);
        if (r != ((a - b) & 0xFFu) || C != (unsigned)(a >= b)
            || V != out8(s8(a) - s8(b))) bad_sub++;
        unsigned lt = (unsigned)(s8(a) < s8(b));
        if (((r >> 7) ^ V) != lt) bad_slt++;           /* SLT = N xor V */
        if ((r >> 7) != lt) naive_wrong++;             /* SLT = N only  */
        if ((C ^ 1u) != (unsigned)(a < b)) bad_sltu++; /* SLTU = NOT C  */
    }
    printf("pairs tested: %d\nadd mismatches: %ld, sub mismatches: %ld\n",
           256 * 256, bad_add, bad_sub);
    printf("slt (N xor V) mismatches: %ld\nsltu (NOT C) mismatches : %ld\n",
           bad_slt, bad_sltu);
    printf("naive slt (N only) wrong: %ld pairs\n", naive_wrong); return 0;
}

আউটপুট:

pairs tested: 65536
add mismatches: 0, sub mismatches: 0
slt (N xor V) mismatches: 0
sltu (NOT C) mismatches : 0
naive slt (N only) wrong: 16384 pairs

সব ৬৫,৫৩৬টি জোড়ায় gate-স্তরের মডেল নিখুঁত: যোগ-বিয়োগের ফল, C (বিয়োগে ARM-এর “no borrow” অর্থে) আর V — কোথাও অমিল নেই, আর N ⊕ V নিয়মের slt ও NOT C নিয়মের sltu-ও শূন্য ভুল। শেষ লাইনটিই সতর্কবাণী: শুধু চিহ্ন-বিট দেখা “সরল” slt ১৬,৩৮৪টি জোড়ায় — মোটের ঠিক এক-চতুর্থাংশে — ভুল উত্তর দেয়; এগুলোই সেই জোড়া যেখানে A − B signed overflow করে। প্রোগ্রামে signed রূপান্তর (p ^ 0x80) - 128 দিয়ে করা হয়েছে, যাতে কোনো implementation-defined বা undefined আচরণ না থাকে।

বাস্তবের ALU — অনেকগুলো execution unit

পাঠ্যবইয়ের CPU-তে একটি ALU. আধুনিক বড় core-এ কিন্তু একাধিক execution unit থাকে: কয়েকটি সরল integer ALU (প্রায়ই ৪ বা তার বেশি, যারা যোগ, লজিক, shift এক cycle-এ শেষ করে), আলাদা branch unit, আলাদা multiplier আর divider (গুণে কয়েক cycle, ভাগে আরও বেশি লাগে), load/store-এর ঠিকানা হিসাবের unit, আর floating-point ও SIMD unit. এক cycle-এ কয়েকটি instruction একসাথে আলাদা unit-এ পাঠানোর এই কৌশলই superscalar নকশা — পর্ব ৩৯-এ। রেস্তোরাঁর রান্নাঘরের মতো: একজন বাবুর্চি সব না করে আলাদা চুলা — ভাজা, তন্দুর, চা — একসাথে চলে।

এক chip-এ ALU — 74181. Texas Instruments-এর 74181 সাধারণত প্রথম single-chip ALU হিসেবে পরিচিত, যা ১৯৭০ সালের দিকে বাজারে আসে। এটি ৪-বিটের: চারটি select লাইন আর একটি mode লাইন দিয়ে ১৬টি লজিক আর ১৬টি গাণিতিক কাজ বাছা যেত, ভেতরে carry-lookahead-এর ব্যবস্থাসহ। কয়েকটি 74181 পাশাপাশি বসিয়ে (আর 74182 lookahead carry generator দিয়ে জুড়ে) ১৬ বা ৩২ বিটের ALU বানানো হতো — ঠিক এই পর্বের slice-চিন্তা, শুধু ৪ বিটের টুকরোয়। বলা হয়, ১৯৭০-এর দশকের বেশ কিছু minicomputer, যেমন DEC-এর কিছু PDP-11 মডেল আর Xerox Alto, এর ওপর নির্ভর করত। পরে microprocessor পুরো ALU-কে CPU chip-এর ভেতরে নিয়ে নেয়।

আরও গভীরে: আধুনিক ৬৪-বিট core-এ integer যোগ সাধারণত এক cycle-এ শেষ হয়, তাই ALU-র adder আর ফল সরাসরি পরের instruction-এ ফেরত দেওয়ার (bypass/forwarding) পথ মিলে প্রায়ই chip-এর সবচেয়ে কঠিন timing-পথগুলোর একটি হয়; এজন্য Kogge–Stone-ধাঁচের parallel-prefix adder আর যত্নে সাজানো layout ব্যবহার হয়। x86-এর মতো flag-ওয়ালা ISA-তে out-of-order core flag-গুলোকেও register-এর মতো rename করে, আর কিছু instruction সব flag না বদলানোয় (যেমন inc CF-কে ছোঁয় না) বাড়তি নির্ভরতা সামলাতে হয়। গবেষণায় approximate adder আর কম-শক্তির ALU নিয়েও কাজ চলছে, বিশেষত AI accelerator-এর জন্য।

১০ · AI-এর যুগে ALU নকশা

AI টুল ALU-র ছবি আর সংজ্ঞা সুন্দরভাবে বলতে পারে, কিন্তু ঠিক সেই কোণগুলোতে ভুল করে যেখানে hardware বাগ লুকিয়ে থাকে। প্রচলিত কয়েকটি ভুল:

AI-এর প্রচলিত ভুলঠিক কথাকীভাবে যাচাই
slt = A − B-এর MSBOverflow হলে ভুল; SLT = N ⊕ V−128 slt 1 (৮ বিট) হাতে বা simulator-এ
“বিয়োগের পর C = 1 মানে borrow” — সব CPU-তেx86-এ তাই, ARM-এ উল্টো (C = NOT borrow)ISA manual-এর SUB/CMP অংশ
MIPS-এর ALU control কোড RISC-V-এর অংশ বলাALU control কোড implementation-এর পছন্দ; ISA শুধু instruction ঠিক করেRISC-V spec-এ “ALU control” খুঁজে দেখুন — নেই
“RISC-V-তে zero/carry flag আছে”, বা nor instructionRV32I-এ কোনো flag register নেই, nor-ও নেইRV32I instruction তালিকা
৩২-বিট barrel shifter-এ “৩২ স্তর” বা “৪ স্তর”log2 32 = ৫ স্তর, প্রতি স্তরে ৩২টি ২:১ muxshift পরিমাণের বিট গুনে
Verilog-এ a < b লিখে ধরে নেওয়া signed তুলনাসাধারণ wire/reg unsigned; signed ঘোষণা বা $signed লাগেsimulator-এ −1 < 1 পরীক্ষা (পর্ব ১৪)

AI-কে ALU নিয়ে প্রশ্ন করলে width, ISA আর প্রতিটি carry চেয়ে নিন, যাতে নিজে মেলাতে পারেন:

আমি Patterson–Hennessy ধাঁচের ১-বিট ALU slice দিয়ে ৮-বিট ALU শিখছি (control: Ainvert, Bnegate, Operation; 00 AND, 01 OR, 10 add, 11 less). ১. A = 0x80, B = 0x01-এর জন্য control 0110 আর 0111-এ প্রতিটি বিটের carry-in, sum আর carry-out একটি টেবিলে দেখাও। ২. Z, N, C, V বের করো। C-এর মান ARM আর x86 দুই নিয়মে আলাদা করে লেখো। ৩. slt-এর Set বিট শুধু MSB থেকে নিলে কী আসে আর N xor V থেকে নিলে কী আসে — দুটিই দেখাও। ৪. শেষে ৮-বিটের সব ৬৫,৫৩৬টি জোড়া পরীক্ষা করার একটি ছোট JavaScript বা Python স্ক্রিপ্ট দাও, যা তোমার নিয়মকে সাধারণ গণিতের সাথে মেলায় — আমি নিজে চালিয়ে দেখব।

AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • একই adder দিয়ে বিয়োগ কীভাবে হয়? — A + B′ + 1: প্রতিটি B বিটে XOR (Binvert) আর বিট ০-এর CarryIn = 1.
  • ALU control 0110 আর 0111 কী বোঝায় (MIPS-ধাঁচে)? — 0110 = subtract, 0111 = set-on-less-than; দুটিতেই Bnegate = 1.
  • Zero flag-এর circuit? — সব ফল-বিটের NOR (OR-এর গাছ, তারপর NOT).
  • শুধু চিহ্ন-বিট দিয়ে slt কেন ভুল? — A − B overflow করলে চিহ্ন উল্টে যায়; সঠিক Less = N ⊕ V.
  • ৩২-বিট barrel shifter-এ কয়টি স্তর আর কয়টি ২:১ mux? — ৫ স্তর, 5 × 32 = ১৬০টি mux.

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১x86 আর ARM-এ condition flag আছে, MIPS আর RISC-V ইচ্ছা করে বাদ দিয়েছে। কোন নকশাটি ভালো?

Flag-এর ধারণা খুব পুরোনো — ১৯৬০-৭০-এর দশকের বহু যন্ত্রে আর 8080, 6502, 8086-এর মতো প্রথম দিকের microprocessor-এ ছিল। সুবিধা স্পষ্ট: একটি sub বা cmp-এর পর একসাথে চারটি তথ্য পাওয়া যায়, আর পরের যেকোনো শর্তসাপেক্ষ instruction সেগুলো পড়তে পারে। বহু-শব্দের যোগে (যেমন ৩২-বিট যন্ত্রে ১২৮-বিট সংখ্যা) adc (add with carry) এক instruction-এ আগের শব্দের carry নিয়ে নেয়, যা cryptography আর বড় সংখ্যার library-তে খুব কাজের।

অসুবিধা আসে pipeline আর out-of-order যুগে। Flag একটি লুকোনো, সবার ভাগের register: প্রায় প্রতিটি গাণিতিক instruction সেটি লেখে, তাই পাশাপাশি দুটি স্বাধীন instruction-ও flag-এর কারণে একে অপরের ওপর নির্ভরশীল দেখায়। x86-এ আরও জটিলতা — inc আর dec CF বদলায় না, ফলে কিছু instruction flag-এর এক অংশ লেখে আর বাকিটা আগের মান থেকে নেয় (partial flag update); কিছু Intel প্রজন্মে এ থেকে বাড়তি stall হতো বলে optimization guide-এ সতর্ক করা হয়েছে। আধুনিক x86 core তাই flag-কেও rename করে, যা বাড়তি hardware.

ARM মাঝামাঝি পথ নিয়েছে: সাধারণ add flag ছোঁয় না, শুধু adds/subs-এর মতো “S” লাগানো instruction লেখে — প্রোগ্রামার বা compiler ঠিক করে কখন flag দরকার। MIPS আর RISC-V সম্পূর্ণ বাদ দিয়েছে: তুলনার ফল হয় সাধারণ register-এ (slt), নয়তো শাখা নিজেই তুলনা করে (blt). দাম হলো overflow বা carry লাগলে বাড়তি instruction — যেমন ৬৪-বিট যোগ ৩২-বিট RISC-V-তে নিচের অর্ধেকে add, carry বের করতে sltu, তারপর ওপরের অর্ধেকে দুটি add. RISC-V-এর নকশাকারদের যুক্তি ছিল, এমন কাজ সাধারণ প্রোগ্রামে বিরল, আর সরল, flag-হীন pipeline সব প্রোগ্রামকে লাভ দেয়; সমালোচকেরা বড়-সংখ্যার গণিতে এর খরচ দেখান।

মূল উপলব্ধি: “ভালো” নির্ভর করে কোন কাজ বেশি চলে আর কোন যুগের hardware-এর ওপর। Flag ছোট, ক্রমিক CPU-তে সস্তা ছিল; গভীর pipeline আর out-of-order যুগে লুকোনো ভাগের অবস্থা দামি হয়ে উঠেছে।

প্র ০২74181 থেকে Am2901 — “bit-slice” যুগ কী ছিল, আর microprocessor এসে কেন তা শেষ হয়ে গেল?

১৯৭০-এর দশকে একটি পুরো ৩২-বিট CPU এক chip-এ ধরানোর মতো transistor ছিল না। তাই নকশাকারেরা CPU বানাতেন ছোট ছোট TTL chip জুড়ে, বোর্ডের পর বোর্ড। Texas Instruments-এর 74181 (প্রায় ১৯৭০) এই কাজ সহজ করে: একটি chip-ই ৪-বিটের একটি ALU slice, ৩২টি কাজসহ। কয়েকটি পাশাপাশি বসালে যেকোনো প্রস্থ, আর 74182 দিয়ে carry-lookahead জুড়লে গতিও ভালো। বলা হয়, DEC-এর কিছু PDP-11 আর VAX মডেল, Xerox Alto-সহ বহু যন্ত্র এর ওপর তৈরি — অর্থাৎ এই পর্বের “একই slice বারবার” চিন্তাটি বাস্তবে শিল্প চালিয়েছে।

এর পরের ধাপ ছিল AMD-এর Am2900 পরিবার (মাঝ-১৯৭০-এর দশক)। এর Am2901 chip-এ ৪-বিট ALU-র সাথে একটি ছোট register file আর shifter-ও ছিল — অর্থাৎ একটি পুরো datapath-এর ৪-বিট টুকরো। এর সাথে microprogrammed control (পর্ব ৩৪) জুড়ে কোম্পানিগুলো নিজের পছন্দমতো instruction set-এর যন্ত্র বানাত; বিভিন্ন minicomputer, graphics আর সামরিক যন্ত্রে এমন নকশা ব্যবহার হয়েছে বলে জানা যায়।

তাহলে শেষ হলো কেন? Moore-এর সূত্র (পর্ব ০২)। Transistor-এর সংখ্যা যত বেড়েছে, পুরো CPU এক chip-এ ঢুকেছে — 8086, 68000, তারপর 80386 আর RISC chip-গুলো। এক chip-এর ভেতরের তার অনেক ছোট আর দ্রুত, বোর্ডের তারের মতো delay আর শক্তি খরচ নেই; তৈরির খরচও কম। একসময় bit-slice বোর্ডের যন্ত্রের চেয়ে একটি microprocessor-ই দ্রুত হয়ে গেল। তবে চিন্তাটি হারায়নি: আজও chip নকশাকারেরা ALU বা datapath-কে একই রকম bit-slice সারি হিসেবে সাজান (datapath layout), কারণ নিয়মিত গঠন ছোট, দ্রুত আর যাচাই করা সহজ।

মূল উপলব্ধি: প্রযুক্তির সীমা নকশার আকার ঠিক করে। Slice-এর ধারণা স্থায়ী, কিন্তু তা কোথায় থাকে — আলাদা chip-এ নাকি এক chip-এর ভেতরে — তা নির্ভর করে transistor কত সস্তা তার ওপর।

প্র ০৩৩২-বিট ripple ALU এত ধীর হলে আধুনিক CPU কয়েক GHz-এ এক cycle-এ ৬৪-বিট যোগ কীভাবে করে?

Ripple ALU-তে carry প্রতিটি slice পেরিয়ে যায় — ৬৪ বিটে মোটামুটি ১২৮ স্তর gate-এর পথ। এক GHz-এর ঘড়িতে একটি cycle মাত্র এক ন্যানোসেকেন্ড, আর তার মধ্যে register থেকে পড়া, mux, ফল লেখা — সবকিছু ধরাতে হয়। Ripple adder দিয়ে তা অসম্ভব। সমাধান তিন দিক থেকে।

প্রথম, দ্রুত adder. Carry-lookahead (পর্ব ১১)-এর মূল চিন্তা হলো প্রতিটি বিট বলে দেয় সে carry “তৈরি করে” (generate, g = a·b) নাকি “পার করে দেয়” (propagate, p = a ⊕ b বা a + b). এই g আর p গুলো একটি গাছে জুড়ে সব carry প্রায় log2 n স্তরে পাওয়া যায়। এই গাছের বিভিন্ন রূপের নাম Kogge–Stone (১৯৭৩), Brent–Kung, Han–Carlson ইত্যাদি — প্রতিটিতে delay, তারের সংখ্যা আর শক্তির আলাদা ভারসাম্য। Carry-select adder আরেক কৌশল: উপরের অর্ধেকের যোগ carry ০ আর ১ দুই ধরে আগেই করে রাখা, তারপর আসল carry এলে mux দিয়ে বেছে নেওয়া — “সব করি, দরকারিটা রাখি” চিন্তা আবারও।

দ্বিতীয়, circuit আর layout. ALU-র মতো গুরুত্বপূর্ণ অংশ অনেক সময় হাতে সাজানো বা বিশেষ circuit কৌশলে বানানো হয়, transistor-এর আকার মেপে মেপে। তৃতীয়, কাজ ভাগ করা: Intel-এর Pentium 4 (২০০০-এর শুরুতে)-এর integer ALU core-এর ঘড়ির দ্বিগুণ হারে চলত বলে জানানো হয়, আর যোগ ১৬-বিটের টুকরোয় ধাপে ধাপে করা হতো — খুব উচ্চ ঘড়ির লক্ষ্য পূরণে চরম একটি পথ, যা পরের নকশাগুলোতে আর রাখা হয়নি।

সবশেষে মনে রাখার মতো: যোগ এক cycle-এ শেষ না হলে নির্ভরশীল প্রতিটি instruction-কে অপেক্ষা করতে হয়, কারণ i = i + 1-এর মতো লুপ প্রতি পাকে আগের ফল চায়। তাই সরল integer যোগের latency এক cycle রাখা প্রায় সব নকশার অগ্রাধিকার।

মূল উপলব্ধি: ALU-র যুক্তি পাঠ্যবইয়ের মতোই, কিন্তু গতি আসে carry-র পথ ছোট করার গণিত আর circuit-কৌশল থেকে। “কী হিসাব করে” আর “কত দ্রুত করে” — দুটি আলাদা নকশা-প্রশ্ন।

প্র ০৪Shifter এক ঘর করে সরালেও তো কাজ চলে — barrel shifter-এর বাড়তি খরচ কি সত্যিই দরকার?

ইতিহাস এর ভালো উত্তর দেয়। Intel 8086 (১৯৭৮)-এ CL register-এ রাখা সংখ্যার সমান ঘর সরাতে প্রতিটি বিটের জন্য আলাদা clock লাগত — বড় shift মানে অনেক cycle. পরের প্রজন্মের x86 chip-এ (বিশেষ করে 80386-এ) barrel shifter আসে বলে সাধারণত উল্লেখ করা হয়, আর তখন থেকে যেকোনো দূরত্বের shift প্রায় স্থির সময়ে হয়। কারণ shift সব জায়গায়: array-র সূচককে element-এর আকার দিয়ে গুণ (×4, ×8 মানে shift), bit-field বের করা, hash আর checksum, cryptography, আর floating-point-এ mantissa সাজানো (পর্ব ২৪)।

ARM-এর প্রথম chip ARM1 (১৯৮৫) এই চিন্তাকে আরও এগিয়ে নেয়: প্রায় প্রতিটি data-processing instruction-এর দ্বিতীয় operand ALU-তে ঢোকার আগে barrel shifter পেরোতে পারে। ফলে add r0, r1, r2, lsl #2 এক instruction-এই r0 = r1 + r2 × 4 করে। ছোট, শক্তি-সাশ্রয়ী chip-এ এটি অনেক instruction বাঁচিয়েছে। ARM64-এও shifted-register operand রয়ে গেছে।

খরচ কত? n বিটে n·log2 n টি ২:১ mux — ৬৪ বিটে ৩৮৪টি, প্রতিটি কয়েকটি transistor. আজকের কোটি-কোটি transistor-এর chip-এ এটি নগণ্য। আসল খরচ তারে: প্রতিটি স্তরে বিটগুলো ১, ২, ৪, ৮… ঘর দূরে যায়, তাই শেষের স্তরে লম্বা তার লাগে, যা layout আর delay-কে প্রভাবিত করে। এজন্য কিছু নকশা ৪:১ mux দিয়ে স্তর কমায়, বা left আর right একই hardware-এ বিটক্রম উল্টে সারে।

শেষে, এমবেডেড জগতে এখনো কিছু ছোট microcontroller-এ শুধু ১-বিট shift instruction থাকে (যেমন ৮-বিট AVR-এ lsl/lsr এক ঘর করে সরায়) — সেখানে compiler বড় shift-কে লুপ বা অন্য কৌশলে ভাঙে। চাহিদা আর খরচের হিসাবে প্রতিটি বাজার নিজের উত্তর বেছে নেয়।

মূল উপলব্ধি: যে কাজ খুব বেশি চলে, সেটিকে দ্রুত করাই আর্কিটেকচারের একটি মূল নীতি (“common case fast”). Shift এত বেশি চলে যে log2 n স্তরের hardware প্রায় সব বড় CPU-র জন্য লাভজনক।

প্র ০৫ISA-তে slt আর sltu দুটিই কেন লাগে? Software-এ signed-unsigned তুলনা কোথায় বাগ আর কোথায় কৌশল হয়ে ওঠে?

বিট-প্যাটার্ন নিজে signed না unsigned তা বলে না (পর্ব ০৫) — অর্থ ঠিক করে প্রোগ্রাম। তাই একই দুটি register-এর তুলনার দুই উত্তর হতে পারে, যেমন 0xFFFFFFFF বনাম 1: signed-এ −1 < 1, unsigned-এ বিশাল > 1. Hardware যদি শুধু একটি তুলনা দিত, অন্যটি বানাতে প্রতিবার বাড়তি instruction লাগত (যেমন দুটির MSB উল্টে তুলনা)। দুটি আলাদা instruction দামেও সস্তা: একটি দেখে N ⊕ V, অন্যটি borrow — একই বিয়োগ থেকে।

বাগের দিক: C-তে int আর unsigned মিশে গেলে compiler সাধারণত unsigned তুলনা বাছে (usual arithmetic conversions), তাই -1 < 1u মিথ্যা হয়ে যায়। Assembly-তে সেটি দেখায় jb/bltu হিসেবে, যেখানে প্রোগ্রামার ভেবেছিলেন jl/blt. Buffer-এর দৈর্ঘ্য যাচাইয়ে এমন ভুল বহুবার নিরাপত্তা-ত্রুটি তৈরি করেছে: ঋণাত্মক দৈর্ঘ্য signed তুলনায় “ছোট” বলে পাস করে, তারপর memcpy-তে unsigned হয়ে বিশাল হয়ে যায়।

কৌশলের দিক: array-র সীমা যাচাই 0 <= i && i < n-এ দুটি তুলনা লাগে। কিন্তু i-কে unsigned ধরে (unsigned)i < (unsigned)n একটিই তুলনা — ঋণাত্মক i unsigned-এ বিশাল হয়ে যায় বলে আপনা থেকেই বাদ পড়ে (n অঋণাত্মক হলে)। Compiler আর JIT (যেমন Java-র bounds check বাদ দেওয়ার optimization) এই কৌশল নিয়মিত ব্যবহার করে, আর RISC-V-তে এটি একটি bltu/bgeu. RISC-V-তে sltu rd, x0, rs দিয়ে “rs ≠ 0” পাওয়া যায় (pseudo-instruction snez), আর sltiu rd, rs, 1 দিয়ে “rs = 0” (seqz) — sltu-র আরেকটি চতুর ব্যবহার।

মূল উপলব্ধি: Signed আর unsigned তুলনা একই বিয়োগের দুটি আলাদা প্রশ্ন। কোন প্রশ্ন করছেন তা জানলে এটি কৌশল, না জানলে বাগ।

অনুশীলন

পরীক্ষার ধাঁচে লেখা — আগে খাতায় সমাধান করুন, তারপর উত্তর মিলিয়ে নিন।

  1. Flag নির্ণয় (হাতে হিসাব): একটি ৮-বিট ALU-তে A = 0x9C, B = 0x32. (ক) add (control 0010) আর sub (control 0110)-এর ফল hex-এ এবং Z, N, C, V বের করুন; sub-এর C ARM আর x86 দুই নিয়মে লিখুন। (খ) slt (0111) আর sltu-এর ফল কত? সরল slt (শুধু চিহ্ন-বিট) কী দিত?

    A = 1001 1100 (unsigned 156, signed −100), B = 0011 0010 (50).

    কাজফলZNCVব্যাখ্যা
    add0xCE (−50)0100−100 + 50 = −50 সীমার ভেতরে; unsigned 206 < 256
    sub0x6A (+106)00ARM C = 1, x86 CF = 01−100 − 50 = −150 সীমার বাইরে; unsigned 156 ≥ 50, borrow নেই

    Sub-এর carry: A + B′ + 1 = 1001 1100 + 1100 1101 + 1 = 1 0110 1010. MSB-তে ঢোকা carry c7 = 0 (বিট ৬-এ 0 + 1 + 0 = 1), বেরোনো c8 = 1, তাই V = 1.

    (খ) slt = N ⊕ V = 0 ⊕ 1 = 1 (−100 < 50, সঠিক)। সরল slt দিত N = 0 — ভুল। sltu = NOT C(ARM) = 0 (156 < 50 মিথ্যা)। একই জোড়ায় signed আর unsigned উত্তর ভিন্ন।

  2. Barrel shifter trace: ৮-বিট barrel shifter-এ (স্তর ১, ২, ৪) x = 1001 1101 (0x9D)-কে ৬ ঘর সরানো হবে। (ক) কোন স্তর shift করবে আর কোনটি pass? (খ) sra, srl আর sll-এ প্রতিটি স্তরের পরের মান আর চূড়ান্ত দশমিক মান লিখুন। (গ) ৬৪-বিট barrel shifter-এ কয়টি স্তর আর কয়টি ২:১ mux লাগবে?

    (ক) 6 = 110 (binary): স্তর ১ (১ ঘর) pass, স্তর ২ (২ ঘর) shift, স্তর ৩ (৪ ঘর) shift.

            stage1(pass)  stage2(by 2)  stage3(by 4)   result
    sra     1001 1101     1110 0111     1111 1110      -2   (x = -99)
    srl     1001 1101     0010 0111     0000 0010       2   (x = 157)
    sll     1001 1101     0111 0100     0100 0000      64

    যাচাই: −99 ÷ 64 = −1.55, floor = −2 ✓; 157 ÷ 64-এর পূর্ণ অংশ 2 ✓; 157 × 64 = 10048, mod 256 = 64 ✓.

    (গ) log2 64 = ৬ স্তর, প্রতি স্তরে ৬৪টি mux — মোট 6 × 64 = ৩৮৪টি ২:১ mux.

  3. আউটপুট অনুমান করুন: RV32I-এ নিচের instruction-গুলো চলার পর x7, x8, x9, x10 আর x11-এ কী থাকবে? (li একটি pseudo-instruction, যা register-এ ধ্রুবক বসায়।)
    li   x5, -1
    li   x6, 1
    slt  x7, x5, x6
    sltu x8, x5, x6
    sub  x9, x6, x5
    sltu x10, x0, x9
    srai x11, x5, 4
    x7  = 1            # signed: -1 < 1
    x8  = 0            # unsigned: 0xFFFFFFFF < 1 is false
    x9  = 2            # 1 - (-1)
    x10 = 1            # 0 <u 2, i.e. "x9 != 0" (snez idiom)
    x11 = -1 (0xFFFFFFFF)  # arithmetic shift keeps the sign
    • slt: ভেতরে −1 − 1 = −2, কোনো overflow নেই, N = 1 → 1.
    • sltu: x5-এর প্যাটার্ন 0xFFFFFFFF unsigned-এ 4,294,967,295, যা 1-এর চেয়ে বড় → 0.
    • sltu x10, x0, x9: 0 < x9 (unsigned) মানে x9 ≠ 0; এটাই snez pseudo-instruction.
    • srai: সব বিট ১-এর সংখ্যা ডানে সরালে বাঁয়ে sign ১ ঢোকে — ফল বদলায় না, −1 থাকে। srli হলে আসত 0x0FFFFFFF.
  4. Circuit-এর নকশা: Patterson–Hennessy ধাঁচের ১-বিট slice (Ainvert, Binvert, CarryIn, ৪:১ mux: AND, OR, sum, Less) ধরে: (ক) NAND আর “A − B-এর বদলে B − A” — প্রতিটির জন্য Ainvert, Binvert, বিট ০-এর CarryIn আর Operation-এর মান লিখুন, আর বলুন MIPS-ধাঁচের ৪-বিট control (যেখানে CarryIn = Bnegate) দিয়ে তা সম্ভব কি না। (খ) XOR কি এই slice-এ সম্ভব? না হলে সবচেয়ে কম পরিবর্তনে কীভাবে যোগ করবেন? (গ) ৪-বিট ALU-র Zero flag-এর Boolean সূত্র লিখুন।

    (ক) NAND: Ainvert = 1, Binvert = 1, Operation = 01 (OR) → a′ + b′ = (a·b)′. CarryIn অপ্রাসঙ্গিক, কারণ mux adder-এর ফল নেয় না; তাই ৪-বিট control 1101 দিয়ে সম্ভব।

    B − A: Ainvert = 1, Binvert = 0, CarryIn = 1, Operation = 10 → a′ + b + 1 = B − A. কিন্তু MIPS-ধাঁচের নকশায় CarryIn আলাদা সংকেত নয়, Bnegate-এর সাথে বাঁধা (Bnegate = 1 মানে Binvert = 1 এবং CarryIn = 1). তাই সেই ৪-বিট control-এ B − A হয় না; করতে হলে বিট ০-এর CarryIn-এর জন্য আলাদা control তার লাগবে। Software-এর সহজ সমাধান হলো operand অদলবদল করে sub.

    (খ) সম্ভব নয় — mux-এর চারটি ইনপুটের কোনোটিই a ⊕ b নয়, আর inverter বসিয়ে AND বা OR থেকে XOR পাওয়া যায় না। তবে full adder-এর ভেতরে a ⊕ b আগে থেকেই হিসাব হয় (sum = (a ⊕ b) ⊕ cin). সবচেয়ে কম পরিবর্তন: সেই অভ্যন্তরীণ a ⊕ b তারটি বের করে mux-এর পঞ্চম ইনপুটে দেওয়া — mux তখন ৮:১ হয় আর Operation ৩ বিটের। RISC-V-এর xor সমর্থনের জন্য বাস্তব ALU-তে এমন বাড়তি ইনপুট থাকে।

    (গ) Z = (r3 + r2 + r1 + r0)′ = r3′ · r2′ · r1′ · r0′ (De Morgan). ২-ইনপুট gate দিয়ে: দুটি OR, তারপর একটি NOR — দুই স্তর।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রম১-বিট slice দিয়ে গড়া ripple ALU-তে A − B হওয়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Control unit পাঠায় Bnegate = 1 আর Operation = 10
  2. প্রতিটি slice-এর XOR gate B-এর বিট উল্টে দেয়, আর বিট ০-এর CarryIn হয় ১
  3. বিট ০ থেকে MSB পর্যন্ত carry এক slice থেকে পরের slice-এ ছড়ায়
  4. প্রতিটি slice-এর mux adder-এর sum বেছে নিয়ে ফলের বিট দেয়
  5. ফল আর MSB-র carry থেকে Z, N, C, V তৈরি হয়

Patterson–Hennessy ধাঁচের ALU-তে (control = Ainvert, Bnegate, Operation1, Operation0; Operation 00 AND, 01 OR, 10 add, 11 less) ৪-বিট control 0110 কোন কাজ বোঝায়?

  • add
  • subtract
  • set-on-less-than
  • NOR

একটি n-বিট adder দিয়ে A − B করতে B-এর প্রতিটি বিট উল্টানোর পাশাপাশি আর কী করতে হয়?

  • ফলের সব বিট উল্টানো
  • MSB slice-এর CarryIn = 1 করা
  • বিট ০-এর CarryIn = 1 করা
  • A-এর সব বিটও উল্টানো

৮-বিট ALU-তে A = −128, B = 1 নিয়ে slt চালানো হলো। ভেতরের A − B-এর ফল 0111 1111, N = 0, V = 1. সঠিক slt ফল আর “শুধু চিহ্ন-বিট” নিয়মের ফল কী?

  • সঠিক 1, সরল নিয়মে 0
  • সঠিক 0, সরল নিয়মে 1
  • দুটিতেই 1
  • দুটিতেই 0

৩২-বিট barrel shifter ২:১ multiplexer-এর স্তর দিয়ে বানালে কয়টি স্তর আর মোট কয়টি ২:১ mux লাগে?

  • ৩২ স্তর, ১,০২৪টি mux
  • ৪ স্তর, ১২৮টি mux
  • ৮ স্তর, ২৫৬টি mux
  • ৫ স্তর, ১৬০টি mux

৮-বিটে CMP 0xFF, 0x01 (অর্থাৎ 0xFF − 0x01) চালানোর পর কোন কথাটি সত্য?

  • Signed less-than মিথ্যা, unsigned less-than সত্য
  • Signed less-than সত্য, unsigned less-than মিথ্যা
  • Z = 1, কারণ দুটির বিট একই রকম দেখায়
  • Signed আর unsigned less-than দুটিই সত্য

৮-বিটে 5 − 9 বিয়োগের পর carry flag-এর মান x86 (CF) আর ARM (C)-এ কত হবে?

  • দুটিতেই 1
  • x86 CF = 0, ARM C = 1
  • x86 CF = 1, ARM C = 0
  • দুটিতেই 0

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. ALU = combinational ক্যালকুলেটর: A, B আর control ঢোকে; ফল আর Z, N, C, V বেরোয়। ভেতরে সব কাজ একসাথে হয়, mux একটি বেছে নেয়।
  2. ১-বিট slice: AND, OR, full adder, Less আর ৪:১ mux; Ainvert ও Binvert-এর XOR. ৩২টি slice জুড়ে ripple ALU (দ্রুত নকশায় carry-lookahead).
  3. বিয়োগ = A + B′ + 1 (Bnegate = Binvert + বিট ০-এর CarryIn); NOR = A′·B′. MIPS-ধাঁচের control: 0000 AND, 0001 OR, 0010 add, 0110 sub, 0111 slt, 1100 NOR.
  4. Flag: Z = সব বিটের NOR, N = MSB, V = cn ⊕ cn−1, C = CarryOut (বিয়োগে ARM: no-borrow, x86: borrow). Signed less = N ⊕ V, unsigned less = borrow.
  5. Barrel shifter: log2 n স্তরের ২:১ mux, স্তর k-এর select = shift পরিমাণের বিট k; srl-এ ০, sra-তে sign ঢোকে। RISC-V flag ছাড়াই blt/bltu-তে তুলনা করে।

পরের পর্বে: এত gate হাতে আঁকা আর সম্ভব নয় — তাই hardware description language. Verilog দিয়ে gate, adder আর ALU লেখা, testbench বানিয়ে simulation চালানো, আর কোন কোড synthesizable তা চেনা।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান