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

গুণ — shift-add, Booth-এর algorithm আর দ্রুত multiplier

Binary Multiplication — Shift-and-Add Hardware, Booth's Algorithm, Radix-4 Booth, Wallace/Dadda Trees, Multiply Instructions and Fixed-Point

Taught in Bangla, this lesson does binary multiplication by hand with partial products, proves the product needs 2n bits, traces the shift-and-add multiplier step by step, multiplies signed numbers with Booth's algorithm through a full trace, proof and worst case, halves partial products with radix-4 Booth, counts Wallace and Dadda tree levels, and covers RISC-V mul and mulh, x86 imul, strength reduction and Q15 fixed-point, with simulators and live C code.

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

  • হাতে binary গুণ — partial product, shift আর যোগ; কেন n × n বিটের গুণফলে 2n বিট লাগে (প্রমাণসহ)
  • Sequential shift-and-add multiplier-এর দুই সংস্করণ, আর পরীক্ষার সেই চেনা টেবিল সারি ধরে ধরে (0110 × 0011)
  • Signed গুণ: চিহ্ন আলাদা করা বনাম Booth-এর algorithm — পূর্ণ trace, প্রমাণ, সবচেয়ে খারাপ ক্ষেত্র আর radix-4 Booth recoding
  • দ্রুত multiplier: array multiplier, Wallace আর Dadda tree-তে কতটি স্তর লাগে, আর আধুনিক CPU কেন কয়েক cycle-এ গুণ করে
  • RISC-V, x86 আর ARM-এর গুণের instruction, ওপরের অর্ধেকের ব্যবহার, ধ্রুবক দিয়ে গুণ (strength reduction) আর Q15 fixed-point গুণ

১ · কাগজে-কলমে binary গুণ — partial product আর 2n বিট

স্কুলে ২৩ × ১২ কীভাবে গুণ করতেন মনে করুন: ২৩-কে প্রথমে ২ দিয়ে গুণ (৪৬), তারপর ১ দিয়ে গুণ করে এক ঘর বাঁয়ে সরিয়ে লেখা (২৩০), শেষে দুটি সারি যোগ (২৭৬)। প্রতিটি সারি হলো একটি partial product — আংশিক গুণফল। যাকে গুণ করা হচ্ছে (২৩) তার নাম multiplicand, আর যা দিয়ে গুণ (১২) তার নাম multiplier.

Binary-তে একই পদ্ধতি আরও সহজ, কারণ প্রতিটি অঙ্ক শুধু ০ বা ১. কোনো সংখ্যাকে ১ দিয়ে গুণ করলে সংখ্যাটিই থাকে, ০ দিয়ে গুণ করলে ০. তাই প্রতিটি partial product হয় multiplicand নিজে, নয়তো শূন্য। Hardware-এর ভাষায়: partial product = multiplicand-এর প্রতিটি বিট AND multiplier-এর একটি বিট. কোনো গুণের নামতা মুখস্থ লাগে না — একটি AND gate-ই ১ বিট × ১ বিটের গুণ। এরপর partial product-গুলোকে যথাস্থানে সরিয়ে (left shift) যোগ করলেই গুণফল।

একটি উদাহরণ: 1011 (১১) × 1101 (১৩). Multiplier-এর বিটগুলো ডান থেকে পড়ুন — 1, 0, 1, 1:

          1 0 1 1      multiplicand  = 11
        x 1 1 0 1      multiplier    = 13
        ---------
          1 0 1 1      bit 0 = 1 -> 1011, shift 0
        0 0 0 0        bit 1 = 0 -> 0000, shift 1
      1 0 1 1          bit 2 = 1 -> 1011, shift 2
    1 0 1 1            bit 3 = 1 -> 1011, shift 3
    ---------------
    1 0 0 0 1 1 1 1    product = 128 + 8 + 4 + 2 + 1 = 143

১১ × ১৩ = ১৪৩ — মিলে গেল। লক্ষ করুন, দুটি ৪ বিটের সংখ্যার গুণফল ৮ বিটের। এটা কাকতালীয় নয়।

প্রমাণ · কেন 2n বিট

