পাঠ ২৯ · ৫৭-এর মধ্যে · মডিউল ৬
Home / Courses / Computer Architecture & Digital Logic / CPU ডেটাপাথ ও কন্ট্রোল

কন্ট্রোল ইউনিট — হার্ডওয়্যার্ড কন্ট্রোল

Control unit — hardwired control
৮ মিনিট পড়া উচ্চ · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • কন্ট্রোল ইউনিটের কাজ, এবং এটি কীভাবে L17-এর FSM ধারণার সরাসরি বাস্তবায়ন
  • হার্ডওয়্যার্ড কন্ট্রোলের সংজ্ঞা, এর গতি-সুবিধা এবং নমনীয়তা-সীমাবদ্ধতা
  • কন্ট্রোল সিগন্যাল টেবিল কী এবং কেন এটি মূলত একটি truth table (L04)
  • Python দিয়ে একটি বাস্তব generate_control_signals(opcode) লুকআপ টেবিল বানিয়ে ৫টি ইনস্ট্রাকশনের জন্য সঠিক, স্বতন্ত্র সিগন্যাল সংমিশ্রণ যাচাই করা

১ · কন্ট্রোল ইউনিট — L17-এর FSM-এর বাস্তব রূপ

L28-এর ডেটাপাথে বহু জায়গায় সিদ্ধান্ত নেওয়া দরকার — ALU-এর দ্বিতীয় ইনপুট রেজিস্টার হবে না immediate, ফলাফল রেজিস্টার ফাইলে লেখা হবে কিনা, মেমরি পড়তে হবে কিনা ইত্যাদি। কন্ট্রোল ইউনিটControl Unitডেটাপাথের সব সিগন্যাল নিয়ন্ত্রণকারী উপাদান। হলো ঠিক এই সিদ্ধান্তগুলো — কন্ট্রোল সিগন্যাল আকারে — তৈরি করার দায়িত্বে থাকা উপাদান। L17-এ শিখেছিলেন ফাইনাইট স্টেট মেশিন (FSM) কী — কন্ট্রোল ইউনিট আসলে সেই ধারণারই একটি বাস্তব বাস্তবায়ন: সিঙ্গেল-সাইকেল ডিজাইনে, বর্তমান ইনস্ট্রাকশনের opcode-ই কার্যত সেই "স্টেট" যা সব আউটপুট সিগন্যাল নির্ধারণ করে।

২ · হার্ডওয়্যার্ড কন্ট্রোল — ফিক্সড কম্বিনেশনাল লজিক

হার্ডওয়্যার্ড কন্ট্রোলHardwired Controlopcode ইনপুট নিয়ে সরাসরি ফিক্সড কম্বিনেশনাল লজিক দিয়ে কন্ট্রোল সিগন্যাল আউটপুট তৈরি করা। পদ্ধতিতে, কন্ট্রোল সিগন্যালগুলো তৈরি হয় একটি ফিক্সড কম্বিনেশনাল সার্কিট দিয়ে (L04-L07-এর গেট দিয়ে তৈরি) — যা ইনস্ট্রাকশনের opcode (এবং প্রয়োজনে অন্য ফিল্ড)-কে ইনপুট নিয়ে সরাসরি প্রয়োজনীয় সিগন্যাল মান আউটপুট করে। এর সুবিধা: এটি দ্রুত — কম্বিনেশনাল লজিক হওয়ায় কোনো অতিরিক্ত ক্লক সাইকেল দরকার হয় না, ফলাফল প্রায় তাৎক্ষণিক পাওয়া যায়। কিন্তু এর একটি গুরুত্বপূর্ণ সীমাবদ্ধতা: এটি অনমনীয় — ISA-তে একটি নতুন ইনস্ট্রাকশন যোগ করতে হলে এই সার্কিটটিকে শারীরিকভাবে পুনর্ডিজাইন করতে হয় — একটি বাস্তব, গুরুত্বপূর্ণ হার্ডওয়্যার-ইঞ্জিনিয়ারিং খরচ (L30-এর বিকল্প পদ্ধতির সরাসরি অনুপ্রেরণা)।

৩ · কন্ট্রোল সিগন্যাল টেবিল — মূলত একটি Truth Table

ব্যবহারিক ডিজাইন টুল হলো একটি কন্ট্রোল সিগন্যাল টেবিল — প্রতিটি opcode-কে ঠিক কোন কন্ট্রোল সিগন্যাল সংমিশ্রণের সাথে ম্যাপ করা দরকার তার একটি টেবিল (যেমন R-type ADD-এর জন্য RegWrite=1, MemRead=0, ALUOp=ADD)। এটি আক্ষরিক অর্থেই L04-এর truth table-এর একটি রূপ — opcode বিটগুলো ইনপুট, কন্ট্রোল সিগন্যালগুলো আউটপুট।

