পাঠ ০৯ · ৫৭-এর মধ্যে · মডিউল ২
Home / Courses / Computer Architecture & Digital Logic / রিপল ক্যারি

রিপল ক্যারি অ্যাডার ও সাবট্রাক্টর

Ripple carry adder and subtractor
৮ মিনিট পড়া শুরু · Beginner Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • কীভাবে N-টি ফুল অ্যাডার চেইন করে বহু-বিট সংখ্যা যোগ করা হয়
  • "ripple" delay ঠিক কী, এবং কেন এটি একটি বাস্তব পারফরম্যান্স উদ্বেগ
  • একই অ্যাডার হার্ডওয়্যার দিয়ে বিয়োগ করার two's-complement ট্রিক
  • Python কোডে ripple_carry_adder ও adder_subtractor বানিয়ে Python-এর নিজস্ব পাটিগণিতের সাথে ফলাফল মিলিয়ে যাচাই করা

১ · N-টি ফুল অ্যাডার চেইন করে বহু-বিট যোগফল

L08-এর ফুল অ্যাডার একটি একক বিট-পজিশনের জন্য কাজ করে। বাস্তব সংখ্যা (যেমন 4-বিট বা 32-বিট) যোগ করতে হলে, প্রতিটি বিট-পজিশনের জন্য একটি করে ফুল অ্যাডার লাগবে — এবং একটির Carry-out পরেরটির Carry-in-এ সংযুক্ত হবে। একে বলে রিপল ক্যারি অ্যাডারRipple Carry AdderN-টি ফুল অ্যাডার চেইন করে বানানো একটি সার্কিট, যেখানে carry সবচেয়ে কম গুরুত্বপূর্ণ বিট (LSB) থেকে সবচেয়ে বেশি গুরুত্বপূর্ণ বিট (MSB) পর্যন্ত ধাপে ধাপে বয়ে যায়। — কারণ carry ঠিক ঢেউয়ের মতো, সবচেয়ে নিচের (least-significant) বিট থেকে সবচেয়ে উপরের (most-significant) বিট পর্যন্ত এক-এক ধাপ করে বয়ে যায়।

FA০ (LSB) Cin=0 FA১ FA২ FA৩ (MSB) Cout চূড়ান্ত প্রতিটি ফুল অ্যাডারের Cout → পরেরটির Cin (carry "ripple" করে বয়ে যায়) প্রতিটি FA-তে একজোড়া A,B বিট প্রবেশ করে, সব FA-এর Sum মিলিয়েই চূড়ান্ত ফলাফল
৪-বিট রিপল ক্যারি অ্যাডার — LSB-এর ফুল অ্যাডার থেকে শুরু করে MSB পর্যন্ত carry ধাপে ধাপে বয়ে যায়।

একটি গুরুত্বপূর্ণ, বাস্তব পারফরম্যান্স সীমাবদ্ধতা এখানেই লুকিয়ে আছে — ripple delay। সবচেয়ে উপরের (MSB) বিটের সঠিক ফলাফল পেতে হলে, carry-কে প্রথমে LSB থেকে শুরু করে প্রতিটি ফুল অ্যাডার একে একে পার হতে হয় — তাই N-বিট সংখ্যায়, সবচেয়ে খারাপ ক্ষেত্রে (worst case) মোট বিলম্ব N-টি ফুল অ্যাডারের বিলম্বের সমষ্টির সমানুপাতিক। বাস্তব হার্ডওয়্যারে দ্রুততর অ্যাডার ডিজাইন (যেমন carry-lookahead adder) বিদ্যমান, কিন্তু এই কোর্স ভিত্তি হিসেবে রিপল ক্যারি অ্যাডারেই সীমাবদ্ধ থাকবে।

২ · একই হার্ডওয়্যার দিয়ে বিয়োগ — two's complement ট্রিক

একটি সত্যিই এলিগ্যান্ট বাস্তব হার্ডওয়্যার কৌশল — বিয়োগের জন্য আলাদা কোনো সার্কিট বানানোর দরকার নেই। গাণিতিকভাবে, A - B = A + (-B)। আর two's complement রিপ্রেজেন্টেশনে (L18-এ বিস্তারিত আসবে), -B ঠিক (NOT B) + 1-এর সমান। তাই —

