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

মাল্টি-সাইকেল ডেটাপাথ

Multi-cycle datapath
৮ মিনিট পড়া উচ্চ · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • মাল্টি-সাইকেল ডিজাইন কীভাবে সিঙ্গেল-সাইকেলের "সবচেয়ে ধীরের জন্য সবাই অপেক্ষা করে" সমস্যা সমাধান করে
  • কেন ভিন্ন ইনস্ট্রাকশন টাইপ ভিন্ন সংখ্যক সাইকেল নেয়, এবং এর ফলে থ্রুপুট-বনাম-ল্যাটেন্সি নিয়ে একটি সূক্ষ্ম, বাস্তব সত্য
  • হার্ডওয়্যার পুনর্ব্যবহারের সুবিধা — কেন মাল্টি-সাইকেল ডিজাইন একই ALU একাধিকবার ব্যবহার করতে পারে
  • Python দিয়ে একটি সাইকেল-বাই-সাইকেল MultiCycleDatapath FSM বানিয়ে ADD ও LOAD-এর জন্য সাইকেল সংখ্যা গণনা ও তুলনা করা

১ · মাল্টি-সাইকেল ডিজাইন — L28-এর সমস্যার সমাধান

L28-এ দেখেছিলেন সিঙ্গেল-সাইকেল ডিজাইনের একটি গুরুতর সমস্যা — ক্লক সাইকেলের দৈর্ঘ্য সবচেয়ে ধীর ইনস্ট্রাকশন (LOAD) দিয়ে নির্ধারিত হয়, ফলে দ্রুত ইনস্ট্রাকশনও (ADD) সেই একই দীর্ঘ সময় "অপেক্ষা" করতে বাধ্য হয়। মাল্টি-সাইকেল ডিজাইনMulti-Cycle Designইনস্ট্রাকশন এক্সিকিউশনকে একাধিক ছোট, স্বতন্ত্র ক্লক সাইকেলে ভাগ করা। সরাসরি এই সমস্যার সমাধান করে — Fetch, Decode, Execute, Memory, Writeback — প্রতিটি ধাপ এখন নিজস্ব, অনেক ছোট ক্লক সাইকেল পায়, এবং প্রতিটি ইনস্ট্রাকশন টাইপ শুধু তার প্রয়োজনীয় ধাপগুলোই ব্যবহার করে — যেমন একটি সরল R-type ADD মেমরি-অ্যাক্সেস ধাপটি সম্পূর্ণ এড়িয়ে যেতে পারে, কিন্তু LOAD সবগুলো ধাপ ব্যবহার করে।

২ · সূক্ষ্ম সত্য — সবসময় দ্রুত নয়, তবে বেশি দক্ষ

এখানে একটি গুরুত্বপূর্ণ, বাস্তব সূক্ষ্মতা আছে (অতি-সরলীকরণ এড়িয়ে সঠিকভাবে বলা দরকার) — প্রতিটি পৃথক ইনস্ট্রাকশনের মোট ল্যাটেন্সি (সময়) সবসময় উন্নত নাও হতে পারে, কারণ এখন প্রতিটি ইনস্ট্রাকশনকে একাধিক ছোট সাইকেলের মধ্য দিয়ে যেতে হয়। আসল সুবিধাটা হলো থ্রুপুট/ক্লক-স্পিড ট্রেড-অফ — যেহেতু ক্লক সাইকেল এখন সবচেয়ে ধীর একক ধাপ-এর সমান দীর্ঘ (পুরো ইনস্ট্রাকশনের সমান নয়), ক্লকের গতি অনেক বাড়ানো যায়, আর সরল ইনস্ট্রাকশনগুলো প্রকৃতপক্ষে কম সাইকেল ব্যবহার করে সামগ্রিকভাবে দ্রুত শেষ করে।

হার্ডওয়্যার পুনর্ব্যবহার