Unsigned: n বিটের সবচেয়ে বড় সংখ্যা 2n − 1. তাই গুণফল সর্বোচ্চ (2n − 1)2 = 22n − 2n+1 + 1, যা 22n-এর চেয়ে ছোট — অর্থাৎ 2n বিটে সবসময় ধরে। আবার n ≥ 2 হলে এটি 22n−1-এর চেয়ে বড় বা সমান (যেমন ৪ বিটে ১৫ × ১৫ = ২২৫ ≥ ১২৮), তাই 2n − 1 বিটে সবসময় ধরে না। Signed (two's complement): সবচেয়ে বড় মানের গুণফল (−2n−1) × (−2n−1) = +22n−2; ধনাত্মক এই সংখ্যার জন্য 2n − 1 বিটের মান আর একটি sign bit, অর্থাৎ ঠিক 2n বিট। যেমন ৪ বিটে (−৮) × (−৮) = ৬৪ = 0100 0000.

এর একটি সরাসরি ফল আছে। C-তে দুটি int32_t গুণ করলে ফলও int32_t — অর্থাৎ ৬৪ বিটের আসল গুণফলের শুধু নিচের ৩২ বিট থাকে। ওপরের অর্ধেক হারিয়ে গেলে তাকেই বলি গুণের overflow. Unsigned-এ এটি নিয়মমাফিক wrap-around, কিন্তু signed int-এ C ভাষায় এটি undefined behaviour — বিস্তারিত পর্ব ০৫-এ (Integer-এর বিট-রূপ)। অংশ ৬-এ দেখবেন, hardware ওপরের অর্ধেকও দিতে পারে, আর সেটি দিয়েই overflow ধরা যায়।

মহল্লার মুদি দোকানের দাঁড়িপাল্লা আর ১, ২, ৪, ৮ কেজির বাটখারা ভাবুন। একজন ক্রেতা ১৩টি একই রকম বস্তার দাম জানতে চাইলেন, প্রতিটির দাম M টাকা। দোকানি ১৩-কে বাটখারায় ভাঙেন: ৮ + ৪ + ১ (২ কেজিরটি লাগে না)। তারপর দাম লেখেন M × ৮, M × ৪ আর M × ১ — তিনটি সারি — আর যোগ করেন। Binary গুণ ঠিক এটাই: কোন বাটখারা পাল্লায় উঠল, তা হলো multiplier-এর বিট (১৩ = 1101), আর প্রতিটি সারি একটি partial product, যা M-কে শুধু সরিয়ে (×২, ×৪, ×৮) পাওয়া যায়। কঠিন কাজটি গুণ নয়, অনেক সারি যোগ করা — আর এই পর্বের বাকি সবটাই সেই যোগকে দ্রুত করার গল্প।

তাহলে n বিটের গুণে মোট কাজ: n2টি AND (প্রতিটি partial product-এ n টি বিট, n টি সারি) আর n টি সারির যোগ। এই যোগ দুইভাবে করা যায় — সময়ে ছড়িয়ে (একটি adder দিয়ে প্রতি clock-এ একটি সারি; অংশ ২), অথবা জায়গায় ছড়িয়ে (অনেক adder একসাথে; অংশ ৫)। প্রথমটি সস্তা কিন্তু ধীর, দ্বিতীয়টি দ্রুত কিন্তু বড়। আর signed সংখ্যার জন্য একটি তৃতীয় কৌশল লাগে (অংশ ৩–৪)।

২ · Shift-and-add multiplier — একটি adder, n টি ধাপ

সবচেয়ে সরল hardware ঠিক কাগজের পদ্ধতির নকল করে: প্রতি clock cycle-এ একটি partial product যোগ। Patterson আর Hennessy-র পাঠ্যবইয়ে (RISC-V সংস্করণ) এর দুটি ধাপে উন্নতি দেখানো হয়; বিশ্ববিদ্যালয় আর চাকরির পরীক্ষায় এই দুটিই সবচেয়ে বেশি আসে। এখানে ৩২ বিটের গুণ ধরে বর্ণনা, উদাহরণে ৪ বিট।

প্রথম সংস্করণ

৬৪ বিটের multiplicand register (শুরুতে ডান অর্ধেকে multiplicand, প্রতি ধাপে এক ঘর বাঁয়ে সরে), ৩২ বিটের multiplier register (প্রতি ধাপে ডানে সরে), ৬৪ বিটের ALU আর ৬৪ বিটের product register (শুরুতে ০)। প্রতি ধাপে: multiplier-এর বিট ০ = 1 হলে product += multiplicand; তারপর multiplicand বাঁয়ে, multiplier ডানে।

৩২ বার × ৩টি কাজ

উন্নত (refined) সংস্করণ

Multiplicand স্থির থাকে (৩২ বিট), ALU মাত্র ৩২ বিটের। ৬৪ বিটের product register-এর ডান অর্ধেকে শুরুতে multiplier বসে, বাঁ অর্ধেক ০. প্রতি ধাপে: Product[0] = 1 হলে বাঁ অর্ধেকে multiplicand যোগ; তারপর পুরো product register এক ঘর ডানে।

অর্ধেক adder, একটি register কম

উন্নত সংস্করণের চালাকিটা বুঝুন। প্রথম সংস্করণে ৬৪ বিটের adder লাগত, অথচ প্রতি ধাপে আসলে মাত্র ৩২টি বিটে নতুন কিছু যোগ হয় — বাকি বিটগুলো হয় শূন্য, নয়তো আর বদলাবে না। তাই multiplicand-কে বাঁয়ে না সরিয়ে উল্টো product-কে ডানে সরানো হয়: আপেক্ষিক অবস্থান একই থাকে, কিন্তু adder সবসময় একই ৩২টি বিটে কাজ করে। আর product যত ডানে সরে, multiplier-এর ব্যবহার হয়ে যাওয়া বিট তত বেরিয়ে যায় — ঠিক সেই খালি জায়গাতেই multiplier রাখা যায়। একটি register বাঁচল। যোগের carry-out হারাতে না দিতে product register আসলে এক বিট বেশি (৩২ বিটে ৬৫) — shift-এর সময় সেই carry বিট বাঁ দিক দিয়ে ঢোকে।

Multiplicand (n bit) n-bit adder hi (+carry) lo = multiplier Product register (2n bit), shift right → hi back Control test Prod[0] write / shift
ছবি ২১.১ — উন্নত shift-and-add multiplier. Adder-এর একটি ইনপুট multiplicand, অন্যটি product register-এর বাঁ অর্ধেক; ফল আবার বাঁ অর্ধেকেই লেখা হয়। Control শুধু Product[0] দেখে ঠিক করে যোগ হবে কি না।

এখন সেই চেনা পরীক্ষার টেবিল। Multiplicand = 0110 (৬), multiplier = 0011 (৩), উত্তর হওয়া উচিত ১৮ = 0001 0010. প্রতিটি iteration-এ দুটি কাজ: ধাপ ১ — Product[0] দেখে যোগ বা কিছু না; ধাপ ২ — ডানে shift.

Iteration 0শুরুর মান
iter  step                     Mcand   Product (hi │ lo)
 0    initial values           0110    0000 │ 0011

hi = 0000, lo = multiplier 0011
Prod[0] = 1  -> next step will add
Iteration 1Prod[0] = 1 → যোগ, তারপর shift
iter  step                     Mcand   Product (hi │ lo)
 0    initial values           0110    0000 │ 0011
 1    1a: hi = hi + Mcand      0110    0110 │ 0011
      2 : shift right          0110    0011 │ 0001

0000 + 0110 = 0110, carry 0
after shift Prod[0] = 1
Iteration 2আবার Prod[0] = 1 → যোগ
iter  step                     Mcand   Product (hi │ lo)
 1    2 : shift right          0110    0011 │ 0001
 2    1a: hi = hi + Mcand      0110   01001 │ 0001
      2 : shift right          0110    0100 │ 1000

0011 + 0110 = 1001 (5 bits: 0 1001)
the carry bit (0) enters from the left on shift
Iteration 3Prod[0] = 0 → শুধু shift
iter  step                     Mcand   Product (hi │ lo)
 2    2 : shift right          0110    0100 │ 1000
 3    1 : no add (Prod[0]=0)   0110    0100 │ 1000
      2 : shift right          0110    0010 │ 0100
Iteration 4Prod[0] = 0 → শুধু shift
iter  step                     Mcand   Product (hi │ lo)
 3    2 : shift right          0110    0010 │ 0100
 4    1 : no add (Prod[0]=0)   0110    0010 │ 0100
      2 : shift right          0110    0001 │ 0010
ফল৪টি iteration পরে
Product = 0001 0010 = 16 + 2 = 18   (6 x 3 = 18)

adds performed : 2   (multiplier 0011 has two 1s)
iterations     : 4   (always n, here n = 4)
multiplier bits: all shifted out of lo
  1. শুরু। Product register-এর বাঁ অর্ধেক (hi) 0000, ডান অর্ধেকে (lo) multiplier 0011. Multiplicand 0110 আলাদা register-এ, পুরো গুণে বদলায় না। Control শুধু Product-এর শেষ বিট দেখে — এখন সেটি ১.
  2. Iteration ১. Prod[0] = 1, তাই hi-তে multiplicand যোগ: 0000 + 0110 = 0110. তারপর পুরো ৮ বিট (carry-সহ) এক ঘর ডানে: 0011 0001. Multiplier-এর ব্যবহৃত বিটটি ডান দিয়ে বেরিয়ে গেল, আর পরের বিট (১) এখন Prod[0]-এ।
  3. Iteration ২. আবার Prod[0] = 1: 0011 + 0110 = 1001. এখানে যোগফল ৪ বিটেই ধরেছে, তাই carry 0; ৫ বিটে লিখলে 0 1001. Shift-এর পর 0100 1000. যোগের carry থাকলে এই shift-এর সময় সেটিই বাঁ দিক দিয়ে hi-তে ঢুকত — এজন্য register আসলে এক বিট বড়।
  4. Iteration ৩. Prod[0] = 0 (multiplier-এর bit 2 ছিল ০), তাই কোনো যোগ নয় — শুধু shift: 0010 0100. Partial product শূন্য হলে কাজও শূন্য, কিন্তু cycle-টি তবু খরচ হয়।
  5. Iteration ৪. আবার Prod[0] = 0, শুধু shift: 0001 0010. চারটি iteration শেষ — multiplier-এর চারটি বিটই ব্যবহৃত হয়ে বেরিয়ে গেছে।
  6. ফল। Product register-এ 0001 0010 = ১৮, ঠিক ৬ × ৩. যোগ হয়েছে দুবার, কারণ multiplier-এ দুটি ১. কিন্তু iteration সবসময় n টি — এই সরল নকশায় ৩২ বিটের গুণে ৩২টি iteration, প্রতিটিতে অন্তত এক cycle.

প্রথম সংস্করণে প্রতি iteration-এ তিনটি আলাদা কাজ (যোগ, multiplicand shift, multiplier shift) ছিল; প্রতিটি এক cycle নিলে ৩২ বিটের গুণে প্রায় ১০০ cycle. উন্নত সংস্করণে যোগ আর shift একই cycle-এ করা যায় (adder-এর আউটপুট সরাসরি এক ঘর সরিয়ে লেখা), তাই প্রায় ৩২ cycle. দুটি নকশার টেবিল পরীক্ষায় আলাদা — প্রথমটিতে কলাম থাকে “Multiplier, Multiplicand, Product”, উন্নতটিতে “Multiplicand, Product”. প্রশ্নে কোন সংস্করণ চাওয়া হয়েছে তা দেখে নিন।

নিচের simulator একই উন্নত নকশা চালায় আর পুরো টেবিল ছাপে — ৪ বিটে প্রতিটি ধাপ, ৮ বিটে প্রতি iteration-এর এক লাইন। নিজের সংখ্যা বসিয়ে পরীক্ষার টেবিল মিলিয়ে নিতে পারবেন:

// Refined shift-and-add multiplier (Patterson-Hennessy style):
// 2n-bit Product register, multiplier starts in its right half.
// Each iteration: if Product[0] = 1, add Multiplicand to the left half
// (with a carry bit), then shift the whole (carry:Product) right by 1.
const bin = (x, n) => x.toString(2).padStart(n, "0");
function multiply(mcand, mplier, n, full) {
  let hi = 0, lo = mplier, adds = 0;           // Product = hi:lo
  console.log("iter  step                         mcand     product");
  const row = (it, st, h, hw) => console.log(`${String(it).padStart(3)}   ${st.padEnd(27)}` +
    `  ${bin(mcand, n)}  ${bin(h, hw).padStart(n + 1)} ${bin(lo, n)}`);
  row(0, "initial values", hi, n);
  for (let i = 1; i <= n; i++) {
    const bit = lo & 1;
    if (bit) { hi += mcand; adds++; }          // hi may become n+1 bits
    if (full) row(i, bit ? "1a: Prod[0]=1 -> hi+=Mcand" : "1 : Prod[0]=0 -> no add", hi, n + 1);
    lo = (lo >> 1) | ((hi & 1) << (n - 1));    // shift carry:hi:lo right
    hi >>= 1;
    row(full ? "" : i, full ? "2 : shift Product right" : (bit ? "add, shift" : "shift only"), hi, n);
  }
  const p = hi * (1 << n) + lo;
  console.log(`${bin(mcand, n)} x ${bin(mplier, n)} = ${bin(hi, n)} ${bin(lo, n)} = ${p}` +
    `  (check ${mcand} x ${mplier} = ${mcand * mplier}, adds = ${adds})\n`);
}
multiply(0b0110, 0b0011, 4, true);
multiply(0b11010110, 0b00001011, 8, false);
multiply(0b11111111, 0b11111111, 8, false);

আউটপুট:

iter  step                         mcand     product
  0   initial values               0110   0000 0011
  1   1a: Prod[0]=1 -> hi+=Mcand   0110  00110 0011
      2 : shift Product right      0110   0011 0001
  2   1a: Prod[0]=1 -> hi+=Mcand   0110  01001 0001
      2 : shift Product right      0110   0100 1000
  3   1 : Prod[0]=0 -> no add      0110  00100 1000
      2 : shift Product right      0110   0010 0100
  4   1 : Prod[0]=0 -> no add      0110  00010 0100
      2 : shift Product right      0110   0001 0010
0110 x 0011 = 0001 0010 = 18  (check 6 x 3 = 18, adds = 2)

iter  step                         mcand     product
  0   initial values               11010110   00000000 00001011
  1   add, shift                   11010110   01101011 00000101
  2   add, shift                   11010110   10100000 10000010
  3   shift only                   11010110   01010000 01000001
  4   add, shift                   11010110   10010011 00100000
  5   shift only                   11010110   01001001 10010000
  6   shift only                   11010110   00100100 11001000
  7   shift only                   11010110   00010010 01100100
  8   shift only                   11010110   00001001 00110010
11010110 x 00001011 = 00001001 00110010 = 2354  (check 214 x 11 = 2354, adds = 3)

iter  step                         mcand     product
  0   initial values               11111111   00000000 11111111
  1   add, shift                   11111111   01111111 11111111
  2   add, shift                   11111111   10111111 01111111
  3   add, shift                   11111111   11011111 00111111
  4   add, shift                   11111111   11101111 00011111
  5   add, shift                   11111111   11110111 00001111
  6   add, shift                   11111111   11111011 00000111
  7   add, shift                   11111111   11111101 00000011
  8   add, shift                   11111111   11111110 00000001
11111111 x 11111111 = 11111110 00000001 = 65025  (check 255 x 255 = 65025, adds = 8)

প্রথম টেবিলটি ঠিক stepper-এর সারিগুলো: iteration ১ আর ২-এ “1a” (যোগ), ৩ আর ৪-এ শুধু shift, শেষে 0001 0010 = 18. যোগের সারিতে hi ৫ বিটে দেখানো হয়েছে (00110, 01001) — বাড়তি বাঁয়ের বিটটি সেই carry. দ্বিতীয় উদাহরণে ২১৪ × ১১: multiplier 00001011-এ তিনটি ১, তাই “add, shift” ঠিক তিনবার (iteration ১, ২, ৪), ফল ২৩৫৪. তৃতীয়টি সবচেয়ে বড় ৮ বিটের গুণ, ২৫৫ × ২৫৫ = ৬৫০২৫ = 11111110 00000001 — পুরো ১৬ বিট লেগেছে, অংশ ১-এর প্রমাণ মতো। এখানে প্রতিটি iteration-এ যোগ, আর লক্ষ করুন iteration ২ থেকে প্রতিবার যোগে carry হয়েছে, যা shift-এর সময় hi-র সবচেয়ে বাঁয়ে ১ হয়ে ঢুকেছে। সময় কিন্তু তিন ক্ষেত্রেই একই — n টি iteration.

৩ · Signed গুণ — চিহ্ন আলাদা করা বনাম Booth-এর algorithm

Shift-and-add শুধু unsigned সংখ্যায় সরাসরি ঠিক উত্তর দেয়। ৪ বিটে 1101-কে unsigned ধরলে ১৩, কিন্তু two's complement-এ −৩. 0111 × 1101 unsigned হিসাবে ৭ × ১৩ = ৯১ দেবে, অথচ আমরা চাই ৭ × (−৩) = −২১. Signed গুণের দুটি প্রচলিত পথ আছে।

পথ ১ — চিহ্ন আলাদা করে ঠিক করা। দুটি সংখ্যাকে ধনাত্মক করুন (ঋণাত্মক হলে two's complement নিয়ে), unsigned গুণ করুন, আর মূল দুটি চিহ্ন আলাদা হলে ফলকে ঋণাত্মক করুন। Patterson–Hennessy-র বই প্রথমে এই পথই দেখায়: চিহ্ন XOR করে মনে রাখা, ৩১ বিটের magnitude-এ গুণ। সরল, কিন্তু শুরুতে আর শেষে বাড়তি negation-এর ধাপ লাগে। (আরেকটি সূক্ষ্ম পথও আছে: multiplier-এর MSB-র ওজন two's complement-এ −2n−1, তাই শেষ iteration-এ যোগের বদলে বিয়োগ করলে আর shift-কে arithmetic shift করলে সরাসরি signed ফল আসে।)

পথ ২ — Booth-এর পদ্ধতি। ১৯৫১ সালে লন্ডনের Birkbeck College-এ কাজ করার সময় Andrew D. Booth একটি পদ্ধতি প্রকাশ করেন, যা two's complement সংখ্যাকে কোনো চিহ্ন-সংশোধন ছাড়াই সরাসরি গুণ করে। Booth-এর algorithm-এ চারটি register বা ঘর:

  • M — multiplicand (n বিট, স্থির)
  • A — accumulator, শুরুতে ০ (n বিট; গুণফলের ওপরের অর্ধেক এখানে জমে)
  • Q — multiplier (n বিট; শেষে গুণফলের নিচের অর্ধেক)
  • Q−1 — Q-এর ডানে একটি বাড়তি বিট, শুরুতে ০

প্রতিটি ধাপে (মোট n বার) জোড়া (Q0, Q−1) দেখুন:

Q0 Q−1অর্থকাজতারপর
0 0০-এর সারির মাঝখানেকিছু নয়A:Q:Q−1-এর arithmetic right shift (A-র sign bit কপি হয়)
1 0১-এর সারি এখানে শুরু (ডান থেকে পড়লে)A = A − M
1 1১-এর সারির মাঝখানেকিছু নয়
0 1১-এর সারি এখানে শেষA = A + M

এবার পূর্ণ উদাহরণ: ৭ × (−৩), ৪ বিটে। M = 0111, Q = 1101 (−৩), A = 0000, Q−1 = 0. A − M করতে A-র সাথে −M = 1001 যোগ করা হয়। উত্তর হওয়া উচিত −২১ = ৮ বিটে 1110 1011.

শুরুM = 0111 (7), Q = 1101 (−3)
step  Q0 Q-1  operation     A     Q     Q-1
 0             initial      0000  1101   0

-M = 1001  (two's complement of 0111)
first pair (Q0, Q-1) = (1, 0)
ধাপ ১(1, 0) → A = A − M, shift
step  Q0 Q-1  operation     A     Q     Q-1
 1     1  0   A = A - M     1001  1101   0
              shift         1100  1110   1

0000 + 1001 = 1001
shift: A's sign bit 1 is copied in from the left
ধাপ ২(0, 1) → A = A + M, shift
step  Q0 Q-1  operation     A     Q     Q-1
 1            shift         1100  1110   1
 2     0  1   A = A + M     0011  1110   1
              shift         0001  1111   0

1100 + 0111 = 1 0011 -> keep 4 bits 0011
(carry out of A is dropped)
ধাপ ৩(1, 0) → A = A − M, shift
step  Q0 Q-1  operation     A     Q     Q-1
 2            shift         0001  1111   0
 3     1  0   A = A - M     1010  1111   0
              shift         1101  0111   1

0001 + 1001 = 1010
ধাপ ৪(1, 1) → শুধু shift
step  Q0 Q-1  operation     A     Q     Q-1
 3            shift         1101  0111   1
 4     1  1   none          1101  0111   1
              shift         1110  1011   1
ফলA:Q পড়ুন ৮ বিটের signed হিসেবে
A:Q = 1110 1011
    = -128 + 64 + 32 + 8 + 2 + 1 = -21   (7 x -3 = -21)

check: invert 1110 1011 -> 0001 0100, +1 -> 0001 0101 = 21
add/sub operations: 3   (sub, add, sub)
  1. শুরু। A = 0000, Q-তে multiplier 1101 (−৩), Q−1 = 0. −M আগে থেকে বের করে রাখুন: 0111-এর বিট উল্টে 1000, তারপর +১ = 1001. প্রথম জোড়া (Q0, Q−1) = (1, 0).
  2. ধাপ ১ — বিয়োগ। (1, 0) মানে ১-এর সারি শুরু, তাই A = A − M = 0000 + 1001 = 1001. তারপর A:Q:Q−1 পুরো ৯ বিট এক ঘর ডানে, আর A-র sign bit (১) বাঁয়ে কপি: A = 1100, Q = 1110, Q−1 = 1 (Q-এর পুরোনো শেষ বিট)।
  3. ধাপ ২ — যোগ। এখন জোড়া (0, 1) — ১-এর সারি শেষ, তাই A = A + M = 1100 + 0111 = 1 0011; ৪ বিট রাখলে 0011 (বাইরের carry বাদ, two's complement যোগের নিয়মে)। Shift-এর পর A = 0001, Q = 1111, Q−1 = 0. A-র sign bit ০, তাই বাঁয়ে ০ ঢুকেছে।
  4. ধাপ ৩ — আবার বিয়োগ। জোড়া (1, 0): A = 0001 + 1001 = 1010. Shift: A = 1101, Q = 0111, Q−1 = 1. Multiplier 1101-এর বাঁয়ের দুটি ১ মিলে একটি নতুন সারি শুরু হলো।
  5. ধাপ ৪ — শুধু সরানো। জোড়া (1, 1) — সারির মাঝখানে, কোনো যোগ-বিয়োগ নয়। Arithmetic shift: A = 1110, Q = 1011, Q−1 = 1. চারটি ধাপ শেষ।
  6. ফল। A:Q = 1110 1011. MSB ১, তাই ঋণাত্মক; মান −১২৮ + ৬৪ + ৩২ + ৮ + ২ + ১ = −২১. বিট উল্টে +১ করে মিলিয়ে দেখুন: 0001 0101 = ২১. কোনো চিহ্ন-সংশোধন লাগেনি — Booth সরাসরি signed ফল দিয়েছে। কাজ হয়েছে তিনটি (বিয়োগ, যোগ, বিয়োগ)।

এবার দুটিই ঋণাত্মক: (−৪) × (−৬). M = 1100, −M = 0100, Q = 1010. উত্তর +২৪ = 0001 1000. হাতে টেবিল করলে (simulator-এর আউটপুট থেকে হুবহু):

step  Q0Q-1  operation   A     Q     Q-1
 1     00    shift       0000  0101   0
 2     10    A = A - M   0100  0101   0     (0000 + 0100)
 2           shift       0010  0010   1
 3     01    A = A + M   1110  0010   1     (0010 + 1100)
 3           shift       1111  0001   0
 4     10    A = A - M   0011  0001   0     (1111 + 0100 = 1 0011)
 4           shift       0001  1000   1
result A:Q = 0001 1000 = +24

নিচের tracer একই নিয়মে যেকোনো উদাহরণ চালায়, প্রতিটি ফল সাধারণ গুণের সাথে মেলায়, আর কয়টি যোগ-বিয়োগ লাগল তা গোনে। শেষের দিকে ৮ বিটের দুটি multiplier ইচ্ছা করে বাছা: একটিতে ১-এর একটি লম্বা সারি, অন্যটিতে ০ আর ১ পালাক্রমে। আর একটি উদাহরণ ইচ্ছা করে একটি ফাঁদ দেখায় — খুঁজে বের করুন:

// Booth's algorithm (radix-2), n-bit two's complement M and Q.
// Look at (Q0, Q-1): 10 -> A = A - M, 01 -> A = A + M, 00/11 -> nothing;
// then arithmetic shift right of A:Q:Q-1. After n steps, A:Q = M x Q.
const bin = (x, n) => (x & ((1 << n) - 1)).toString(2).padStart(n, "0");
function booth(m, q, n, show) {
  const mask = (1 << n) - 1, M = m & mask;
  let A = 0, Q = q & mask, Q_1 = 0, ops = 0;
  if (show) console.log(`M = ${bin(M, n)} (${m}), Q = ${bin(Q, n)} (${q})\nstep Q0Q-1  operation   A    Q    Q-1`);
  for (let i = 1; i <= n; i++) {
    const pair = `${Q & 1}${Q_1}`;
    let op = "none     ";
    if (pair === "10") { A = (A - M) & mask; op = "A = A - M"; ops++; }
    if (pair === "01") { A = (A + M) & mask; op = "A = A + M"; ops++; }
    if (show && op[0] === "A") console.log(`  ${i}    ${pair}    ${op}  ${bin(A, n)} ${bin(Q, n)}  ${Q_1}`);
    Q_1 = Q & 1;                                    // arithmetic shift right A:Q:Q-1
    Q = ((Q >> 1) | ((A & 1) << (n - 1))) & mask;
    A = ((A >> 1) | (A & (1 << (n - 1)))) & mask;
    if (show) console.log(`  ${i}    ${op[0] === "A" ? "  " : pair}    shift      ${bin(A, n)} ${bin(Q, n)}  ${Q_1}`);
  }
  let r = A * (1 << n) + Q;
  if (A >> (n - 1)) r -= 2 ** (2 * n);              // read A:Q as signed 2n bits
  console.log(`${String(m).padStart(4)} x ${String(q).padStart(4)} = ${bin(A, n)} ${bin(Q, n)} = ${r}` +
    `  check ${m * q} ${r === m * q ? "OK" : "WRONG"}, add/sub ops = ${ops}${show ? "\n" : ""}`);
}
booth(7, -3, 4, true);
booth(-4, -6, 4, true);
for (const [m, q] of [[-8, 7], [5, -8], [-7, -7]]) booth(m, q, 4, false);
booth(25, 0b01111100, 8, false);   // one run of 1s: 0111 1100
booth(25, 0b01010101, 8, false);   // worst case: alternating bits
booth(-100, -128, 8, false);

আউটপুট:

M = 0111 (7), Q = 1101 (-3)
step Q0Q-1  operation   A    Q    Q-1
  1    10    A = A - M  1001 1101  0
  1          shift      1100 1110  1
  2    01    A = A + M  0011 1110  1
  2          shift      0001 1111  0
  3    10    A = A - M  1010 1111  0
  3          shift      1101 0111  1
  4    11    shift      1110 1011  1
   7 x   -3 = 1110 1011 = -21  check -21 OK, add/sub ops = 3

M = 1100 (-4), Q = 1010 (-6)
step Q0Q-1  operation   A    Q    Q-1
  1    00    shift      0000 0101  0
  2    10    A = A - M  0100 0101  0
  2          shift      0010 0010  1
  3    01    A = A + M  1110 0010  1
  3          shift      1111 0001  0
  4    10    A = A - M  0011 0001  0
  4          shift      0001 1000  1
  -4 x   -6 = 0001 1000 = 24  check 24 OK, add/sub ops = 3

  -8 x    7 = 0011 1000 = 56  check -56 WRONG, add/sub ops = 2
   5 x   -8 = 1101 1000 = -40  check -40 OK, add/sub ops = 1
  -7 x   -7 = 0011 0001 = 49  check 49 OK, add/sub ops = 3
  25 x  124 = 00001100 00011100 = 3100  check 3100 OK, add/sub ops = 2
  25 x   85 = 00001000 01001101 = 2125  check 2125 OK, add/sub ops = 8
-100 x -128 = 00110010 00000000 = 12800  check 12800 OK, add/sub ops = 1

প্রথম দুটি টেবিল stepper আর হাতের টেবিলের সাথে হুবহু মিলেছে: -21 আর 24, দুটিতেই তিনটি কাজ। এবার ফাঁদ: -8 x 7-এ ফল এসেছে ৫৬, অথচ হওয়া উচিত −৫৬ — WRONG. কারণ M = −৮ = 1000 হলো ৪ বিটের সবচেয়ে ছোট সংখ্যা, আর এর ঋণাত্মক +৮ ৪ বিটে লেখাই যায় না: −M-ও 1000 হয়ে যায়, ফলে “A − M” আসলে A − (−8) না হয়ে A + (−8) হয়। সমাধান: A-কে এক বিট চওড়া (n + 1 বিট) রাখা, অথবা M-কে আগে sign-extend করা — বাস্তব hardware তাই করে। লক্ষ করুন, 5 x -8 ঠিক আছে; Q-তে −৮ থাকলে সমস্যা নেই, সমস্যা শুধু M-এ। এখন ৮ বিটের দুটি লাইন দেখুন: multiplier 0111 1100 (১২৪)-এ পাঁচটি ১, অথচ Booth-এ মাত্র ২টি কাজ — সারির শুরুতে একটি বিয়োগ, শেষে একটি যোগ। কিন্তু 0101 0101 (৮৫)-এ মাত্র চারটি ১, অথচ Booth লেগেছে ৮টি কাজ — প্রতিটি ধাপে হয় যোগ নয় বিয়োগ। সাধারণ shift-and-add-এ এখানে ৪টি যোগই লাগত। পরের অংশে এর কারণ।

৪ · Booth কেন কাজ করে — প্রমাণ, সবচেয়ে খারাপ ক্ষেত্র আর radix-4 Booth

মূল ভাবনা: ১-এর সারি = দুটি ঘাতের বিয়োগফল। 0111 1100 = ১২৪. এটিকে পাঁচটি ঘাতের যোগ (৬৪ + ৩২ + ১৬ + ৮ + ৪) হিসেবে না দেখে দেখুন ১২৮ − ৪ হিসেবে। তাহলে M × ১২৪ = M × ১২৮ − M × ৪: একটি যোগ আর একটি বিয়োগ, দুটি shift-এর জায়গায়। দোকানে ৯৯৮ টাকার জিনিস কিনতে আপনি যেমন হাজার টাকার নোট দিয়ে ২ টাকা ফেরত নেন — একটি বড় লেনদেন আর একটি ছোট ফেরত, অনেকগুলো খুচরো নোটের বদলে।

প্রমাণ · Booth recoding

n বিটের two's complement সংখ্যা Q-এর মান Q = −qn−12n−1 + Σi=0n−2 qi2i. দাবি: q−1 = 0 ধরলে Q = Σi=0n−1 (qi−1 − qi) 2i.

ডান দিক খুলুন: Σ qi−12i − Σ qi2i. প্রথম যোগফলে j = i − 1 বসালে তা Σj=0n−2 qj2j+1 (q−1 = 0 বলে j = −1 পদটি শূন্য)। এখন দুটি যোগফলের j = 0…n−2 পদগুলো মেলান: qj(2j+1 − 2j) = qj2j. দ্বিতীয় যোগফলের শুধু i = n−1 পদটি বাকি থাকে: −qn−12n−1. যোগ করলে ঠিক two's complement-এর মান। ∎

তাই M × Q = Σ (qi−1 − qi) · M · 2i, আর প্রতিটি অঙ্ক di = qi−1 − qi ∈ {−1, 0, +1}: (qi, qi−1) = (1, 0) হলে −1 (বিয়োগ), (0, 1) হলে +1 (যোগ), সমান হলে ০. প্রতি ধাপের right shift ঠিক 2i-এর ওজন বসিয়ে দেয়। আর সবচেয়ে বাঁয়ের বিটের ঋণাত্মক ওজন সূত্রেই ধরা আছে, তাই signed সংখ্যায় আলাদা সংশোধন লাগে না — Booth-এর সবচেয়ে বড় সুবিধা।

এই অঙ্কের সারিকে বলে Booth recoding, আর {−1, 0, +1} অঙ্কের সংখ্যারূপকে signed-digit রূপ। যেমন 1101 (−৩) → (qi−1 − qi) ডান থেকে: −1, +1, −1, 0 → বাঁ থেকে লিখলে 0 −1 +1 −1 = −৪ + ২ − ১ = −৩. ✓

সবচেয়ে খারাপ ক্ষেত্র। di শূন্য নয় ঠিক তখন, যখন পাশাপাশি দুটি বিট আলাদা। 0101 0101-এর মতো পালাক্রমের প্যাটার্নে প্রতিটি জোড়াই আলাদা, তাই n টি কাজ — সাধারণ shift-and-add-এর চেয়েও বেশি (সেখানে শুধু ১-এর সংখ্যা, ৪টি)। গড়ে এলোমেলো সংখ্যায় দুই পদ্ধতিতেই প্রায় n/2 কাজ। তাহলে লাভ কোথায়? প্রথমত, signed সংখ্যা সরাসরি — এটিই আসল লাভ। দ্বিতীয়ত, যে পুরোনো যন্ত্রে শূন্যের ওপর দিয়ে shift দ্রুত আর যোগ ধীর ছিল, সেখানে ১-এর লম্বা সারিতে সময় বাঁচত (Booth-এর মূল প্রেরণা নিয়ে ভাবনার প্রশ্ন ১ দেখুন)। কিন্তু প্রতি ধাপে এক cycle-এর synchronous hardware-এ radix-2 Booth cycle কমায় না — n টি ধাপ লাগেই। গতি বাড়ায় এর পরের রূপ।

Radix-4 (modified) Booth — partial product অর্ধেক

১৯৬১ সালে O. L. MacSorley-র প্রকাশিত একটি প্রবন্ধে বহুল-পরিচিত এই উন্নতিটি — radix-4 Booth recoding — আছে: পাশাপাশি দুটি Booth অঙ্ক জুড়ে একটি ৪-ভিত্তিক (radix-4) অঙ্ক বানানো। d2j + 2d2j+1 = (q2j−1 − q2j) + 2(q2j − q2j+1) = q2j−1 + q2j − 2q2j+1. অর্থাৎ পরস্পর-ঢাকা (overlapping) তিন বিট (q2j+1, q2j, q2j−1) দেখে একটি অঙ্ক, যার মান −২ থেকে +২:

q2j+1 q2j q2j−1অঙ্ক djPartial productHardware-এ কীভাবে
00000শূন্য
001+1+MM যেমন আছে
010+1+MM যেমন আছে
011+2+2MM এক ঘর বাঁয়ে (তারের সংযোগ মাত্র)
100−2−2M2M-এর বিট উল্টে, +১ পরে যোগ
101−1−MM-এর বিট উল্টে, +১ পরে যোগ
110−1−MM-এর বিট উল্টে, +১ পরে যোগ
11100শূন্য

প্রতিটি অঙ্কের ওজন 4j, তাই n বিটের multiplier থেকে মাত্র n/2টি partial product. আর ±2M বা ±M বানানো খুব সস্তা — একটি shift (তার বদলে দেওয়া) আর বিট উল্টানো; “+১” আলাদা করে partial product-এর সারিতে ঢুকিয়ে দেওয়া হয়। ৩M-এর মতো কঠিন মান কখনো লাগে না — এটিই radix-4 Booth-এর চতুরতা।

উদাহরণ: M = ১৩, Q = 0111 (৭). Q-এর ডানে q−1 = 0 বসান: 0111|0. ত্রয়ী j = 0: (q1 q0 q−1) = 110 → −1. ত্রয়ী j = 1: (q3 q2 q1) = 011 → +2. তাই Q = 2 · 4 − 1 = ৭ ✓, আর গুণফল = −১৩ + ২ · ১৩ · ৪ = −১৩ + ১০৪ = ৯১ = ১৩ × ৭ ✓. চারটির বদলে দুটি partial product. অংশ ৫-এর কোডে ৮ বিটের উদাহরণসহ এই recoding চালিয়ে দেখা যাবে।

৫ · দ্রুত multiplier — array, Wallace tree আর Dadda tree

Sequential multiplier ৩২ বিটে ৩২ cycle নেয় — আজকের CPU-র জন্য অনেক ধীর। দ্রুত করার উপায়: সব partial product একসাথে তৈরি করে (n2টি AND gate, সব একই gate delay-তে) যোগটাকেও সমান্তরাল করা।

Array multiplier. সবচেয়ে সরল উপায়: n − 1 সারি adder, প্রতিটি সারি আগের যোগফলের সাথে পরের partial product যোগ করে — গুণের কাগজের ছবিটিই silicon-এ আঁকা। মোট প্রায় n2টি AND আর প্রায় n(n − 1)টি full adder. সারিগুলো যদি ripple adder হয়, প্রতিটি সারি আগেরটির carry-র জন্য অপেক্ষা করে। চালাক নকশায় মাঝের সারিগুলোর carry নিচের সারিতে পাঠানো হয় (carry-save ধাঁচে), আর শুধু শেষ সারিতে carry ছড়ায়; তখন critical path মোটামুটি (n − 2) টি carry-save সারি + একটি n বিটের ripple adder ≈ 2n − 2 full adder delay — সরল মডেলে delay n-এর সমানুপাতিক, O(n). নকশাটি খুব নিয়মিত (একই cell বারবার, তার শুধু পাশের cell পর্যন্ত), কিন্তু ৬৪ বিটে ১২৬ full adder delay অনেক।

মূল চাবি: 3:2 compressor. পর্ব ১১-এ (Adder) দেখেছেন, একটি carry-save adder তিনটি সংখ্যা নিয়ে দুটি সংখ্যা দেয় — প্রতিটি ঘরে একটি full adder তিনটি বিট যোগ করে sum বিট এক সংখ্যায় আর carry বিট এক ঘর বাঁয়ে আরেক সংখ্যায় রাখে। ঘরগুলো একে অপরের সাথে জোড়া নয়, তাই width যা-ই হোক, delay একটি full adder-এর। এই ভূমিকায় full adder-কে বলে 3:2 compressor: সমান ওজনের ৩টি বিট ঢোকে, ২টি বেরোয়, মোট মান একই থাকে।

Wallace tree (C. S. Wallace, ১৯৬৪). সব partial product-কে তিনটি তিনটি করে দলে ভাগ করুন, প্রতিটি দলকে একটি carry-save adder দিয়ে দুটিতে নামান — সব দল একসাথে, এক full adder delay-তে। দলের বাইরে বাড়তি এক-দুটি সারি থাকলে সেগুলো পরের স্তরে চলে যায়। প্রতি স্তরে সারি প্রায় ২/৩ হয়, তাই স্তর লাগে log3/2 n-এর কাছাকাছি — O(log n). দুটি সারি বাকি থাকলে একটি দ্রুত carry-propagate adder (পর্ব ১১-এর prefix adder-এর মতো) শেষ যোগটি করে।

৮টি সারির হিসাব: ৮ = ৩ + ৩ + ২ → (২ + ২) + ২ = ৬; ৬ = ৩ + ৩ → ৪; ৪ = ৩ + ১ → ২ + ১ = ৩; ৩ → ২. অর্থাৎ ৮ → ৬ → ৪ → ৩ → ২, চারটি স্তর। ১৬টি সারিতে ১৬ → ১১ → ৮ → ৬ → ৪ → ৩ → ২, ছয়টি স্তর। সূত্র: পরের স্তরের সারি = 2⌊r/3⌋ + (r mod 3).

Dadda tree (L. Dadda, ১৯৬৫). একই স্তর-সংখ্যা, কিন্তু উল্টো দিক থেকে ভাবা: শেষে ২টি সারি চাই; তার আগের স্তরে সর্বোচ্চ ৩, তার আগে ৪, তারপর ৬, ৯, ১৩, ১৯, ২৮, ৪২, ৬৩… (প্রতিটি = ⌊আগেরটি × ৩/২⌋)। প্রতি স্তরে শুধু ততটুকুই কমানো হয় যতটুকু পরের লক্ষ্যে পৌঁছাতে লাগে। ফলে full adder কিছুটা কম লাগে, যদিও শেষ adder সাধারণত একটু চওড়া হয়। ১৬ সারিতে Dadda-র লক্ষ্য-ক্রম ১৬ → ১৩ → ৯ → ৬ → ৪ → ৩ → ২ — Wallace-এর মতোই ৬ স্তর।

নিচের stepper-এ একটি পুরো ছোট গাছ: ১০১১ × ১১০১-এর চারটি partial product (অংশ ১-এর উদাহরণ)। Wallace-এর হিসাবে ৪ → ৩ → ২, দুটি স্তর। প্রথম স্তরের ভেতরটা কলাম ধরে দেখুন:

শুরু৪টি partial product, ৮ বিটে সাজানো
bit       7 6 5 4 3 2 1 0
P0        0 0 0 0 1 0 1 1    = 11   (bit 0 of 1101 = 1)
P1        0 0 0 0 0 0 0 0    =  0   (bit 1 = 0)
P2        0 0 1 0 1 1 0 0    = 44   (bit 2 = 1, shift 2)
P3        0 1 0 1 1 0 0 0    = 88   (bit 3 = 1, shift 3)
                       total = 143   rows = 4
স্তর ১, ভেতরেP0, P1, P2 → প্রতি কলামে একটি full adder
bit       7 6 5 4 3 2 1 0
P0        0 0 0 0 1 0 1 1
P1        0 0 0 0 0 0 0 0
P2        0 0 1 0 1 1 0 0
          -----------------
column 3: 1+0+1 = 10 -> sum 0, carry 1 to bit 4
column 2: 0+0+1 = 01 -> sum 1
column 5: 0+0+1 = 01 -> sum 1
S  = 0 0 1 0 0 1 1 1   (XOR of each column)
C  = 0 0 0 1 0 0 0 0   (majority, shifted left 1)
স্তর ১-এর পরে৪ সারি → ৩ সারি
bit       7 6 5 4 3 2 1 0
S         0 0 1 0 0 1 1 1    = 39
C         0 0 0 1 0 0 0 0    = 16
P3        0 1 0 1 1 0 0 0    = 88   (passed through)
                       total = 39 + 16 + 88 = 143   rows = 3
check: P0 + P1 + P2 = 11 + 0 + 44 = 55 = 39 + 16
স্তর ২৩ সারি → ২ সারি
bit       7 6 5 4 3 2 1 0
S         0 0 1 0 0 1 1 1
C         0 0 0 1 0 0 0 0
P3        0 1 0 1 1 0 0 0
          -----------------
column 4: 0+1+1 = 10 -> sum 0, carry 1 to bit 5
S2        0 1 1 0 1 1 1 1    = 111
C2        0 0 1 0 0 0 0 0    =  32
                       total = 143   rows = 2
শেষ যোগএকটি carry-propagate adder
S2        0 1 1 0 1 1 1 1    = 111
C2      + 0 0 1 0 0 0 0 0    =  32
          -----------------
product   1 0 0 0 1 1 1 1    = 143   (11 x 13)

delay = AND (partial products)
      + 2 full-adder levels
      + one fast adder
  1. Partial product. Multiplier 1101-এর প্রতিটি বিট দিয়ে 1011-কে AND, তারপর যথাস্থানে সরানো — চারটি সারি, সব একসাথে, এক AND gate delay-তে। সারিগুলোর যোগফল ১১ + ০ + ৪৪ + ৮৮ = ১৪৩. লক্ষ্য: সারি কমিয়ে দুটিতে আনা, carry না ছড়িয়ে।
  2. প্রথম স্তরের ভেতরে। প্রথম তিনটি সারি একটি carry-save adder-এ যায়: প্রতিটি কলামে আলাদা একটি full adder. কলাম ৩-এ তিনটি বিট 1, 0, 1 — যোগফল ২ = 10, তাই sum 0 আর carry 1, যা C সারির bit 4-এ বসে (এক ঘর বাঁয়ে)। অন্য কলামগুলোতে বড়জোর একটি ১, তাই সেখানে carry 0. সব কলাম একই সময়ে, কেউ কারো জন্য অপেক্ষা করে না।
  3. স্তর ১-এর ফল। তিনটি সারি (১১, ০, ৪৪) এখন দুটি — S = ৩৯ আর C = ১৬, যোগফল সেই ৫৫. চতুর্থ সারি P3 দলে জায়গা পায়নি, তাই অপরিবর্তিত পরের স্তরে গেল। এখন ৩টি সারি, মোট মান এখনো ১৪৩ — কোনো তথ্য হারায়নি, শুধু রূপ বদলেছে।
  4. স্তর ২. S, C আর P3 আরেকটি carry-save adder-এ। একমাত্র কলাম ৪-এ দুটি ১ (C আর P3 থেকে), তাই সেখানে sum 0 আর carry 1 → C2-এর bit 5. ফল S2 = ১১১ আর C2 = ৩২. দুটি সারি বাকি — আর কমানো যায় না, কারণ 3:2 compressor-এর তিনটি ইনপুট লাগে।
  5. শেষ যোগ। এখনই প্রথমবার carry ছড়ানো হয়: একটি দ্রুত adder ১১১ + ৩২ = ১৪৩ = 1000 1111 দেয়। মোট সময় = এক AND + দুই full adder স্তর + একটি দ্রুত adder. বড় গুণে স্তরের সংখ্যা log হারে বাড়ে, তাই ৬৪টি সারিতেও মাত্র ১০টি স্তর।
PP0PP1PP2PP3 PP4PP5PP6PP7 CSACSA CSACSA CSACSA fast carry-propagate adder 8 → 66 → 44 → 33 → 2
ছবি ২১.২ — ৮টি partial product-এর Wallace tree. প্রতিটি নীল বাক্স একটি carry-save adder (একটি সারির সব কলামে full adder); ডটেড রেখা হলো যে সারিগুলো সেই স্তরে দলে জায়গা না পেয়ে পরের স্তরে যায়। চার স্তরে ৮ → ৬ → ৪ → ৩ → ২, তারপর একটিমাত্র দ্রুত adder.

এবার কোডে: প্রথমে radix-4 Booth recoder (অংশ ৪), তারপর ৮, ১৬, ৩২ আর ৬৪টি সারির জন্য Wallace আর Dadda-র স্তর গোনা, আর array নকশার carry-save সারির সংখ্যার সাথে তুলনা:

// (1) Radix-4 (modified) Booth recoding: overlapping triplets
//     (q[2i+1], q[2i], q[2i-1]) -> digit d_i in {-2..+2}, Q = sum d_i * 4^i
// (2) Carry-save reduction levels (Wallace / Dadda) vs a simple array.
const DIGIT = { "000": 0, "001": 1, "010": 1, "011": 2, "100": -2, "101": -1, "110": -1, "111": 0 };
function recode(m, q, n) {
  const bits = ((q & ((1 << n) - 1)) * 2).toString(2).padStart(n + 1, "0"); // append q[-1] = 0
  let sum = 0, line = [];
  for (let i = 0; i < n / 2; i++) {
    const t = bits.slice(n - 2 * i - 2, n - 2 * i + 1), d = DIGIT[t];
    const pp = d * m * 4 ** i;                                   // partial product
    line.push(`${t}->${d >= 0 ? "+" : ""}${d}: ${d}*${m}*4^${i} = ${pp}`);
    sum += pp;
  }
  console.log(`Q = ${bits.slice(0, n)} (${q}), M = ${m}, ${n / 2} partial products:\n  ` +
    line.join("\n  ") + `\n  sum = ${sum}, M*Q = ${m * q} ${sum === m * q ? "OK" : "WRONG"}`);
}
recode(13, 0b0111, 4);
recode(13, 0b01101110, 8);
recode(-9, -29, 8);
function wallace(rows) {                  // every full group of 3 rows -> 2 rows
  const seq = [rows];
  while (rows > 2) { rows = 2 * Math.floor(rows / 3) + (rows % 3); seq.push(rows); }
  return seq;
}
function dadda(rows) {                    // targets 2, 3, 4, 6, 9, 13, 19, 28, 42, 63 ...
  const d = [2]; while (d[d.length - 1] < rows) d.push(Math.floor(d[d.length - 1] * 1.5));
  return [rows, ...d.filter(x => x < rows).reverse()];
}
console.log("\nrows  Wallace sequence                       levels  Dadda targets              array depth");
for (const n of [8, 16, 32, 64]) {
  const w = wallace(n), dd = dadda(n);
  console.log(`${String(n).padStart(4)}  ${w.join(">").padEnd(38)} ${String(w.length - 1).padStart(4)}` +
    `    ${dd.join(">").padEnd(26)} ${String(n - 2).padStart(3)} CSA rows`);
}

আউটপুট:

Q = 0111 (7), M = 13, 2 partial products:
  110->-1: -1*13*4^0 = -13
  011->+2: 2*13*4^1 = 104
  sum = 91, M*Q = 91 OK
Q = 01101110 (110), M = 13, 4 partial products:
  100->-2: -2*13*4^0 = -26
  111->+0: 0*13*4^1 = 0
  101->-1: -1*13*4^2 = -208
  011->+2: 2*13*4^3 = 1664
  sum = 1430, M*Q = 1430 OK
Q = 11100011 (-29), M = -9, 4 partial products:
  110->-1: -1*-9*4^0 = 9
  001->+1: 1*-9*4^1 = -36
  100->-2: -2*-9*4^2 = 288
  111->+0: 0*-9*4^3 = 0
  sum = 261, M*Q = 261 OK

rows  Wallace sequence                       levels  Dadda targets              array depth
   8  8>6>4>3>2                                 4    8>6>4>3>2                    6 CSA rows
  16  16>11>8>6>4>3>2                           6    16>13>9>6>4>3>2             14 CSA rows
  32  32>22>15>10>7>5>4>3>2                     8    32>28>19>13>9>6>4>3>2       30 CSA rows
  64  64>43>29>20>14>10>7>5>4>3>2              10    64>63>42>28>19>13>9>6>4>3>2  62 CSA rows

প্রথম ব্লক অংশ ৪-এর হাতের হিসাবই: 0111 → −1 আর +2, partial product −১৩ আর ১০৪, যোগফল ৯১. দ্বিতীয়টি ৮ বিটে: 01101110 (১১০)-এর চারটি অঙ্ক −2, 0, −1, +2 — অর্থাৎ ২ · ৬৪ − ১ · ১৬ + ০ · ৪ − ২ = ১১০; ৮টির বদলে ৪টি partial product, যার একটি আবার শূন্য। তৃতীয়টিতে দুটি সংখ্যাই ঋণাত্মক (−৯ × −২৯ = ২৬১), তবু কোনো চিহ্ন-সংশোধন ছাড়াই ঠিক — Booth-এর মতোই radix-4-ও two's complement সরাসরি সামলায়। টেবিলে Wallace-এর ক্রম ৮ → ৬ → ৪ → ৩ → ২ (৪ স্তর), ১৬-তে ৬, ৩২-এ ৮, ৬৪-তে ১০ স্তর — সারি দ্বিগুণ হলে স্তর বাড়ে মাত্র দুটি করে। Dadda-র লক্ষ্য-ক্রম আলাদা (৬৪ → ৬৩ → ৪২ → …) কিন্তু স্তরের সংখ্যা একই। আর array নকশায় সারি-ধরে carry-save করলে ৬৪টি সারিতে ৬২টি স্তর (তারপর শেষ adder) — ১০-এর তুলনায় ছয় গুণের বেশি। Radix-4 Booth আর Wallace মিলিয়ে ৬৪ বিটের গুণে সারি প্রায় ৩২টি (চিহ্ন সামলানোর জন্য বাস্তবে একটি বেশি থাকতে পারে), স্তর ৮টি।

আধুনিক CPU কয়েক cycle-এ গুণ করে কীভাবে? Booth-recoded partial product, Wallace/Dadda ধাঁচের গাছ (প্রায়ই 4:2 compressor দিয়ে, যা দুটি 3:2 একসাথে জোড়া) আর শেষে একটি দ্রুত adder — সব মিলিয়ে একটি ৬৪ বিটের multiplier এক cycle-এ শেষ হয় না, তাই এর মাঝে pipeline register বসিয়ে কয়েকটি ধাপে ভাগ করা হয়। বিভিন্ন স্বাধীন পরিমাপ আর vendor-এর optimization manual অনুযায়ী অনেক আধুনিক x86-64 core-এ ৬৪ বিটের integer গুণের latency প্রায় ৩–৪ cycle, আর যেহেতু multiplier pipelined, প্রতি cycle-এ একটি নতুন গুণ শুরু করা যায় — অর্থাৎ throughput প্রতি cycle-এ প্রায় একটি গুণ। ARM আর RISC-V-এর বড় core-এও সংখ্যাগুলো মোটামুটি একই পরিসরে, তবে core ভেদে আলাদা — নির্দিষ্ট chip-এর জন্য তার manual দেখুন। তুলনায়, ছোট microcontroller-এ গুণ কখনো এক cycle, কখনো ৩২ cycle-এর sequential multiplier, কখনো hardware-ই নেই।

আরও গভীরে: আসল multiplier-এর বড় সমস্যা স্তর নয়, তার। Wallace tree-র অনিয়মিত সংযোগ silicon-এ বিছানো কঠিন, তাই শিল্পে প্রায়ই 4:2 compressor-এর নিয়মিত গাছ (যেমন overturned-stairs বা ZM tree ধরনের নকশা, গবেষণাপত্রে বর্ণিত) আর বিশেষ hand-crafted layout ব্যবহার হয়। FPGA-তে উল্টো ছবি: সেখানে আলাদা কঠিন (hard) DSP block থাকে — যেমন Xilinx/AMD-এর DSP48 পরিবারে একটি ২৫ × ১৮ বা ২৭ × ১৮ বিটের signed multiplier আর accumulator, যা vendor-এর ডকুমেন্টে বর্ণিত — বড় গুণ কয়েকটি block জুড়ে বানানো হয়। আর floating-point গুণের ভেতরেও (পর্ব ২৪) মূল কাজ এই integer multiplier-ই করে: double-এর ৫৩ বিটের significand গুণ।

৬ · গুণের instruction — নিচের অর্ধেক, ওপরের অর্ধেক

৩২ বিট × ৩২ বিট = ৬৪ বিট, অথচ একটি register ৩২ বিটের। ISA এই সমস্যা বিভিন্নভাবে সমাধান করে। এই কোর্সের মূল ISA RISC-V-এ base RV32I-তে কোনো গুণের instruction নেই; গুণ-ভাগ আসে ঐচ্ছিক M extension থেকে (instruction-গুলোর বিস্তারিত পর্ব ২৬-এ)। গুণের চারটি instruction:

Instructionrs1 × rs2 কী হিসেবেফল (rd-তে)কোথায় লাগে
mul— (signed আর unsigned-এ একই)৬৪ বিটের গুণফলের নিচের ৩২ বিটসাধারণ a*b
mulhsigned × signedওপরের ৩২ বিটsigned overflow, signed বড় সংখ্যা
mulhuunsigned × unsignedওপরের ৩২ বিটmulti-precision, ধ্রুবক দিয়ে ভাগ
mulhsusigned (rs1) × unsigned (rs2)ওপরের ৩২ বিটsigned বড় সংখ্যার মাঝের অংশ

নিচের অর্ধেকের জন্য একটিই instruction কেন? কারণ two's complement-এ গুণফলের নিচের n বিট signed বা unsigned — যেকোনো ব্যাখ্যায় একই। দুটি ব্যাখ্যার পার্থক্য প্রতিটি সংখ্যায় 2n-এর গুণিতক (যেমন 1101 unsigned ১৩, signed −৩; পার্থক্য ১৬)। (a + k·2n)(b + l·2n) খুললে বাড়তি সব পদ 2n-এর গুণিতক, যা নিচের n বিটে কোনো প্রভাব ফেলে না। কিন্তু ওপরের অর্ধেক বদলে যায় — তাই সেখানে তিনটি আলাদা instruction. RV64-এ একই নিয়ম ৬৪ বিটে (১২৮ বিটের ফল), আর ৩২ বিটের ফলের জন্য বাড়তি mulw.

অন্য ISA-তে। x86-64-এ এক-operand-এর mul r/m64 (unsigned) আর imul r/m64 (signed) RAX-কে operand দিয়ে গুণ করে পুরো ১২৮ বিট ফল দেয় RDX:RAX জোড়ায় — RDX-এ ওপরের অর্ধেক; ৩২ বিটে EDX:EAX. ওপরের অর্ধেক অর্থবহ হলে (ফল নিচের অর্ধেকে ধরেনি) CF আর OF flag ১ হয়। দুই বা তিন operand-এর imul শুধু নিচের অর্ধেক দেয় — compiler সাধারণ a*b-তে এটিই ব্যবহার করে। ARM-এর AArch64-এ mul নিচের ৬৪ বিট, smulh/umulh ১২৮ বিটের ওপরের ৬৪ বিট, আর smull/umull দুটি ৩২ বিট থেকে পুরো ৬৪ বিট; ৩২ বিটের ARM-এ SMULL/UMULL দুটি register-এ (RdLo, RdHi) পুরো ফল লেখে।

RISC-V spec একটি কৌশলও বলে: একই গুণের দুই অর্ধেক লাগলে পরপর mulh[[s]u] rdh, rs1, rs2 আর mul rdl, rs1, rs2 লিখুন (একই ক্রমে source register, আর rdh যেন rs1 বা rs2 না হয়) — তাহলে microarchitecture চাইলে দুটিকে একটি গুণে জুড়ে (fuse) দিতে পারে। ISA সরল থাকে, দ্রুত hardware-ও বাদ পড়ে না।

ওপরের অর্ধেক কেন লাগে? তিনটি বড় কারণ:

  • Overflow ধরা: unsigned গুণে overflow ঠিক তখনই, যখন mulhu ≠ 0. Signed গুণে overflow ঠিক তখনই, যখন mulh-এর ফল নিচের অর্ধেকের sign bit-এর sign extension (সব ০ বা সব ১) নয়।
  • বড় সংখ্যার হিসাব (multi-precision): RSA বা elliptic-curve cryptography-তে ২০৪৮ বিটের মতো সংখ্যা ৬৪ বিটের টুকরোয় ভেঙে গুণ হয়; প্রতিটি টুকরো-গুণের দুই অর্ধেকই লাগে।
  • Fixed-point আর ধ্রুবক দিয়ে ভাগ: Q31 গুণ আসলে ওপরের অর্ধেক থেকে (অংশ ৮)। আর compiler x / 10-কে (unsigned ৩২ বিটে) (x * 0xCCCCCCCD) >> 35-এ বদলায় — অর্থাৎ mulhu তারপর shift; ভাগের চেয়ে অনেক দ্রুত (পর্ব ২২-এ ভাগ কেন ধীর)।

নিচের C প্রোগ্রাম চারটি RISC-V instruction-এর ফল ৬৪ বিটের মাঝের হিসাব দিয়ে বানায় — signed গুণে overflow যাতে না হয় (৩২ বিটের সংখ্যা ৬৪ বিটে গুণ করলে কখনো overflow হয় না), আর ঋণাত্মক সংখ্যা shift না করে আগে unsigned-এ রূপান্তর। তারপর ওপরের অর্ধেক দিয়ে overflow ধরে gcc-র নিজস্ব __builtin_mul_overflow-এর সাথে মেলায়, আর শেষে strength reduction-এর একটি যাচাই (অংশ ৭):

#include <stdio.h>
#include <stdint.h>

/* RV32M-style results from one 64-bit product (no signed overflow, no UB) */
static uint32_t mul(int32_t a, int32_t b)    { return (uint32_t)((uint64_t)(uint32_t)a * (uint32_t)b); }
static uint32_t mulhu(int32_t a, int32_t b)  { return (uint32_t)(((uint64_t)(uint32_t)a * (uint32_t)b) >> 32); }
static uint32_t mulh(int32_t a, int32_t b)   { return (uint32_t)((uint64_t)((int64_t)a * b) >> 32); }
static uint32_t mulhsu(int32_t a, int32_t b) { return (uint32_t)((uint64_t)((int64_t)a * (int64_t)(uint32_t)b) >> 32); }

int main(void) {
    int32_t t[][2] = { {100000, 50000}, {-7, 3}, {2147483647, 2}, {-2, -3}, {65536, 65536} };
    printf("         a      b | mul      mulh     mulhu    mulhsu   | s-ovf u-ovf builtin\n");
    for (int i = 0; i < 5; i++) {
        int32_t a = t[i][0], b = t[i][1], r;
        uint32_t lo = mul(a, b), hi = mulh(a, b), hu = mulhu(a, b);
        uint32_t sign = (lo >> 31) ? 0xFFFFFFFFu : 0u;   /* sign-extension of low half */
        int ovf = __builtin_mul_overflow(a, b, &r);       /* gcc's own signed check */
        printf("%10d %6d | %08X %08X %08X %08X | %-5s %-5s %d\n", (int)a, (int)b,
               (unsigned)lo, (unsigned)hi, (unsigned)hu, (unsigned)mulhsu(a, b),
               hi != sign ? "yes" : "no", hu != 0 ? "yes" : "no", ovf);
    }
    unsigned bad = 0;                                    /* x*10 == (x<<3)+(x<<1) ? */
    for (uint32_t x = 0, k = 0; k < 2000000u; k++, x += 2147u)
        if (x * 10u != (x << 3) + (x << 1)) bad++;
    printf("x*10 vs (x<<3)+(x<<1): 2000000 values, mismatches %u\n", bad);
    return 0;
}

Wandbox-এ (gcc, x86-64 Linux) আউটপুট:

         a      b | mul      mulh     mulhu    mulhsu   | s-ovf u-ovf builtin
    100000  50000 | 2A05F200 00000001 00000001 00000001 | yes   yes   1
        -7      3 | FFFFFFEB FFFFFFFF 00000002 FFFFFFFF | no    yes   0
2147483647      2 | FFFFFFFE 00000000 00000000 00000000 | yes   no    1
        -2     -3 | 00000006 00000000 FFFFFFFB FFFFFFFE | no    yes   0
     65536  65536 | 00000000 00000001 00000001 00000001 | yes   yes   1
x*10 vs (x<<3)+(x<<1): 2000000 values, mismatches 0

প্রতিটি সারি পড়ুন। ১০০০০০ × ৫০০০০ = ৫০০ কোটি, যা 232-এর (প্রায় ৪২৯ কোটি) চেয়ে বড়: ওপরের অর্ধেক 00000001, তাই signed আর unsigned দুই হিসাবেই overflow. −৭ × ৩ = −২১: নিচের অর্ধেক FFFFFFEB (−২১), mulh = FFFFFFFF — ঠিক নিচের অর্ধেকের sign-এর বিস্তার, তাই signed overflow নেই। কিন্তু একই বিটকে unsigned ধরলে −৭ হয় ৪২৯৪৯৬৭২৮৯, আর তিন গুণ ৩২ বিটে ধরে না — mulhu = 2. একই ৬৪ বিটের নিচের অর্ধেক, তিনটি ভিন্ন ওপরের অর্ধেক। INT_MAX × ২ সবচেয়ে শিক্ষণীয়: mulh = 0, দেখে মনে হয় “ওপরে কিছু নেই”, অথচ নিচের অর্ধেক FFFFFFFE-এর sign bit ১ — signed হিসেবে ফলটি −২ পড়া যেত, তাই overflow yes. অর্থাৎ signed overflow পরীক্ষায় শুধু “mulh ≠ 0” দেখা ভুল; নিচের অর্ধেকের sign-এর সাথে মেলাতে হয়। −২ × −৩ = ৬, কোনো signed overflow নেই, কিন্তু unsigned-এ বিশাল সংখ্যা। শেষ কলামে gcc-র নিজস্ব পরীক্ষা (builtin) প্রতিটি সারিতে আমাদের s-ovf-এর সাথে মিলেছে। আর শেষ লাইন: ২০ লাখ মানে x*10 আর (x<<3)+(x<<1)-এর একটিও অমিল নেই — wrap-around হওয়া বড় মানেও।

৭ · ধ্রুবক দিয়ে গুণ — strength reduction আর shift-add

যখন গুণের একটি সংখ্যা আগে থেকেই জানা (ধ্রুবক), তখন সাধারণ multiplier লাগে না। ধ্রুবকের binary রূপে যেখানে যেখানে ১, শুধু সেই shift-গুলো যোগ করলেই চলে: ১০ = 1010 = ৮ + ২, তাই x × 10 = (x << 3) + (x << 1). দামি কাজকে সস্তা কাজ দিয়ে বদলানোর এই compiler-কৌশলের নাম strength reduction. Booth-এর ধারণাও এখানে কাজে লাগে: x × 15 = (x << 4) − x, চারটি পদের তিনটি যোগের বদলে একটি বিয়োগ।

x86-64-এ compiler এর জন্য প্রায়ই LEA (load effective address) instruction ব্যবহার করে — এটি আসলে ঠিকানা হিসাবের জন্য, কিন্তু base + index × (১, ২, ৪ বা ৮) একটি instruction-এ করতে পারে বলে ছোট গুণের দারুণ হাতিয়ার। উদাহরণ (gcc -O2, x86-64, int f(int x){ return x*10; }-এর মতো ফাংশনে সাধারণত যা দেখা যায়):

# x86-64 (Intel syntax), gcc -O2:  int f(int x) { return x * 10; }
f:
    lea   eax, [rdi + rdi*4]   # eax = x + 4x = 5x
    add   eax, eax             # eax = 10x
    ret

# RISC-V, gcc -O2: depending on compiler version and -mtune,
# either a plain mul or a shift-add sequence like this one:
f:
    slli  a5, a0, 2            # a5 = 4x
    add   a0, a5, a0           # a0 = 5x
    slli  a0, a0, 1            # a0 = 10x
    ret

Compiler কখন shift-add বাছবে আর কখন সরাসরি গুণ, তা নির্ভর করে target CPU-র গুণের latency কত আর shift-add sequence কত লম্বা হবে তার ওপর। যে CPU-তে গুণ ৩ cycle, সেখানে চার-পাঁচটি instruction-এর sequence লাভজনক নাও হতে পারে; যে microcontroller-এ গুণের hardware নেই (যেমন M extension ছাড়া RV32I), সেখানে shift-add প্রায় সবসময় লাভ। তাই নিজের হাতে x*10-কে shift-এ লেখার দরকার নেই — x*10 লিখলে compiler সেরাটা বাছবে, আর কোড পড়তেও সহজ থাকে। অংশ ৬-এর C প্রোগ্রামের শেষ লাইন দেখিয়েছে, unsigned গণিতে দুটি রূপ সব ২০ লাখ মানেই একই — overflow হলেও, কারণ দুই পাশই mod 232-এ হিসাব।

Hardware-এ ধ্রুবক গুণ। DSP filter বা video codec-এর chip-এ অনেক গুণ নির্দিষ্ট ধ্রুবক দিয়ে (filter-এর coefficient বা সহগ)। সেখানে পূর্ণ multiplier না বসিয়ে শুধু ধ্রুবকের ১-এর ঘরগুলোর shift (তারের সংযোগ, gate লাগে না) আর কয়েকটি adder বসানো হয়। ধ্রুবকটিকে Booth-এর মতো {−1, 0, +1} অঙ্কে লিখলে (যাকে canonical signed digit বলে, যেখানে পাশাপাশি দুটি অশূন্য অঙ্ক থাকে না) adder আরও কমে। অনেকগুলো ধ্রুবক একই ইনপুট দিয়ে গুণ হলে মাঝের ফল ভাগাভাগি করা যায় — এটি একটি সক্রিয় গবেষণার বিষয়, যার নাম multiple constant multiplication বা একাধিক ধ্রুবকের গুণ।

C-তে গুণের overflow: int a = 50000, b = 50000; int c = a * b; — ২৫০ কোটি ৩২ বিটের int-এ ধরে না। Signed overflow C-তে undefined behaviour: compiler ধরে নিতে পারে এটা ঘটবেই না, আর সেই অনুমানে কোড বদলাতে পারে (পর্ব ০৫-এর অংশ ৮). নিরাপদ পথ: আগে int64_t-তে রূপান্তর করে গুণ, gcc/clang-এর __builtin_mul_overflow, অথবা C23-এর <stdckdint.h>-এর ckd_mul. বাফারের আকার হিসাব (count * size) overflow হয়ে ছোট মেমরি বরাদ্দ হওয়া একটি পরিচিত নিরাপত্তা-ত্রুটির ধরন — calloc এই কারণেই নিজে গুণের overflow পরীক্ষা করে।

৮ · Fixed-point গুণ — Q15, Q31 আর rounding

অনেক DSP chip, অডিও প্রসেসিং আর ছোট microcontroller-এ floating-point hardware থাকে না বা ধীর। সেখানে ভগ্নাংশ রাখা হয় fixed-point রূপে: একটি সাধারণ integer, কিন্তু আমরা মনে মনে ধরে নিই binary point একটি নির্দিষ্ট জায়গায়। সবচেয়ে প্রচলিত Q format:

  • Q15 (Q1.15 নামেও লেখা হয়): ১৬ বিটের signed integer N মানে N / 215. পরিসর −১ থেকে 1 − 2−15 ≈ ০.৯৯৯৯৭; ধাপ (১ LSB) 2−15 ≈ ০.০০০০৩০৫. যেমন 0x4000 = ১৬৩৮৪ = ০.৫.
  • Q31: ৩২ বিটে একই ধারণা, N / 231.

গুণের নিয়ম। দুটি Q15 গুণ করলে (a/215)(b/215) = ab/230: ৩২ বিটের গুণফল Q30 রূপে। Q15-এ ফিরতে ১৫ ঘর ডানে shift. শুধু shift করলে ভগ্নাংশ কেটে যায় (truncation, যা arithmetic shift-এ সবসময় −∞-এর দিকে যায় — গড়ে আধা LSB ঋণাত্মক ঝোঁক); কাছের মানে rounding-এর জন্য shift-এর আগে 214 যোগ করুন।

Worked example (Q15): 0.3 x 0.7
0.3 -> round(0.3 x 32768) =  9830 = 0x2666
0.7 -> round(0.7 x 32768) = 22938 = 0x599A
product (32 bit, Q30)     = 9830 x 22938 = 225480540 = 0x0D708F5C
truncate: 225480540 >> 15           = 6881
round   : (225480540 + 16384) >> 15 = 6881
result  : 6881 / 32768 = 0.2099915...   (exact 0.21, error ~ 0.0000085 < 1 LSB)

special case: -1 x -1 = 0x8000 x 0x8000 -> (1073741824 >> 15) = 32768
              does not fit in Q15 (max 32767) -> saturate to 0x7FFF

এখানে truncation আর rounding একই ফল দিয়েছে, কারণ বাদ পড়া ভগ্নাংশ ছিল আধা LSB-র কম; হাজার হাজার গুণ পরপর হলে (যেমন filter-এ) truncation-এর ঝোঁক জমে চোখে পড়ার মতো ভুল হয়, তাই DSP-তে rounding সাধারণ। আর −১ × −১-এর বিশেষ ক্ষেত্র: +১ Q15-এ ধরে না, তাই DSP instruction-গুলো ফলকে সবচেয়ে বড় মানে আটকে দেয় — saturating arithmetic. Q31-এ: ৬৪ বিটের গুণফল Q62, ৩১ ঘর ডানে shift — অর্থাৎ ওপরের ৩২ বিট (mulh) এক ঘর বাঁয়ে সরানো। এজন্যই ARM-এর DSP/NEON instruction-সেটে “doubling multiply high”-ধরনের instruction (যেমন SQDMULH) আছে: গুণ, দ্বিগুণ, ওপরের অর্ধেক, saturation — এক instruction-এ। অনেক DSP প্রসেসরের মূল একক হলো MAC (multiply-accumulate): প্রতি cycle-এ একটি গুণ আর চওড়া accumulator-এ যোগ।

৯ · AI-এর যুগে গুণের hardware

AI টুল Booth-এর নিয়ম ঠিক বলে, কিন্তু trace করতে গিয়ে প্রায়ই ভুল করে — বিশেষ করে arithmetic shift, বিয়োগের two's complement আর শেষ ফল পড়ায়। কয়েকটি সাধারণ ভুল:

AI-এর প্রচলিত ভুলঠিক কথাকীভাবে যাচাই
Booth-এ (Q0, Q−1) = 01-এ বিয়োগ, 10-এ যোগ (উল্টো)10 → A − M (সারি শুরু), 01 → A + M৭ × (−৩)-এর মতো ছোট উদাহরণ নিজে trace করে −২১ আসে কি না
Booth-এ logical right shift (বাঁয়ে ০ ঢোকানো)A:Q:Q−1-এর arithmetic shift — A-র sign bit কপিA ঋণাত্মক হওয়া প্রতিটি ধাপে বাঁয়ের বিট দেখা
M = −2n−1-এর ফাঁদ উপেক্ষা−M n বিটে ধরে না; A-কে n + 1 বিট রাখতে হয়৪ বিটে −৮ × ৭ চালিয়ে দেখা
Wallace-এর স্তর গুনতে log2 n বা n/3r → 2⌊r/3⌋ + (r mod 3); ৮ সারিতে ৪, ৬৪-তে ১০ স্তরসারির ক্রম ধাপে ধাপে লিখে (অংশ ৫-এর কোড)
“mulh মানে unsigned গুণের ওপরের অর্ধেক” বা mulhsu-র operand উল্টোmulh signed × signed, mulhu unsigned × unsigned, mulhsu rs1 signed × rs2 unsignedRISC-V unprivileged spec-এর M chapter পড়ে, ঋণাত্মক ইনপুটে C দিয়ে পরীক্ষা
x86-এ দুই-operand imul-এর ফল RDX:RAX-এ বলাশুধু এক-operand mul/imul RDX:RAX-এ লেখেIntel/AMD manual, বা compiler-এর assembly আউটপুট দেখে

AI-কে এমনভাবে প্রশ্ন করুন যাতে প্রতিটি ধাপ টেবিলে থাকে আর আপনি নিজে মিলিয়ে নিতে পারেন:

আমি Booth-এর multiplication algorithm শিখছি। ৪ বিটের two's complement-এ M = 1011 (−5) আর Q = 0110 (+6) নাও। ১. −M কত, আগে লেখো। ২. প্রতিটি ধাপে (Q0, Q−1), কী operation (A−M / A+M / কিছু না), operation-এর পরের A, তারপর arithmetic right shift-এর পরের A, Q, Q−1 — একটি টেবিলে দাও। ৩. শেষের A:Q-কে ৮ বিটের signed সংখ্যা হিসেবে পড়ে দশমিকে লেখো, আর −5 × 6-এর সাথে মেলাও। ৪. একই Q-কে radix-4 Booth-এ recode করো: প্রতিটি ত্রয়ী আর তার অঙ্ক দেখাও। শেষে ৩০ লাইনের মধ্যে একটি JavaScript tracer দাও যা যেকোনো M, Q-তে এই টেবিল ছাপে আর ফল সাধারণ গুণের সাথে মেলায় — আমি নিজে চালাব।

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

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

  • n বিট × n বিট গুণে কত বিট লাগে? — 2n; সবচেয়ে বড় unsigned গুণফল 22n − 2n+1 + 1 < 22n.
  • Booth-এর নিয়ম কী? — (Q0, Q−1) = 10 → A − M, 01 → A + M, 00/11 → কিছু নয়; প্রতিবার A:Q:Q−1-এর arithmetic right shift, n বার।
  • Booth-এর সবচেয়ে খারাপ ক্ষেত্র? — পালাক্রমের বিট (0101…), যেখানে প্রতিটি ধাপে যোগ বা বিয়োগ — n টি কাজ।
  • Radix-4 Booth কী লাভ দেয়? — তিন বিট দেখে অঙ্ক {−2..+2}; partial product n থেকে প্রায় n/2.
  • Wallace tree-র delay কেন O(log n)? — প্রতি স্তরে carry-save adder সারি প্রায় ২/৩ করে, carry না ছড়িয়ে; শেষে একটি দ্রুত adder.

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

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

প্র ০১Booth তাঁর algorithm বানিয়েছিলেন signed সংখ্যার জন্য, নাকি গতির জন্য? আর ৭০ বছর পরেও এর রূপভেদ প্রায় প্রতিটি multiplier-এ কেন?

Andrew Donald Booth আর তাঁর স্ত্রী Kathleen Booth ১৯৪০-এর শেষ আর ১৯৫০-এর দশকের শুরুতে লন্ডনের Birkbeck College-এ প্রথম দিকের কয়েকটি কম্পিউটার (যেমন ARC আর APE(X)C নামে পরিচিত যন্ত্র) নিয়ে কাজ করেন। ১৯৫১ সালে প্রকাশিত “A signed binary multiplication technique” প্রবন্ধে Booth একটি পদ্ধতি দেন যা two's complement সংখ্যাকে আলাদা চিহ্ন-সংশোধন ছাড়াই গুণ করে। শিরোনামেই বোঝা যায়, মূল লক্ষ্য ছিল signed গুণ সরল করা।

গতির গল্পটিও প্রায়ই বলা হয়: সে যুগের যান্ত্রিক ডেস্ক ক্যালকুলেটরে চালকেরা জানতেন, ৯৯৯৯ দিয়ে গুণ করতে চারটি ঘরে ৯ বার করে হাতল ঘোরানোর চেয়ে ১০০০০ দিয়ে গুণ করে একবার বিয়োগ করা দ্রুত। আর কিছু প্রথম যুগের কম্পিউটারে শুধু shift করা যোগের চেয়ে দ্রুত ছিল বলে ১-এর লম্বা সারিতে Booth সময় বাঁচাত। কিন্তু বর্ণনাগুলো ইতিহাসের বই ভেদে আলাদা, তাই “Booth ক্যালকুলেটর দেখে অনুপ্রাণিত হয়েছিলেন” কথাটিকে প্রচলিত ব্যাখ্যা হিসেবেই নেওয়া ভালো।

আজকের hardware-এ radix-2 Booth প্রায় দেখা যায় না, কারণ synchronous নকশায় এটি ধাপ কমায় না আর পালাক্রমের বিটে কাজ বাড়ায়। টিকে আছে এর ১৯৬১ সালের রূপ — radix-4 (modified) Booth. এটি partial product-এর সংখ্যা অর্ধেক করে, আর প্রতিটি partial product হয় ০, ±M বা ±2M — সবই shift আর বিট উল্টানো দিয়ে সস্তায় বানানো যায়। Partial product অর্ধেক মানে Wallace tree-তে এক-দুই স্তর কম, অনেক কম full adder আর তার। ৫৩ বিটের double significand গুণে সারি ৫৩ থেকে ২৭-এ নামে, আর গাছের স্তর ৯ থেকে ৭-এ।

Radix-8-ও সম্ভব (চার বিট দেখে −4..+4), কিন্তু তখন ±3M লাগে, যা একটি আলাদা adder দিয়ে আগে বানিয়ে রাখতে হয়; তাই এটি বিশেষ ক্ষেত্রে (যেমন কিছু কম-power নকশায়) ব্যবহৃত হয়, সাধারণত নয়। রূপভেদ যা-ই হোক, সবগুলোর পেছনে একই গণিত: Q = Σ (qi−1 − qi) 2i — একটি সংখ্যাকে অন্য অঙ্ক-সেটে লিখে কাজ কমানো।

মূল উপলব্ধি: একটি ভালো ধারণা প্রযুক্তি বদলালেও টিকে থাকে, শুধু রূপ বদলায়। Booth-এর আসল অবদান একটি নির্দিষ্ট circuit নয়, বরং এই অন্তর্দৃষ্টি যে সংখ্যার উপস্থাপন বদলালে হিসাবের খরচ বদলায়।

প্র ০২পুরোনো microprocessor-এ গুণ এত ধীর বা অনুপস্থিত ছিল কেন, আর RISC-V আজও base ISA থেকে গুণ বাদ রাখে কেন?

১৯৭০-এর দশকের জনপ্রিয় ৮ বিটের microprocessor — যেমন MOS 6502 আর Zilog Z80 — এ কোনো গুণের instruction ছিল না। প্রোগ্রামাররা এই পর্বের shift-and-add লুপটি নিজেরাই assembly-তে লিখতেন, প্রতিটি গুণে কয়েকশো cycle. কারণ সরল: একটি chip-এ তখন মাত্র কয়েক হাজার transistor, আর একটি সমান্তরাল multiplier-এর জন্য n2 ক্রমের gate লাগে — তখনকার পুরো CPU-র transistor বাজেটের বড় একটি অংশ। Intel 8086 (১৯৭৮)-এ MUL ছিল, কিন্তু ভেতরে microcode দিয়ে ধাপে ধাপে করা, তাই কয়েক ডজন থেকে শতাধিক cycle লাগত বলে Intel-এর সেই সময়ের ডকুমেন্টে পাওয়া যায়।

Transistor সস্তা হতে থাকলে ছবি বদলায়। প্রথমে sequential multiplier hardware-এ এল (প্রতি cycle কয়েক বিট), তারপর Booth আর carry-save গাছ, শেষে pipelined পূর্ণ multiplier. আজ একটি ৬৪ বিটের multiplier একটি বড় core-এর তুলনায় ছোট অংশ, আর গুণ মোটামুটি ৩–৪ cycle-এর কাজ। এই যাত্রাটি Moore-এর সূত্রের একটি সরাসরি গল্প: transistor বাড়লে আগে software-এ করা কাজ hardware-এ উঠে আসে।

তাহলে RISC-V-এর base RV32I-তে গুণ নেই কেন? কারণ RISC-V শুরু থেকেই একটি modular ISA: সবচেয়ে ছোট core-এর জন্য ন্যূনতম base, আর বাকি সব ঐচ্ছিক extension (M, A, F, D, C, V…)। একটি ছোট sensor-chip বা controller core, যার কাজ মূলত বিট নাড়াচাড়া আর মাঝেমধ্যে যোগ, সেখানে multiplier-এর জায়গা আর power অপচয়। প্রয়োজনে compiler সেখানে গুণকে library ফাংশনে (shift-and-add) বদলে দেয়। আবার Zmmul নামে একটি extension শুধু গুণ (ভাগ ছাড়া) দেয় — যেহেতু ভাগের hardware আরও বড় আর ধীর (পর্ব ২২)।

তুলনায় ARM-এর প্রথম chip ARM1 (১৯৮৫)-এ গুণের instruction ছিল না বলে জানা যায়, ARM2-তে তা যোগ হয় — একই বিবেচনা: আগে ন্যূনতম, পরে প্রয়োজন মতো। x86 পিছনের-সামঞ্জস্যের (backward compatibility) জন্য সবকিছু রেখে দেয়; RISC-V প্রতিটি প্রয়োগের জন্য বেছে নেওয়ার স্বাধীনতা দেয়।

মূল উপলব্ধি: কোন কাজ hardware-এ থাকবে আর কোনটি software-এ, তা প্রযুক্তি আর প্রয়োগের দাম-হিসাবে ঠিক হয় — আর সেই হিসাব সময়ের সাথে বদলায়। ISA-র নকশা এই বদলের জন্য জায়গা রাখলে টিকে থাকে।

প্র ০৩২০৪৮ বিটের RSA-তে বড় সংখ্যার গুণ কীভাবে হয়? শুধু hardware-এর গতি, নাকি algorithm-ও বদলায়?

২০৪৮ বিটের সংখ্যা ৬৪ বিটের ৩২টি টুকরো (যাকে limb বলে)। স্কুলের পদ্ধতিতে দুটি এমন সংখ্যা গুণ করতে ৩২ × ৩২ = ১০২৪টি টুকরো-গুণ লাগে, প্রতিটির পুরো ১২৮ বিট ফল — নিচের অর্ধেক এক ঘরে, ওপরের অর্ধেক পরের ঘরে carry-সহ যোগ। এখানেই mulhu/umulh বা x86-এর mul (RDX:RAX) অপরিহার্য। Intel-এর BMI2 extension-এর MULX flag না ছুঁয়ে গুণ করে, আর ADX-এর ADCX/ADOX দুটি আলাদা carry-শিকল একসাথে চালাতে দেয় — বড় সংখ্যার গুণ দ্রুত করার জন্যই এগুলো আনা হয়েছিল বলে Intel জানিয়েছে।

কিন্তু algorithm-ও বদলায়। স্কুলের পদ্ধতি O(n2) সময় নেয়। ১৯৬০ সালে মস্কোতে তরুণ Anatoly Karatsuba দেখান, দুটি দুই-টুকরোর সংখ্যা চারটির বদলে তিনটি গুণে করা যায়: (a1B + a0)(b1B + b0)-এর মাঝের পদ = (a1 + a0)(b1 + b0) − a1b1 − a0b0. এটি বারবার প্রয়োগ করলে O(nlog23) ≈ O(n1.585) — কথিত আছে, Kolmogorov-এর সেমিনারে O(n2)-ই সেরা বলে অনুমান করা হচ্ছিল, আর Karatsuba কয়েক দিনেই সেটি ভুল প্রমাণ করেন। এরপর Toom–Cook, ১৯৭১ সালে Schönhage–Strassen-এর FFT-ভিত্তিক পদ্ধতি, আর ২০১৯ সালে Harvey ও van der Hoeven-এর O(n log n) algorithm — যদিও শেষটি কেবল বিশাল সংখ্যায় তাত্ত্বিকভাবে দ্রুত।

বাস্তবে GMP-র মতো library সংখ্যার আকার দেখে পদ্ধতি বাছে: ছোট সংখ্যায় স্কুলের পদ্ধতি (কারণ hardware-এর গুণ দ্রুত আর overhead কম), মাঝারিতে Karatsuba আর Toom, বিশালে FFT. RSA-র ২০৪৮ বিট এখনো মাঝারির নিচের দিকে, তাই সেখানে Montgomery multiplication-এর মতো কৌশল (modular গুণে ভাগ এড়ানো) আর হাতে-লেখা assembly-ই মূল। মজার মিল: Wallace tree আর Karatsuba দুটিই “যোগ সস্তা, গুণ দামি” নীতিতে দাঁড়িয়ে — একটি hardware-এ, অন্যটি algorithm-এ।

নিরাপত্তার দিকও আছে: cryptography-র গুণ এমনভাবে লিখতে হয় যাতে সময় গোপন সংখ্যার মানের ওপর নির্ভর না করে (constant-time), নইলে timing থেকে তথ্য ফাঁস হতে পারে। এই কারণে কিছু পুরোনো CPU-তে, যেখানে গুণের সময় operand-এর ওপর নির্ভর করত, cryptography library সতর্ক থাকে।

মূল উপলব্ধি: দ্রুত গুণ দুই স্তরে আসে — hardware একটি word-এর গুণ দ্রুত করে, algorithm word-এর সংখ্যা কমায়। আর ওপরের অর্ধেক দেওয়ার instruction এই দুইয়ের মাঝের সেতু।

প্র ০৪AI-এর চিপগুলো ৩২ বিট float-এর বদলে ৮ বিট integer বা ১৬ বিট ফরম্যাটে গুণ করতে চায় কেন? গুণের খরচ বিটের সাথে কীভাবে বাড়ে?

এই পর্বের একটি তথ্য থেকেই উত্তর শুরু: n বিটের multiplier-এ প্রায় n2টি AND আর n2 ক্রমের full adder. বিট অর্ধেক করলে multiplier-এর আকার আর switching energy মোটামুটি চার ভাগের এক ভাগে নামে। ৩২ থেকে ৮ বিটে নামলে প্রায় ১৬ গুণ কম — আর একই chip-এর জায়গায় অনেক বেশি multiplier বসে। Mark Horowitz-এর ২০১৪ সালের একটি বহুল-উদ্ধৃত হিসাবে (৪৫ nm প্রযুক্তির জন্য) ৮ বিট integer গুণের energy ৩২ বিট float গুণের তুলনায় এক ক্রমের (দশ গুণের) বেশি কম দেখানো হয়েছিল; নির্দিষ্ট সংখ্যা প্রযুক্তি ভেদে বদলায়, কিন্তু প্রবণতাটি স্থির।

Neural network-এর মূল কাজ বিশাল সংখ্যক গুণ-যোগ (dot product বা ডট গুণফল)। প্রশিক্ষিত model-এর weight-গুলো অনেক ক্ষেত্রে ৮ বিটের integer-এ নামিয়ে (quantization) রাখলেও নির্ভুলতা সামান্যই কমে — কারণ network নিজেই কিছুটা “গোলমাল” সহ্য করতে শেখে। এটি আসলে এই পর্বের fixed-point ধারণারই আধুনিক রূপ: একটি integer আর একটি scale factor. Google-এর প্রথম TPU (২০১৬ সালে ঘোষিত) ৮ বিটের integer গুণের একটি বিশাল systolic array-কে কেন্দ্র করে বানানো ছিল বলে Google-এর প্রকাশিত গবেষণাপত্রে বর্ণিত।

প্রশিক্ষণের (training) জন্য পরিসর বেশি লাগে, তাই সেখানে bfloat16-এর মতো ফরম্যাট জনপ্রিয় — ৮ বিটের exponent (float32-এর সমান পরিসর) কিন্তু মাত্র ৮ বিটের significand (লুকানো বিটসহ)। Significand ছোট মানে significand-এর multiplier ছোট — ঠিক এই পর্বের n2 হিসাব। আর জমানোর (accumulate) জন্য সাধারণত চওড়া register রাখা হয় (যেমন ৮ বিট গুণের ফল ৩২ বিটে জমানো), যাতে হাজারো যোগে overflow আর rounding-এর ভুল না জমে — DSP-র MAC-এর মতোই।

সাম্প্রতিক GPU আর accelerator-এ আরও ছোট ফরম্যাট (৮ বিট বা ৪ বিট float-এর রূপভেদ) এসেছে; এগুলোর বিস্তারিত পর্ব ৫৫-এ। কিন্তু প্রতিটি সিদ্ধান্তের পেছনে একই হিসাব: গুণের খরচ বিটের বর্গে বাড়ে, আর data চলাচলের খরচ বিটের সাথে রৈখিকভাবে।

মূল উপলব্ধি: “কত বিট লাগবে” — এই প্রশ্নটি hardware-এর দক্ষতার সবচেয়ে বড় চাবি। প্রয়োজনের চেয়ে বেশি নির্ভুলতা মানে বর্গহারে বাড়তি silicon আর energy.

প্র ০৫Multiplier-এর ভুল কি সত্যিই ঘটে? পরীক্ষা করে সব ইনপুট যাচাই সম্ভব না হলে chip কোম্পানিগুলো গুণের hardware-এর নির্ভুলতা কীভাবে নিশ্চিত করে?

৬৪ বিট × ৬৪ বিটের ইনপুট-জোড়া 2128টি — প্রতি সেকেন্ডে শত কোটি পরীক্ষা করলেও মহাবিশ্বের বয়সের চেয়ে বহু গুণ বেশি সময় লাগবে। তাই exhaustive পরীক্ষা অসম্ভব। ইতিহাসের সবচেয়ে বিখ্যাত গাণিতিক hardware-ভুল ছিল ১৯৯৪ সালের Intel Pentium FDIV bug — ভাগের একটি lookup table-এ কয়েকটি ঘর ফাঁকা থাকায় কিছু বিরল ভাগে সামান্য ভুল ফল আসত (বিস্তারিত পর্ব ২২-এ)। গণিতবিদ Thomas Nicely সংখ্যাতত্ত্বের হিসাব করতে গিয়ে এটি ধরেন। Intel শেষ পর্যন্ত chip বদলে দিতে রাজি হয়, আর এর আর্থিক ক্ষতি বিপুল ছিল বলে Intel জানিয়েছিল।

এই ঘটনার পর শিল্পে formal verification-এর ব্যবহার অনেক বাড়ে। এতে পরীক্ষার বদলে গাণিতিকভাবে প্রমাণ করা হয় যে circuit-টি সব ইনপুটে নির্দিষ্ট গাণিতিক সংজ্ঞা মেনে চলে। Multiplier এখানে বিশেষ কঠিন, কারণ BDD-র মতো সাধারণ কৌশলে গুণের function-এর আকার বিস্ফোরিত হয় (এটি একটি পরিচিত তাত্ত্বিক ফল); তাই theorem prover আর গুণের গঠন-বোঝা বিশেষ পদ্ধতি লাগে। AMD, Intel আর ARM-এর মতো কোম্পানি floating-point আর integer unit-এর formal প্রমাণ নিয়ে প্রবন্ধ প্রকাশ করেছে।

তার পাশাপাশি এই পর্বের মতো যাচাই চলে প্রতিটি স্তরে: একটি সরল “golden model” (software-এ সাধারণ গুণ) আর circuit-এর simulation-কে লাখো এলোমেলো আর বিশেষ ইনপুটে মেলানো — সীমার মান (০, ১, −১, সবচেয়ে বড়, সবচেয়ে ছোট), পালাক্রমের বিট, লম্বা carry-শিকল। আমাদের Booth tracer-এ −৮ × ৭-এর ভুলটি ঠিক এমন একটি সীমার ইনপুটেই ধরা পড়েছে — একটি এলোমেলো ইনপুটে হয়তো চোখেই পড়ত না।

আরেকটি ধরন হলো উৎপাদনের ত্রুটি আর বয়সজনিত ক্ষয়। বড় data center-এর অভিজ্ঞতা নিয়ে সাম্প্রতিক কয়েকটি প্রকাশিত গবেষণায় (Google আর Meta-র প্রকৌশলীদের লেখা) “silent data corruption”-এর কথা এসেছে — কিছু বিরল CPU core নির্দিষ্ট গণিতে চুপচাপ ভুল ফল দেয়, crash না করেই। তাই বড় সিস্টেমে নিয়মিত স্ব-পরীক্ষা আর গুরুত্বপূর্ণ হিসাবে দ্বিগুণ যাচাইও ব্যবহৃত হয়।

মূল উপলব্ধি: গাণিতিক hardware-এর নির্ভুলতা “পরীক্ষা করে দেখলাম” দিয়ে নিশ্চিত হয় না; প্রমাণ, সীমার ইনপুট আর golden model-এর সাথে তুলনা — তিনটিই লাগে। আর আপনার নিজের কোডেও একই নীতি খাটে।

অনুশীলন

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

  1. Booth trace (trace by hand): ৪ বিটের two's complement-এ (−৫) × ৬ Booth-এর algorithm দিয়ে করুন — M = 1011, Q = 0110. প্রতিটি ধাপে (Q0, Q−1), operation, আর shift-এর পরের A, Q, Q−1 লিখুন। কয়টি যোগ-বিয়োগ লাগল? M আর Q অদলবদল করলে (M = 6, Q = −5) কয়টি লাগবে?

    −M = 0101 (১০১১-এর বিট উল্টে 0100, +১)।

    step  Q0Q-1  operation   A     Q     Q-1
     0           initial     0000  0110   0
     1     00    shift       0000  0011   0
     2     10    A = A - M   0101  0011   0     (0000 + 0101)
     2           shift       0010  1001   1
     3     11    shift       0001  0100   1
     4     01    A = A + M   1100  0100   1     (0001 + 1011)
     4           shift       1110  0010   0
    result A:Q = 1110 0010 = -128+64+32+2 = -30

    (−৫) × ৬ = −৩০ ✓. যোগ-বিয়োগ ২টি, কারণ 0110-এ একটিই ১-এর সারি (শুরুতে বিয়োগ, শেষে যোগ)। অদলবদল করলে Q = 1011 (−৫): জোড়াগুলো 10, 11, 01, 10 → বিয়োগ, কিছু না, যোগ, বিয়োগ — ৩টি কাজ, ফল একই −৩০. শিক্ষা: Booth-এ কাজের সংখ্যা multiplier-এর বিট-প্যাটার্নের ওপর নির্ভর করে, তাই কিছু variable-time নকশা কম “সারি”-ওয়ালা সংখ্যাকে multiplier বানায়।

  2. Radix-4 Booth recoding: ৮ বিটের multiplier Q = 1011 0110 (signed −৭৪) আর M = ৩. (ক) Radix-4 Booth অঙ্কগুলো (সবচেয়ে ডান থেকে) বের করুন আর দেখান তাদের মান −৭৪. (খ) প্রতিটি partial product লিখে যোগ করে M × Q যাচাই করুন। (গ) সাধারণ radix-2 পদ্ধতির তুলনায় কয়টি partial product বাঁচল?

    (ক) ডানে q−1 = 0 বসান: 1011 0110|0. ত্রয়ীগুলো (q2j+1 q2j q2j−1):

    • j = 0: q1q0q−1 = 100 → −2
    • j = 1: q3q2q1 = 011 → +2
    • j = 2: q5q4q3 = 110 → −1
    • j = 3: q7q6q5 = 101 → −1

    মান: −2 + 2·4 − 1·16 − 1·64 = −২ + ৮ − ১৬ − ৬৪ = −৭৪ ✓.

    (খ) Partial product: −2·3 = −৬; +2·3·4 = ২৪; −1·3·16 = −৪৮; −1·3·64 = −১৯২. যোগফল −৬ + ২৪ − ৪৮ − ১৯২ = −২২২ = ৩ × (−৭৪) ✓.

    (গ) Radix-2-এ ৮টি partial product (প্রতি বিটে একটি), radix-4-এ ৪টি — অর্ধেক, অর্থাৎ ৪টি বাঁচল। আর কোনো partial product ±M বা ±2M-এর বাইরে যায়নি, তাই ৩M বানানোর দরকার হয়নি।

  3. Wallace tree-র স্তর (numerical): (ক) ২৪টি partial product-কে 3:2 carry-save adder দিয়ে দুটিতে নামাতে Wallace পদ্ধতিতে কয়টি স্তর লাগে? সারির ক্রম লিখুন। (খ) IEEE double-এর ৫৩ বিট significand গুণে সরাসরি ৫৩টি সারি, আর radix-4 Booth দিয়ে ২৭টি — দুই ক্ষেত্রে স্তর কত? (গ) প্রতিটি full adder-এর delay ২ gate delay আর শেষ adder ১২ gate delay ধরলে (খ)-এর দুই নকশায় partial product তৈরির পর মোট delay কত?

    নিয়ম: পরের সারি = 2⌊r/3⌋ + (r mod 3).

    • (ক) ২৪ → ১৬ → ১১ → ৮ → ৬ → ৪ → ৩ → ২: ৭ স্তর. (২৪ = ৮টি দল → ১৬; ১৬ = ৫ দল + ১ → ১১; ১১ = ৩ দল + ২ → ৮; তারপর ৮-এর চেনা ক্রম।)
    • (খ) ৫৩ → ৩৬ → ২৪ → ১৬ → ১১ → ৮ → ৬ → ৪ → ৩ → ২: ৯ স্তর. ২৭ → ১৮ → ১২ → ৮ → ৬ → ৪ → ৩ → ২: ৭ স্তর.
    • (গ) সরাসরি: ৯ × ২ + ১২ = ৩০ gate delay. Radix-4 Booth: ৭ × ২ + ১২ = ২৬ gate delay (recoding আর ±M বাছাইয়ের mux-এর কয়েকটি delay আলাদা যোগ হবে, কিন্তু full adder প্রায় অর্ধেক লাগে — আসল লাভ আকার আর power-এ)।
  4. আউটপুট অনুমান করুন (mulh-এর অর্থ): RV32IM-এ a0 = 0x80000000 আর a1 = 0xFFFFFFFF. নিচের প্রতিটি instruction-এর পর a2-তে কী থাকবে? signed গুণ হিসেবে ফলটি কি ৩২ বিটে ধরেছে?
    mul    a2, a0, a1
    mulh   a2, a0, a1
    mulhu  a2, a0, a1
    mulhsu a2, a0, a1
    • Signed: a0 = −231, a1 = −1. গুণফল +231 = ৬৪ বিটে 0x00000000_80000000. তাই mul → 0x80000000, mulh → 0x00000000.
    • Unsigned: 231 × (232 − 1) = 263 − 231 = 0x7FFFFFFF_80000000. তাই mulhu → 0x7FFFFFFF (নিচের অর্ধেক সেই একই 0x80000000).
    • Signed × unsigned: −231 × (232 − 1) = −263 + 231; ৬৪ বিটের two's complement-এ 0x80000000_80000000. তাই mulhsu → 0x80000000.

    Overflow: হ্যাঁ, signed overflow ঘটেছে। নিচের অর্ধেক 0x80000000-এর sign bit ১, তাই ফল ৩২ বিটে ধরলে ওপরের অর্ধেক হওয়া উচিত ছিল 0xFFFFFFFF; অথচ mulh দিয়েছে 0x00000000. বাস্তবেও +231 ৩২ বিটের signed পরিসরের (সর্বোচ্চ 231 − 1) বাইরে। এটি সেই বিখ্যাত ক্ষেত্র: INT_MIN × −1. লক্ষ করুন, তিন ব্যাখ্যাতেই গুণফলের নিচের অর্ধেক একই (0x80000000) — শুধু ওপরের অর্ধেক ব্যাখ্যা ভেদে বদলায়।

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

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

ক্রমBooth-এর algorithm দিয়ে n বিটের দুটি signed সংখ্যা গুণ করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. A = 0, Q = multiplier, Q−1 = 0, M = multiplicand বসানো
  2. (Q0, Q−1) জোড়াটি পরীক্ষা করা
  3. 10 হলে A = A − M, 01 হলে A = A + M, নইলে কিছু না
  4. A:Q:Q−1-কে এক ঘর arithmetic right shift
  5. n বার হলে থেমে A:Q-কে 2n বিটের signed গুণফল হিসেবে পড়া

দুটি ১৬ বিটের unsigned সংখ্যার গুণফল কোনো তথ্য না হারিয়ে রাখতে সর্বনিম্ন কত বিট লাগে?

  • ১৬
  • ৩২
  • ৩১
  • ২৫৬

Booth-এর algorithm-এ বর্তমান জোড়া (Q0, Q−1) = (1, 0) হলে কী করা হয়?

  • A = A + M, তারপর arithmetic right shift
  • কিছু না, শুধু arithmetic right shift
  • A = A − M, তারপর arithmetic right shift
  • A = A − M, তারপর logical right shift

৮ বিটের কোন multiplier-এ radix-2 Booth-এর algorithm সবচেয়ে বেশি যোগ-বিয়োগ করে?

  • 1111 1111
  • 0111 1110
  • 0000 0000
  • 0101 0101

Radix-4 (modified) Booth recoding-এ বিট-ত্রয়ী (q2j+1, q2j, q2j−1) = 100 হলে partial product কী?

  • −2M
  • +2M
  • −M
  • ০

৮টি partial product-কে 3:2 carry-save adder (full adder) দিয়ে Wallace tree-তে দুটি সংখ্যায় নামাতে কয়টি স্তর লাগে?

  • ৩
  • ৭
  • ৪
  • ৬

RV32IM-এ mulhu t0, a0, a1 কী দেয়?

  • a0 × a1-এর নিচের ৩২ বিট
  • a0 আর a1-কে unsigned ধরে ৬৪ বিটের গুণফলের ওপরের ৩২ বিট
  • a0 আর a1-কে signed ধরে ওপরের ৩২ বিট
  • a0 signed আর a1 unsigned ধরে ওপরের ৩২ বিট

৩২ বিটের unsigned x-এর জন্য x * 10-এর সমান কোনটি (সব x-এ, overflow-সহ)?

  • (x << 3) + x
  • (x << 2) + (x << 1)
  • x << 10
  • (x << 3) + (x << 1)

শব্দকোষ

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

মনে রাখুন

  1. Binary গুণ = AND + shift + যোগ: প্রতিটি multiplier বিট একটি partial product দেয়; n × n বিটের গুণফলে 2n বিট লাগে। আসল খরচ সারিগুলোর যোগে।
  2. Shift-and-add (উন্নত): 2n বিটের product register-এর ডান অর্ধেকে multiplier; প্রতি iteration-এ Prod[0] = 1 হলে বাঁ অর্ধেকে multiplicand যোগ, তারপর ডানে shift — n টি iteration, একটি n বিটের adder.
  3. Booth: (Q0, Q−1) = 10 → A − M, 01 → A + M, তারপর A:Q:Q−1-এর arithmetic shift; ভিত্তি Q = Σ (qi−1 − qi)2i. Signed সরাসরি, ১-এর সারিতে দুটি কাজ, পালাক্রমের বিটে n টি; M = −2n−1-এ A-কে এক বিট চওড়া রাখতে হয়।
  4. দ্রুত multiplier: radix-4 Booth partial product অর্ধেক করে ({−2..+2}); Wallace/Dadda tree 3:2 compressor-এ সারি কমায় (৮ → ৬ → ৪ → ৩ → ২, ৬৪ সারিতে ১০ স্তর), শেষে একটি দ্রুত adder; pipelined হয়ে আধুনিক CPU-তে প্রায় ৩–৪ cycle.
  5. Instruction ও software: RISC-V mul নিচের অর্ধেক, mulh/mulhu/mulhsu ওপরের অর্ধেক; x86 এক-operand mul/imul → RDX:RAX. ওপরের অর্ধেক দিয়ে overflow, বড় সংখ্যা আর Q31. ধ্রুবক গুণ shift-add-এ, Q15 গুণে ১৫ ঘর shift আর rounding.

পরের পর্বে: গুণের উল্টো কাজ — ভাগ। একই shift-register-এর ছক, কিন্তু যোগের বদলে বিয়োগ আর একটি বড় প্রশ্ন: বিয়োগফল ঋণাত্মক হলে কী? Restoring আর non-restoring division ধাপে ধাপে, আর কেন ভাগ গুণের চেয়ে অনেক ধীর।

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

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