RegWrite
ফলাফল রেজিস্টার ফাইলে লেখা হবে কিনা
MemRead
ডেটা মেমরি থেকে পড়া হবে কিনা (শুধু LOAD)
MemWrite
ডেটা মেমরিতে লেখা হবে কিনা (শুধু STORE)
ALUOp
ALU কোন অপারেশন করবে (ADD/SUB ইত্যাদি)
Branch
এটি কি একটি শর্তাধীন ব্রাঞ্চ ইনস্ট্রাকশন (BEQ)
ALUSrc
ALU-এর দ্বিতীয় ইনপুট রেজিস্টার (0) না immediate (1)

নিচের কোড সেলে generate_control_signals(opcode) একটি dict-ভিত্তিক লুকআপ টেবিল হিসেবে ৫টি ইনস্ট্রাকশন টাইপের জন্য এই সিগন্যালগুলো তৈরি করে, এবং পুরো টেবিল প্রিন্ট করে যাচাই করে যে প্রতিটি সংমিশ্রণ স্বতন্ত্র ও সঠিক।

Python
# -- হার্ডওয়্যার্ড কন্ট্রোল সিমুলেশন: এই dict-লুকআপ opcode থেকে সিগন্যাল আউটপুটের সেই কম্বিনেশনাল সার্কিটকেই প্রতিনিধিত্ব করছে

def generate_control_signals(opcode):
    """একটি ফিক্সড লুকআপ টেবিল -- বাস্তব হার্ডওয়্যারে এটি গেট দিয়ে তৈরি কম্বিনেশনাল লজিক"""
    table = {
        'ADD':   {'RegWrite': 1, 'MemRead': 0, 'MemWrite': 0, 'ALUOp': 'ADD', 'Branch': 0, 'ALUSrc': 0},
        'ADDI':  {'RegWrite': 1, 'MemRead': 0, 'MemWrite': 0, 'ALUOp': 'ADD', 'Branch': 0, 'ALUSrc': 1},
        'LOAD':  {'RegWrite': 1, 'MemRead': 1, 'MemWrite': 0, 'ALUOp': 'ADD', 'Branch': 0, 'ALUSrc': 1},
        'STORE': {'RegWrite': 0, 'MemRead': 0, 'MemWrite': 1, 'ALUOp': 'ADD', 'Branch': 0, 'ALUSrc': 1},
        'BEQ':   {'RegWrite': 0, 'MemRead': 0, 'MemWrite': 0, 'ALUOp': 'SUB', 'Branch': 1, 'ALUSrc': 0},
    }
    return table[opcode]

opcodes = ['ADD', 'ADDI', 'LOAD', 'STORE', 'BEQ']
signal_names = ['RegWrite', 'MemRead', 'MemWrite', 'ALUOp', 'Branch', 'ALUSrc']

print(f"{'opcode':<8}" + "".join(f"{name:<11}" for name in signal_names))
print("-" * 74)
all_signals = {}
for op in opcodes:
    sig = generate_control_signals(op)
    all_signals[op] = sig
    print(f"{op:<8}" + "".join(f"{str(sig[name]):<11}" for name in signal_names))

# ---- যাচাই: শুধু LOAD-এর MemRead=1, শুধু STORE-এর MemWrite=1 ----
mem_readers = [op for op in opcodes if all_signals[op]['MemRead'] == 1]
mem_writers = [op for op in opcodes if all_signals[op]['MemWrite'] == 1]
print(f"\nMemRead=1 যাদের জন্য: {mem_readers}")
print(f"MemWrite=1 যাদের জন্য: {mem_writers}")

assert mem_readers == ['LOAD'], "শুধু LOAD-এরই MemRead=1 হওয়া উচিত!"
assert mem_writers == ['STORE'], "শুধু STORE-এরই MemWrite=1 হওয়া উচিত!"
assert all_signals['BEQ']['Branch'] == 1 and all(all_signals[op]['Branch'] == 0 for op in opcodes if op != 'BEQ')
print("\nযাচাই সফল -- মেমরি-সংক্রান্ত ও ব্রাঞ্চ সিগন্যাল প্রতিটি opcode-এর জন্য সঠিকভাবে স্বতন্ত্র।")

    
লক্ষ করুন BEQ-এর ALUOp='SUB' — কারণ "দুটো রেজিস্টার সমান কিনা" পরীক্ষা করার সবচেয়ে সহজ হার্ডওয়্যার কৌশল হলো তাদের বিয়োগ করে ফলাফল শূন্য কিনা দেখা (বিয়োগফল শূন্য মানেই দুটো মান সমান) — এটি L09-এর অ্যাডার/সাবট্রাক্টর ইউনিটেরই আরেকটি ব্যবহারিক প্রয়োগ, কোনো আলাদা "তুলনা সার্কিট" ছাড়াই।
মূল কথা · Key takeaway