মাল্টি-সাইকেল ডিজাইনের আরেকটি বাস্তব সুবিধা — একই ইনস্ট্রাকশনের ভিন্ন ভিন্ন সাইকেলে একই ALU একাধিকবার ব্যবহার করা যায় (যেমন LOAD-এ প্রথমে ঠিকানা গণনায়, প্রয়োজনে পরে অন্য হিসাবে) — সিঙ্গেল-সাইকেল ডিজাইনে যেহেতু সবকিছু একই সাইকেলে একসাথে ঘটতে হয়, সেখানে এই পুনর্ব্যবহার সম্ভব নয় (প্রতিটি ব্যবহারের জন্য আলাদা হার্ডওয়্যার লাগত) — একটি সরাসরি, বাস্তব হার্ডওয়্যার-খরচ সাশ্রয়।

৩ · একটি FSM হিসেবে মাল্টি-সাইকেল ডেটাপাথ

Fetch Decode Execute Writeback (ADD) Memory (LOAD) Writeback (LOAD)
ADD পথ: Fetch→Decode→Execute→Writeback (৪ সাইকেল) — Memory ধাপ এড়িয়ে যায়। LOAD পথ: সবগুলো ৫টি ধাপ ব্যবহার করে।

নিচের কোড সেলে একটি MultiCycleDatapath ক্লাস L17-এর FSM প্যাটার্ন অনুসরণ করে — প্রতিটি সাইকেলে একটি নির্দিষ্ট স্টেট এক্সিকিউট হয়, এবং ইনস্ট্রাকশন টাইপ অনুযায়ী Memory স্টেট এড়িয়ে যাওয়া বা অন্তর্ভুক্ত করা হয়। ADD ও LOAD দুটোই চালিয়ে মোট সাইকেল সংখ্যা গুনে তুলনা করা হয়েছে।

Python
# -- মাল্টি-সাইকেল ডেটাপাথ সিমুলেশন: সাইকেল-বাই-সাইকেল FSM, রেজিস্টার/মেমরি Python dict/list

class MultiCycleDatapath:
    def __init__(self, registers, data_memory):
        self.registers = registers
        self.data_memory = data_memory

    def run(self, instr):
        cycle = 0
        trace = []

        # সাইকেল ১: Fetch (সব ইনস্ট্রাকশনের জন্য প্রযোজ্য)
        cycle += 1
        trace.append((cycle, 'Fetch', f"ইনস্ট্রাকশন আনা হলো: {instr['op']}"))

        # সাইকেল ২: Decode (সব ইনস্ট্রাকশনের জন্য প্রযোজ্য)
        cycle += 1
        rs1_val = self.registers[instr['rs1']]
        detail = f"r{instr['rs1']}={rs1_val}"
        if instr['op'] == 'ADD':
            detail += f", r{instr['rs2']}={self.registers[instr['rs2']]}"
        trace.append((cycle, 'Decode', f"রেজিস্টার পড়া হলো: {detail}"))

        # সাইকেল ৩: Execute -- ALU (প্রথমবার ব্যবহার -- হার্ডওয়্যার পুনর্ব্যবহারের সুযোগ পরে LOAD-এ প্রযোজ্য হলে হতো)
        cycle += 1
        if instr['op'] == 'ADD':
            alu_result = rs1_val + self.registers[instr['rs2']]
        else:  # ADDI, LOAD
            alu_result = rs1_val + instr['imm']
        trace.append((cycle, 'Execute', f"ALU ফলাফল = {alu_result}"))

        # সাইকেল ৪ (শুধু LOAD/STORE): Memory
        mem_val = None
        if instr['op'] == 'LOAD':
            cycle += 1
            mem_val = self.data_memory.get(alu_result, 0)
            trace.append((cycle, 'Memory', f"mem[{alu_result}] পড়া হলো = {mem_val}"))
        else:
            trace.append((None, 'Memory', "(এড়িয়ে যাওয়া হলো -- এই ইনস্ট্রাকশনের দরকার নেই)"))

        # চূড়ান্ত সাইকেল: Writeback
        cycle += 1
        result = mem_val if instr['op'] == 'LOAD' else alu_result
        if instr['rd'] != 0:
            self.registers[instr['rd']] = result
        trace.append((cycle, 'Writeback', f"r{instr['rd']} = {result}"))

        return trace, cycle


registers = [0, 10, 20, 0, 0, 0, 100, 0]
data_memory = {100: 42}
cpu = MultiCycleDatapath(registers, data_memory)