A - B = A + (~B) + 1

B-এর প্রতিটি বিট ইনভার্ট (NOT) করে দিলে, এবং অ্যাডারের প্রাথমিক Carry-in-কে 0-এর বদলে 1 বসিয়ে দিলে (এই "+1"-টাই সরবরাহ করে), একই রিপল ক্যারি অ্যাডার হার্ডওয়্যার A - B সঠিকভাবে বের করে দেয় — কোনো অতিরিক্ত সার্কিট ছাড়াই, শুধু একটি XOR-নিয়ন্ত্রিত bit-inverter আর একটি mode-select ওয়্যার যোগ করলেই চলে।

নিচের কোড সেলে L08-এর full_adder পুনর্ব্যবহার করে প্রথমে একটি ripple_carry_adder বানানো হবে (4-বিট যোগফল, Python-এর নিজস্ব পূর্ণসংখ্যা যোগফলের সাথে মিলিয়ে যাচাই), তারপর একটি adder_subtractor ফাংশন — যা subtract_mode=True হলে B-এর বিট ইনভার্ট করে ও প্রাথমিক carry 1 বসায় — বানিয়ে একটি বিয়োগের উদাহরণ যাচাই করা হবে।

Python
def XOR(a, b): return a ^ b
def AND(a, b): return a & b
def OR(a, b):  return a | b

def half_adder(a, b):
    return XOR(a, b), AND(a, b)

def full_adder(a, b, cin):
    """L08 থেকে পুনর্ব্যবহৃত -- দুটি হাফ অ্যাডার + OR"""
    s1, c1 = half_adder(a, b)
    s2, c2 = half_adder(s1, cin)
    return s2, OR(c1, c2)

def int_to_bits(n, width):
    """পূর্ণসংখ্যা -> MSB-first বিট তালিকা"""
    return [(n >> i) & 1 for i in range(width - 1, -1, -1)]

def bits_to_int(bits):
    """MSB-first বিট তালিকা -> পূর্ণসংখ্যা"""
    value = 0
    for b in bits:
        value = value * 2 + b
    return value

def ripple_carry_adder(a_bits, b_bits):
    """N-টি ফুল অ্যাডার চেইন করে -- (sum_bits, carry_out) রিটার্ন করে, সব MSB-first"""
    a_lsb = a_bits[::-1]
    b_lsb = b_bits[::-1]
    carry = 0
    sum_lsb = []
    for i in range(len(a_bits)):
        s, carry = full_adder(a_lsb[i], b_lsb[i], carry)
        sum_lsb.append(s)
    return sum_lsb[::-1], carry

# ৪-বিট যোগফল: 5 + 3
a_bits = int_to_bits(5, 4)
b_bits = int_to_bits(3, 4)
sum_bits, cout = ripple_carry_adder(a_bits, b_bits)
print("রিপল ক্যারি অ্যাডার: 5 + 3")
print(f"  a_bits={a_bits}  b_bits={b_bits}")
print(f"  sum_bits={sum_bits}  carry_out={cout}")
print(f"  bits_to_int(sum_bits) = {bits_to_int(sum_bits)}  |  Python-এর 5+3 = {5 + 3}")
print(f"  মিলেছে কি? {bits_to_int(sum_bits) == 5 + 3}")

def adder_subtractor(a_bits, b_bits, subtract_mode=False):
    """subtract_mode=True হলে B ইনভার্ট + Cin=1 বসিয়ে A-B বের করে, একই অ্যাডার হার্ডওয়্যার দিয়ে"""
    b_used = [1 - bit for bit in b_bits] if subtract_mode else b_bits
    a_lsb = a_bits[::-1]
    b_lsb = b_used[::-1]
    carry = 1 if subtract_mode else 0
    sum_lsb = []
    for i in range(len(a_bits)):
        s, carry = full_adder(a_lsb[i], b_lsb[i], carry)
        sum_lsb.append(s)
    return sum_lsb[::-1], carry