হার্ডওয়্যার্ড কন্ট্রোল একটি ফিক্সড, দ্রুত কম্বিনেশনাল লজিক সার্কিট — কিন্তু ISA পরিবর্তন করা মানেই সার্কিট পুনর্ডিজাইন। L30-এ দেখবেন কীভাবে এই একই কন্ট্রোল সিগন্যালগুলো সম্পূর্ণ ভিন্ন একটি পদ্ধতিতে — একটি অভ্যন্তরীণ "মাইক্রোপ্রোগ্রাম" থেকে পড়ে — তৈরি করা যায়, এবং কেন উভয় পদ্ধতিই একই সঠিক ফলাফল দেয়।

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

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

প্র ০১ ADD ও ADDI-এর প্রায় সব সিগন্যাল একই, শুধু ALUSrc আলাদা কেন?

দুটোই রেজিস্টার ফাইলে ফলাফল লেখে (RegWrite=1), কোনো মেমরি স্পর্শ করে না, এবং ALU-তে যোগ করে (ALUOp=ADD) — একমাত্র পার্থক্য হলো ALU-এর দ্বিতীয় ইনপুট কোথা থেকে আসে: ADD-এর জন্য দ্বিতীয় রেজিস্টার (rs2, ALUSrc=0), ADDI-এর জন্য ইনস্ট্রাকশনের ভেতরে থাকা immediate মান (ALUSrc=1)। এই একটি সিগন্যালই L28-এর MUX-কে বলে দেয় কোন উৎস বেছে নিতে হবে।

প্র ০২ হার্ডওয়্যার্ড কন্ট্রোলে যদি ISA-তে একটি নতুন "MUL" (গুণ) ইনস্ট্রাকশন যোগ করতে হয়, তাহলে ঠিক কী কাজ করতে হবে?

কম্বিনেশনাল সার্কিট (বা এই পাঠের dict টেবিল)-এ একটি নতুন এন্ট্রি যোগ করলেই যথেষ্ট নয় — বাস্তব হার্ডওয়্যারে এর মানে opcode-ডিকোডিং লজিকের গেট-স্তরের সার্কিটটাই শারীরিকভাবে বদলাতে হবে (নতুন গেট, নতুন তার-সংযোগ, সম্ভবত নতুন চিপ ফ্যাব্রিকেশন) — এটিই হার্ডওয়্যার্ড কন্ট্রোলের মূল অসুবিধা, এবং ঠিক এই কারণেই L30-এর মাইক্রোপ্রোগ্রামড পদ্ধতি ঐতিহাসিকভাবে গুরুত্বপূর্ণ হয়ে ওঠে।

প্র ০৩ কোড সেলের যাচাই-অংশে assert mem_readers == ['LOAD'] কেন গুরুত্বপূর্ণ — এটি স্রেফ প্রিন্ট করে দেখালেই কি যথেষ্ট হতো না?

প্রিন্ট করাটা মানুষকে দেখানোর জন্য দরকারি, কিন্তু assert কোডকেই বাধ্য করে প্রমাণ করতে যে টেবিলে কোনো ভুল opcode-এ MemRead=1 বসে যায়নি — এটি ঠিক L05/L06-এর "truth-table সমতা দিয়ে যাচাই" প্যাটার্নের পুনরাবৃত্তি, এখন কন্ট্রোল সিগন্যাল টেবিলে প্রয়োগ করা হলো। শুধু চোখে দেখে "ঠিক মনে হচ্ছে" বলার বদলে কোড নিজেই নিশ্চিত করে।

অনুশীলন

  1. চিন্তা করুন: BEQ-এর ALUSrc=0 কেন — দুটো অপারেন্ডই কি রেজিস্টার থেকে আসা উচিত, নাকি একটি immediate হওয়া উচিত?

    BEQ দুটো রেজিস্টারের মান তুলনা করে (rs1 == rs2), তাই ALU-এর দুটো ইনপুটই রেজিস্টার থেকে আসা উচিত — ALUSrc=0 ঠিক এটাই নিশ্চিত করে। offset ফিল্ডটি ALU-তে যায় না — সেটি ভিন্নভাবে (branch টার্গেট ঠিকানা গণনার জন্য) ব্যবহৃত হয়, যা এই সরলীকৃত টেবিলে আলাদা সিগন্যাল হিসেবে দেখানো হয়নি।

  2. পরীক্ষা করুন: যদি ভুলবশত STORE-এর RegWrite-ও ১ সেট করা হতো, তাহলে বাস্তব ডেটাপাথে কী সমস্যা হতে পারত?

    STORE কোনো ফলাফল রেজিস্টারে লেখার কথা না — ভুলবশত RegWrite=1 হলে ডেটাপাথ কোনো একটি (সম্ভবত অনির্দিষ্ট বা ভুল) মান রেজিস্টার ফাইলে লিখে ফেলতে পারত, একটি নীরব ডেটা-করাপশন বাগ তৈরি করে — ঠিক এই কারণেই এই পাঠের কোড সেল প্রতিটি opcode-এর প্রতিটি সিগন্যাল স্পষ্টভাবে যাচাই করে, অনুমানের উপর নির্ভর করে না।

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

আগের পাঠ
সিঙ্গেল-সাইকেল ডেটাপাথ ডিজাইন