print("=== ADD r3, r1, r2  (r1=10, r2=20) ===")
add_trace, add_cycles = cpu.run({'op': 'ADD', 'rd': 3, 'rs1': 1, 'rs2': 2})
for c, stage, msg in add_trace:
    label = f"সাইকেল {c}" if c is not None else "  --  "
    print(f"  [{label:^9}] {stage:<10} {msg}")
print(f"ADD মোট সাইকেল লেগেছে: {add_cycles}\n")

print("=== LOAD r5, 0(r6)  (r6=100, mem[100]=42) ===")
load_trace, load_cycles = cpu.run({'op': 'LOAD', 'rd': 5, 'rs1': 6, 'imm': 0})
for c, stage, msg in load_trace:
    label = f"সাইকেল {c}" if c is not None else "  --  "
    print(f"  [{label:^9}] {stage:<10} {msg}")
print(f"LOAD মোট সাইকেল লেগেছে: {load_cycles}\n")

print(f"চূড়ান্ত রেজিস্টার: r3={registers[3]} (প্রত্যাশিত 30), r5={registers[5]} (প্রত্যাশিত 42)")
print(f"ADD সাইকেল ({add_cycles}) < LOAD সাইকেল ({load_cycles}) ? {add_cycles < load_cycles}")

assert registers[3] == 30 and registers[5] == 42
assert add_cycles == 4 and load_cycles == 5
assert add_cycles < load_cycles
print("\nযাচাই সফল -- ADD মেমরি-ধাপ এড়িয়ে LOAD-এর চেয়ে কম সাইকেলে (৪ বনাম ৫) সঠিকভাবে শেষ হয়েছে।")

    
লক্ষ করুন ADD ৪ সাইকেলে শেষ হয় (Fetch, Decode, Execute, Writeback), কিন্তু LOAD ৫ সাইকেল নেয় (অতিরিক্ত Memory সাইকেলসহ) — এটিই মাল্টি-সাইকেল ডিজাইনের মূল প্রতিশ্রুতি বাস্তবে দেখা: প্রতিটি ইনস্ট্রাকশন ঠিক ততটুকু সময় নেয় যতটুকু তার আসলেই প্রয়োজন, সিঙ্গেল-সাইকেল ডিজাইনের মতো সবাইকে একই দীর্ঘ সাইকেলে বাধ্য না করে।
মূল কথা · Key takeaway

মাল্টি-সাইকেল ডিজাইন L28-এর সিঙ্গেল-সাইকেল অদক্ষতা কমায় — ধাপগুলোকে ছোট, পৃথক সাইকেলে ভাগ করে এবং প্রতিটি ইনস্ট্রাকশনকে শুধু প্রয়োজনীয় ধাপ ব্যবহার করতে দিয়ে। L29-L30-এর কন্ট্রোল ইউনিট এখন প্রতিটি সাইকেলে ভিন্ন সিগন্যাল সরবরাহ করে (এক-সাইকেলে-সব-সিগন্যাল নয়) — M7-এ দেখবেন কীভাবে এই একই ধাপ-বিভাজনকে আরও এগিয়ে নিয়ে একাধিক ইনস্ট্রাকশন একসাথে ওভারল্যাপ করানো যায় (পাইপলাইনিং)।

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

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

প্র ০১ মাল্টি-সাইকেল ডিজাইনে একটি ইনস্ট্রাকশনের মোট সময় কি সবসময় সিঙ্গেল-সাইকেলের চেয়ে কম হবে?

না, সবসময় নয় — এই সূক্ষ্মতাটি স্পষ্টভাবে মনে রাখা দরকার। একটি একক LOAD ইনস্ট্রাকশনের ক্ষেত্রে, পাঁচটি ছোট সাইকেলের যোগফল সিঙ্গেল-সাইকেল ডিজাইনের একটি দীর্ঘ সাইকেলের প্রায় সমানই হতে পারে। আসল সুবিধা হলো ADD-এর মতো সরল ইনস্ট্রাকশন কম সাইকেল ব্যবহার করে, এবং সামগ্রিক ক্লক-স্পিড অনেক বাড়ানো যায় — তাই মিশ্র ইনস্ট্রাকশনের একটি বাস্তব প্রোগ্রামে গড় পারফরম্যান্স উন্নত হয়, প্রতিটি একক ইনস্ট্রাকশনের ল্যাটেন্সি নয়, সামগ্রিক থ্রুপুটই আসল লাভ।