# ৪-বিট বিয়োগ: 7 - 3
a2 = int_to_bits(7, 4)
b2 = int_to_bits(3, 4)
result_bits, cout2 = adder_subtractor(a2, b2, subtract_mode=True)
print()
print("adder_subtractor (একই হার্ডওয়্যার, subtract_mode=True): 7 - 3")
print(f"  a_bits={a2}  b_bits={b2}  (B ইনভার্ট হয়ে ব্যবহৃত হয়েছে, Cin প্রাথমিকভাবে 1)")
print(f"  result_bits={result_bits}  carry_out={cout2} (৪-বিট রেঞ্জের বাইরে গেলে বাতিল হয়)")
print(f"  bits_to_int(result_bits) = {bits_to_int(result_bits)}  |  Python-এর 7-3 = {7 - 3}")
print(f"  মিলেছে কি? {bits_to_int(result_bits) == 7 - 3}")

    
বিয়োগের কেসে carry_out=1 এসেছে, কিন্তু সেটি উপেক্ষা (discard) করা হয়েছে — ৪-বিট সীমার মধ্যে থাকা একটি বৈধ বিয়োগের ফলাফলে এই "অতিরিক্ত" carry স্বাভাবিক এবং প্রত্যাশিত (two's complement বিয়োগে চূড়ান্ত carry-out ফেলে দেওয়াই নিয়ম) — এই আচরণের পূর্ণাঙ্গ ব্যাখ্যা ও ওভারফ্লো-নির্ণয় L18-L19-এ বিস্তারিত আসবে। গুরুত্বপূর্ণ বিষয় হলো, চূড়ান্ত result_bits Python-এর নিজস্ব 7 - 3-এর সাথে হুবহু মিলে গেছে।
মূল কথা · Key takeaway

রিপল ক্যারি অ্যাডার দেখায় কীভাবে ছোট বিল্ডিং ব্লক (ফুল অ্যাডার) পুনরাবৃত্তি করে বহু-বিট বাস্তব যোগফল বানানো যায়, আর adder-subtractor ট্রিক দেখায় কীভাবে সামান্য অতিরিক্ত লজিক (bit-inverter + mode-select) দিয়েই একই হার্ডওয়্যার দুটি কাজ (যোগ ও বিয়োগ) করতে পারে — বাস্তব CPU-এর ALU-তে ঠিক এই নীতিই ব্যবহৃত হয়।

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

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

প্র ০১ ৮-বিট রিপল ক্যারি অ্যাডারের তুলনায় ৩২-বিট রিপল ক্যারি অ্যাডারে ripple delay কীভাবে বদলাবে?

ripple delay একটি ফুল অ্যাডারের বিলম্ব × বিটের সংখ্যার সমানুপাতিক (worst case), তাই ৩২-বিট অ্যাডারে ৮-বিটের তুলনায় প্রায় চারগুণ বেশি বিট থাকায় সবচেয়ে খারাপ ক্ষেত্রে বিলম্বও প্রায় চারগুণ বেশি হতে পারে — কারণ carry-কে সবচেয়ে নিচের বিট থেকে শুরু করে চারগুণ বেশি ফুল অ্যাডার পার হয়ে যেতে হয়। এই কারণেই বাস্তব উচ্চ-পারফরম্যান্স CPU-তে দ্রুততর (কিন্তু বেশি জটিল) অ্যাডার ডিজাইন ব্যবহৃত হয়।

প্র ০২ বিয়োগের সময় শুধু B ইনভার্ট করলেই যথেষ্ট নয় কেন — Cin=1 বসানো কেন আবশ্যক?

শুধু B-এর প্রতিটি বিট ইনভার্ট করলে ওয়ান্স-কমপ্লিমেন্ট (~B) পাওয়া যায়, যা -B-এর সমান নয় — two's complement সংজ্ঞা অনুযায়ী -B = (~B) + 1। এই "+1"-টুকু ছাড়া ফলাফল এক কম হয়ে যাবে। রিপল ক্যারি অ্যাডারে এই "+1" আলাদা কোনো এক্সট্রা যোগফল ধাপ ছাড়াই, শুধু সবচেয়ে নিচের ফুল অ্যাডারের প্রাথমিক Cin-কে 0-এর পরিবর্তে 1 বসিয়ে বিনামূল্যে পাওয়া যায় — এটিই এই ট্রিকের আসল কৌশল।

প্র ০৩ উপরের কোডে ripple_carry_adder এবং adder_subtractor(subtract_mode=False) — এই দুটো ফাংশন কি একই কাজ করে?

হ্যাঁ, প্রায় হুবহু একই — adder_subtractor-এ subtract_mode=False দিলে b_used = b_bits (কোনো ইনভার্শন হয় না) এবং প্রাথমিক carry = 0 হয়, যা ঠিক ripple_carry_adder-এর আচরণের সমান। এটাই দেখায় কেন বাস্তব হার্ডওয়্যারে যোগ ও বিয়োগের জন্য আলাদা সার্কিট না বানিয়ে একটি একক, mode-select-নিয়ন্ত্রিত ইউনিট বানানো অর্থনৈতিকভাবে সুবিধাজনক।

অনুশীলন

  1. হাতে বসিয়ে দেখুন: ৪-বিট রিপল ক্যারি অ্যাডার দিয়ে 6 + 2 যোগ করার সময় প্রতিটি বিট-পজিশনের Sum ও Carry হাতে-কলমে ট্রেস করুন, তারপর মোট ফলাফল 8-এর সাথে মেলে কিনা যাচাই করুন।

    6 = 0110, 2 = 0010। LSB (বিট ০): 0+0+Cin(0) = Sum 0, Carry 0। বিট ১: 1+1+0 = Sum 0, Carry 1। বিট ২: 1+0+1 = Sum 0, Carry 1। বিট ৩ (MSB): 0+0+1 = Sum 1, Carry 0। চূড়ান্ত sum_bits = 1000 = দশমিকে 8 — ঠিক 6+2=8-এর সাথে মেলে, এবং চূড়ান্ত carry_out=0 (কোনো ওভারফ্লো নেই)।

  2. চিন্তা করুন: যদি adder_subtractor-এ subtract_mode=True দিয়ে B হিসেবে A-এর চেয়ে বড় একটি সংখ্যা দেওয়া হয় (যেমন 3-7 চেষ্টা করা), ফলাফল বিটগুলো কী প্রকাশ করবে?

    ফলাফল হবে -4-এর ৪-বিট two's complement রিপ্রেজেন্টেশন (1100), শুধু ধনাত্মক পূর্ণসংখ্যা নয় — কারণ এই সার্কিট two's complement অ্যারিথমেটিকে কাজ করে, যেখানে ঋণাত্মক সংখ্যাও একই বিট-প্যাটার্নে প্রকাশ পায়। এই বিট-প্যাটার্নকে সঠিকভাবে ঋণাত্মক সংখ্যা হিসেবে ব্যাখ্যা করাটাই L18-এর মূল বিষয়বস্তু — এখানে শুধু লক্ষ্য করুন যে চূড়ান্ত carry_out=0 আসবে, যা এই নির্দিষ্ট ক্ষেত্রে ভিন্ন অর্থ বহন করে।

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

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ M4-এ (L18-L19) two's complement ও signed arithmetic বিস্তারিতভাবে ফিরে আসবে, এই পাঠের ট্রিককে পূর্ণাঙ্গ প্রেক্ষাপটে ব্যাখ্যা করে।
  • আগের পাঠ L08 হাফ অ্যাডার ও ফুল অ্যাডার — এই পাঠের রিপল ক্যারি অ্যাডার ঠিক সেই ফুল অ্যাডারকেই চেইন করে বানানো।
  • পরের পাঠ L10 মাল্টিপ্লেক্সার ও ডিমাল্টিপ্লেক্সার — CPU ডেটাপাথে কোন সিগন্যাল কোথায় যাবে তা নির্বাচন করার সার্কিট।
আগের পাঠ
হাফ অ্যাডার ও ফুল অ্যাডার