প্র ০২ কোড সেলে trace.append((None, 'Memory', ...)) লাইনটি কেন cycle-কে না বাড়িয়ে None ব্যবহার করল?

কারণ এই ধাপটি আসলে ঘটেই না — cycle ভেরিয়েবল শুধু প্রকৃতপক্ষে ব্যবহৃত সাইকেলগুলো গোনে। None ব্যবহার করে ট্রেসে স্পষ্টভাবে দেখানো হয় যে এই ধাপটি স্কিপ হয়েছে, ভুলবশত একটি অতিরিক্ত সাইকেল গোনা হয়নি — এটিই নিশ্চিত করে যে চূড়ান্ত add_cycles == 4 (৫ নয়) হিসাবটি সঠিক।

প্র ০৩ মাল্টি-সাইকেল ডিজাইনে "হার্ডওয়্যার পুনর্ব্যবহার" (যেমন একই ALU একাধিক সাইকেলে ব্যবহার) সিঙ্গেল-সাইকেল ডিজাইনে সম্ভব নয় কেন?

সিঙ্গেল-সাইকেল ডিজাইনে একটি ইনস্ট্রাকশনের সব হিসাব (যেমন ঠিকানা গণনা এবং সম্ভাব্য অন্য কোনো ALU অপারেশন) একই ক্লক সাইকেলের মধ্যে, একসাথেই ঘটতে হবে — তাই যদি একটি ইনস্ট্রাকশনের দুটো ভিন্ন ALU-হিসাব লাগে, তাহলে দুটোর জন্য দুটো আলাদা ALU সার্কিট লাগবে (যেহেতু একই সময়ে একই হার্ডওয়্যার দুই জায়গায় ব্যবহার করা যায় না)। মাল্টি-সাইকেল ডিজাইনে যেহেতু প্রতিটি হিসাব ভিন্ন সাইকেলে (ভিন্ন সময়ে) ঘটে, একই ALU দুবার ব্যবহার করা যায় — হার্ডওয়্যার খরচ বাঁচে।

অনুশীলন

  1. চিন্তা করুন: একটি STORE ইনস্ট্রাকশন এই সিমুলেটরের ডিজাইন অনুযায়ী কয় সাইকেলে শেষ হবে, এবং কোন ধাপগুলো সক্রিয় থাকবে?

    STORE-এরও LOAD-এর মতোই Memory ধাপ লাগবে (মেমরিতে লিখতে), কিন্তু Writeback ধাপ লাগবে না (কোনো রেজিস্টারে ফলাফল লেখে না) — তাই মোট সাইকেল হবে ৪: Fetch, Decode, Execute (ঠিকানা গণনা), Memory (লেখা) — LOAD-এর সমান সংখ্যক সাইকেল হলেও ভিন্ন ধাপ-সমন্বয়ে।

  2. পরীক্ষা করুন: কোড সেলে assert add_cycles == 4 and load_cycles == 5 লাইনটি বাদ দিলে প্রোগ্রামের আউটপুটে কোনো দৃশ্যমান পরিবর্তন হতো কি?

    না — সঠিক ইনপুটে print স্টেটমেন্টগুলো একই আউটপুট দিত, কারণ assert শুধু তখনই দৃশ্যমান প্রভাব ফেলে যখন শর্তটি মিথ্যা হয় (তখন প্রোগ্রাম থেমে একটি এরর দেখায়)। এটি ভবিষ্যতে কোড পরিবর্তনের সময় একটি "সেফটি নেট" হিসেবে কাজ করে — যদি কেউ ভুলবশত সাইকেল-গণনার লজিক বদলে ফেলে, assert সাথে সাথে সেই ভুল ধরিয়ে দেবে।

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

আগের পাঠ
মাইক্রোপ্রোগ্রামড কন্ট্রোল