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

চূড়ান্ত প্রকল্প — একটি সিম্পল CPU সিমুলেটর বানানো

Capstone — building a simple CPU simulator
১৮ মিনিট পড়া চূড়ান্ত প্রকল্প · Capstone Python কোডসহ সম্পূর্ণ বাংলায়

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

  • কোর্সের M1-M12-এর মূল উপাদানগুলো কীভাবে একটি একক, সুসংগত সিমুলেটরে একসাথে কাজ করে তা প্রত্যক্ষ করা
  • একটি বাস্তব প্রোগ্রামের ফেচ-ডিকোড-এক্সিকিউট-মেমরি-রাইটব্যাক ট্রেস ধাপে ধাপে অনুসরণ করা
  • একটি বাস্তব প্রোগ্রামে ক্যাশ হিট ও মিস ঠিক কখন ঘটে তা প্রত্যক্ষ করা
  • প্রকৃত এক্সিকিউশন থেকে একটি genuine CPI কম্পিউট করা ও তার অর্থ বোঝা

১ · এই ক্যাপস্টোন কী — নতুন তত্ত্ব নয়, সংশ্লেষণ

এতদূর আসার পথে আপনি ধাপে ধাপে ডিজিটাল লজিকের গেট থেকে শুরু করে একটি সম্পূর্ণ CPU-এর প্রতিটি অংশ আলাদা আলাদাভাবে শিখেছেন। এই শেষ পাঠে কোনো নতুন ধারণা নেই — বরং সেই সব টুকরো একত্র করে একটি সত্যিকার, চলমান SimpleCPU ক্লাস বানানো হবে, যা একটি বাস্তব প্রোগ্রাম চালিয়ে সঠিক ফলাফল দেবে।

২ · উপাদানগুলো — কোন অংশ কোন পাঠ থেকে এসেছে

রেজিস্টার ফাইল · M5/L26
সাধারণ-উদ্দেশ্য রেজিস্টার + PC (প্রোগ্রাম কাউন্টার) + IR (ইনস্ট্রাকশন রেজিস্টার) + flags — r0 সবসময় ০ পড়ে, ঠিক বাস্তব RISC ISA-র মতো।
মেমরি + ক্যাশ · M8/L38
ইনস্ট্রাকশন মেমরি ও ডেটা মেমরি; ডেটা মেমরির সামনে একটি ডিরেক্ট-ম্যাপড ক্যাশ, যা প্রতিটি অ্যাক্সেসে হিট/মিস ট্র্যাক করে।
ALU · M2/L09, M4/L19
অ্যাডার/সাবট্রাক্টর লজিকের ওপর ভিত্তি করে ADD, SUB ও কম্পেয়ার (CMP) অপারেশন সমর্থন করে।
কন্ট্রোল লুপ · M6/L31
মাল্টি-সাইকেল-স্টাইল FSM — FETCH → DECODE → EXECUTE → (MEMORY) → (WRITEBACK), ইনস্ট্রাকশন-টাইপ অনুযায়ী অপ্রয়োজনীয় স্টেজ বাদ দিয়ে।
পারফরম্যান্স ট্র্যাকিং · M12/L54
মোট সাইকেল ও মোট ইনস্ট্রাকশন গুনে প্রকৃত, কম্পিউট-করা CPI বের করা।
FETCH DECODE EXECUTE MEMORY (শুধু LOAD/STORE) WRITEBACK (শুধু রেজিস্টার-রাইট)
M6/L31-এর মাল্টি-সাইকেল ডিজাইনের রিক্যাপ — প্রতিটি ইনস্ট্রাকশন শুধু তার প্রয়োজনীয় স্টেজ দিয়ে যায়; ADD/ADDI MEMORY বাদ দেয়, BEQ MEMORY ও WRITEBACK দুটোই বাদ দেয়।

৩ · হাতে-অ্যাসেম্বল করা প্রোগ্রাম — যোগফল ১+২+৩+৪+৫

M5/L27-এর অ্যাসেম্বলি ভাষার ধারণা পুনরায় ব্যবহার করে, নিচের প্রোগ্রামটি হাতে "অ্যাসেম্বল" করা হয়েছে — একটি লুপের মাধ্যমে ১ থেকে ৫ পর্যন্ত যোগ করে, ফলাফল মেমরিতে সংরক্ষণ করে, তারপর যাচাইয়ের জন্য আবার লোড করে:

Assembly (hand-assembled)
PC   ইনস্ট্রাকশন                 মন্তব্য
--   ------------------------    -----------------------------
 0   ADDI  r1, r0, 0             i = 0
 1   ADDI  r2, r0, 0             sum = 0
 2   ADDI  r3, r0, 5             limit = 5
 3   ADDI  r4, r0, 1             const_one = 1
 4   ADD   r1, r1, r4      <-- LOOP: i = i + 1
 5   ADD   r2, r2, r1             sum = sum + i
 6   BEQ   r1, r3, 1              i == limit হলে PC=8 (এক্সিটে যাও)
 7   BEQ   r0, r0, -4             নাহলে PC=4 (LOOP-এ ফিরে যাও, অ্যানকন্ডিশনাল জাম্প)
 8   STORE r2, 0(r0)              mem[0] = sum
 9   LOAD  r5, 0(r0)              r5 = mem[0]  (যাচাইয়ের জন্য আবার লোড)
10   HALT

লক্ষ্য করুন লাইন ৭-এ BEQ r0, r0, -4 একটি "অ্যানকন্ডিশনাল জাম্প" হিসেবে ব্যবহৃত হয়েছে — যেহেতু r0 সবসময় r0-এর সমান (M5/L26-এর হার্ডওয়্যার্ড-জিরো রেজিস্টার), এই BEQ সবসময় ট্রু হয়, ঠিক একটি জাম্প ইনস্ট্রাকশনের মতো আচরণ করে — আলাদা কোনো JUMP ইনস্ট্রাকশন ছাড়াই। এই একই কৌশল বাস্তব RISC ISA-তেও ব্যবহৃত হয়।

৪ · সম্পূর্ণ সিমুলেটর — চালিয়ে দেখুন

নিচের কোড সেলে সম্পূর্ণ SimpleCPU ক্লাস (এবং তার সহায়ক DirectMappedCache, ALU, RegisterFile ক্লাস) সংজ্ঞায়িত করে, উপরের প্রোগ্রামটি চালিয়ে একটি সম্পূর্ণ এক্সিকিউশন ট্রেস প্রিন্ট করা হবে।

Python
class DirectMappedCache:
    """M8/L38 -- ডেটা মেমরির সামনে বসানো ডিরেক্ট-ম্যাপড ক্যাশ, হিট/মিস ট্র্যাক করে"""
    def __init__(self, num_lines=4):
        self.num_lines = num_lines
        self.lines = [{"valid": False, "tag": None, "data": 0} for _ in range(num_lines)]
        self.hits = 0
        self.misses = 0

    def _index_tag(self, address):
        return address % self.num_lines, address // self.num_lines

    def read(self, address, data_memory):
        index, tag = self._index_tag(address)
        line = self.lines[index]
        if line["valid"] and line["tag"] == tag:
            self.hits += 1
            return line["data"], "HIT"
        self.misses += 1
        value = data_memory.get(address, 0)
        line.update(valid=True, tag=tag, data=value)
        return value, "MISS"

    def write(self, address, value, data_memory):
        index, tag = self._index_tag(address)
        line = self.lines[index]
        status = "HIT" if (line["valid"] and line["tag"] == tag) else "MISS"
        if status == "HIT":
            self.hits += 1
        else:
            self.misses += 1
        line.update(valid=True, tag=tag, data=value)   # write-through: ক্যাশ + মেমরি দুটোই আপডেট
        data_memory[address] = value
        return status


class ALU:
    """M2/L09 + M4/L19 -- অ্যাডার/সাবট্রাক্টর-ভিত্তিক ALU, তুলনাসহ"""
    @staticmethod
    def execute(op, a, b):
        if op == "ADD":
            result = a + b
        elif op == "SUB":
            result = a - b
        elif op == "CMP":
            result = a - b
        else:
            raise ValueError(f"অজানা ALU অপারেশন: {op}")
        flags = {"zero": result == 0, "negative": result < 0}
        return result, flags


class RegisterFile:
    """M5/L26 -- সাধারণ-উদ্দেশ্য রেজিস্টার, r0 সবসময় ০, প্লাস PC/IR/flags"""
    def __init__(self, n=8):
        self.regs = [0] * n
        self.pc = 0
        self.ir = None
        self.flags = {"zero": False, "negative": False}

    def read(self, i):
        return 0 if i == 0 else self.regs[i]

    def write(self, i, value):
        if i != 0:
            self.regs[i] = value


class SimpleCPU:
    """M6/L31 -- মাল্টি-সাইকেল-স্টাইল ফেচ-ডিকোড-এক্সিকিউট-মেমরি-রাইটব্যাক কন্ট্রোল লুপ"""

    STAGE_CYCLES = {
        "ADD":   ["FETCH", "DECODE", "EXECUTE", "WRITEBACK"],
        "SUB":   ["FETCH", "DECODE", "EXECUTE", "WRITEBACK"],
        "ADDI":  ["FETCH", "DECODE", "EXECUTE", "WRITEBACK"],
        "LOAD":  ["FETCH", "DECODE", "EXECUTE", "MEMORY", "WRITEBACK"],
        "STORE": ["FETCH", "DECODE", "EXECUTE", "MEMORY"],
        "BEQ":   ["FETCH", "DECODE", "EXECUTE"],
        "HALT":  ["FETCH", "DECODE"],
    }

    def __init__(self, instruction_memory, cache_lines=4):
        self.imem = instruction_memory
        self.dmem = {}
        self.cache = DirectMappedCache(num_lines=cache_lines)
        self.rf = RegisterFile()
        self.alu = ALU()
        self.total_cycles = 0
        self.instructions_executed = 0
        self.trace = []
        self.halted = False

    def step(self):
        if self.halted:
            return False
        pc_before = self.rf.pc
        instr = self.imem[pc_before]
        op = instr["op"]
        stages = self.STAGE_CYCLES[op]
        rec = {"pc": pc_before, "instr": instr, "stages": ["FETCH", "DECODE"], "mem_status": None}

        self.rf.ir = instr                 # FETCH: ইনস্ট্রাকশন রেজিস্টারে লোড
        pc_next = pc_before + 1

        if op == "HALT":                   # DECODE-এই থেমে যায়
            self.halted = True
            self.total_cycles += len(stages)
            self.instructions_executed += 1
            self.trace.append(rec)
            return False

        rec["stages"].append("EXECUTE")
        result, branch_taken = None, False
        if op in ("ADD", "SUB"):
            a, b = self.rf.read(instr["rs1"]), self.rf.read(instr["rs2"])
            result, self.rf.flags = self.alu.execute(op, a, b)
        elif op == "ADDI":
            a = self.rf.read(instr["rs1"])
            result, self.rf.flags = self.alu.execute("ADD", a, instr["imm"])
        elif op in ("LOAD", "STORE"):
            rec["addr"] = self.rf.read(instr["rs1"]) + instr["offset"]
        elif op == "BEQ":
            a, b = self.rf.read(instr["rs1"]), self.rf.read(instr["rs2"])
            _, cmp_flags = self.alu.execute("CMP", a, b)
            branch_taken = cmp_flags["zero"]
            if branch_taken:
                pc_next = pc_before + 1 + instr["offset"]

        if "MEMORY" in stages:
            rec["stages"].append("MEMORY")
            if op == "LOAD":
                result, rec["mem_status"] = self.cache.read(rec["addr"], self.dmem)
            elif op == "STORE":
                rec["mem_status"] = self.cache.write(rec["addr"], self.rf.read(instr["rs2"]), self.dmem)

        if "WRITEBACK" in stages:
            rec["stages"].append("WRITEBACK")
            self.rf.write(instr["rd"], result)

        self.total_cycles += len(stages)
        self.instructions_executed += 1
        self.rf.pc = pc_next
        rec["branch_taken"] = branch_taken
        self.trace.append(rec)
        return True

    def run(self, max_steps=200):
        steps = 0
        while not self.halted and steps < max_steps:
            self.step()
            steps += 1

    @property
    def cpi(self):
        return self.total_cycles / self.instructions_executed if self.instructions_executed else 0


# --- হাতে-অ্যাসেম্বল করা প্রোগ্রাম: ১ থেকে ৫ পর্যন্ত যোগফল ---
program = [
    {"op": "ADDI",  "rd": 1, "rs1": 0, "imm": 0},        # 0: i = 0
    {"op": "ADDI",  "rd": 2, "rs1": 0, "imm": 0},        # 1: sum = 0
    {"op": "ADDI",  "rd": 3, "rs1": 0, "imm": 5},        # 2: limit = 5
    {"op": "ADDI",  "rd": 4, "rs1": 0, "imm": 1},        # 3: const_one = 1
    {"op": "ADD",   "rd": 1, "rs1": 1, "rs2": 4},        # 4: LOOP: i = i + 1
    {"op": "ADD",   "rd": 2, "rs1": 2, "rs2": 1},        # 5: sum = sum + i
    {"op": "BEQ",   "rs1": 1, "rs2": 3, "offset": 1},    # 6: i == limit -> PC 8
    {"op": "BEQ",   "rs1": 0, "rs2": 0, "offset": -4},   # 7: অ্যানকন্ডিশনাল জাম্প -> PC 4
    {"op": "STORE", "rs2": 2, "rs1": 0, "offset": 0},    # 8: mem[0] = sum
    {"op": "LOAD",  "rd": 5, "rs1": 0, "offset": 0},     # 9: r5 = mem[0]
    {"op": "HALT"},                                       # 10
]

cpu = SimpleCPU(program)
cpu.run()

print("=== এক্সিকিউশন ট্রেস ===")
for rec in cpu.trace:
    mem = f"  [ক্যাশ: {rec['mem_status']}]" if rec["mem_status"] else ""
    print(f"PC={rec['pc']:>2}  {rec['instr']['op']:<6}  {'>'.join(rec['stages']):<40}{mem}")

print()
print("চূড়ান্ত রেজিস্টার:", cpu.rf.regs)
print("r2 (sum)                       =", cpu.rf.read(2))
print("r5 (মেমরি থেকে রি-লোড করা sum)  =", cpu.rf.read(5))
print("ডেটা মেমরি:", cpu.dmem)
print()
print(f"ক্যাশ -- হিট: {cpu.cache.hits}, মিস: {cpu.cache.misses}")
print(f"মোট সাইকেল: {cpu.total_cycles}")
print(f"এক্সিকিউট হওয়া ইনস্ট্রাকশন: {cpu.instructions_executed}")
print(f"CPI = {cpu.total_cycles} / {cpu.instructions_executed} = {cpu.cpi:.4f}")

assert cpu.rf.read(2) == 15, "যোগফল ভুল!"
assert cpu.rf.read(5) == 15, "রি-লোড করা মান ভুল!"
assert cpu.cache.misses == 1 and cpu.cache.hits == 1, "ক্যাশ হিট/মিস প্রত্যাশিত মতো নয়!"
print()
print("সব যাচাই সফল -- ১+২+৩+৪+৫ = ১৫, সঠিকভাবে কম্পিউট হয়েছে।")

    

৫ · ফলাফল ব্যাখ্যা

ট্রেস লক্ষ্য করলে দেখবেন PC ৪-৭ পাঁচবার পুনরাবৃত্তি হয় (লুপের ৫টি ইটারেশন), তারপর PC ৮-এ পৌঁছে STORE চালানো হয় — এটাই প্রথম ডেটা-মেমরি অ্যাক্সেস, তাই ক্যাশে এখনো কিছু নেই এবং এটি একটি MISS হিসেবে রেকর্ড হয়। ঠিক পরের ইনস্ট্রাকশন LOAD একই অ্যাড্রেস (০) থেকে পড়ে — যেহেতু STORE ইতিমধ্যে সেই লাইনটি ক্যাশে বসিয়ে দিয়েছে (write-through নীতিতে), এই অ্যাক্সেসটি একটি HIT। চূড়ান্ত CPI ১-এর চেয়ে উল্লেখযোগ্যভাবে বেশি — কারণ এটি M7-এর পাইপলাইনড ডিজাইন নয়, বরং M6/L31-এর মাল্টি-সাইকেল ডিজাইন, যেখানে প্রতিটি ইনস্ট্রাকশনের প্রতিটি স্টেজ সিরিয়ালিভাবে, একটির পর একটি ঘটে — কোনো ওভারল্যাপ ছাড়াই।

লক্ষ্য করুন ADD/ADDI/BEQ ইনস্ট্রাকশনগুলো MEMORY স্টেজ সম্পূর্ণ এড়িয়ে যায় — এটাই M6/L31-এর মাল্টি-সাইকেল ডিজাইনের মূল দক্ষতা: প্রতিটি ইনস্ট্রাকশন শুধু তার প্রয়োজনীয় স্টেজ দিয়েই যায়, M6/L28-এর সিঙ্গেল-সাইকেল ডিজাইনের মতো সবাইকে জোর করে সবচেয়ে ধীর ইনস্ট্রাকশনের সমান দীর্ঘ সময় নিতে হয় না।
মূল কথা · Key takeaway

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

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

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

প্র ০১ কেন এই সিমুলেটরের ক্যাশ (M8) একটি লুপযুক্ত প্রোগ্রামের জন্য গুরুত্বপূর্ণ — লুপ কী ধরনের অ্যাক্সেস প্যাটার্ন তৈরি করে, আর সেটা M8/L37-এর locality-of-reference ধারণার সাথে কীভাবে যুক্ত?

একটি লুপ বারবার একই, ছোট্ট একগুচ্ছ ইনস্ট্রাকশন ও ডেটা অ্যাক্সেস করে — এই প্রোগ্রামে PC ৪-৭ পাঁচবার পুনরাবৃত্তি হয়, এটাই M8/L37-এর টেম্পোরাল লোকালিটি-র ক্লাসিক উদাহরণ। আমাদের এই টয় সিমুলেটরের ক্যাশ শুধু ডেটা মেমরির সামনে বসানো (ইনস্ট্রাকশন মেমরির সামনে নয়), তাই লুপ-বডির বারবার ফেচ হওয়া ইনস্ট্রাকশনগুলো এই সিমুলেশনে ক্যাশ হয় না — কিন্তু ঠিক এই একই বারবার-অ্যাক্সেসের প্যাটার্নই বাস্তব CPU-তে ইনস্ট্রাকশন ক্যাশিং-কে এত কার্যকর করে তোলে। আমাদের ডেটা-সাইডে দেখা একমাত্র MISS-তারপর-HIT প্যাটার্নও একই নীতির ছোট্ট, concrete প্রমাণ।

প্র ০২ চূড়ান্ত CPI ১-এর তুলনায় বেশ বেশি (একের চেয়ে অনেক বড়) কেন হলো, অথচ M7-এর পাইপলাইনিং তত্ত্ব অনুযায়ী CPI ১-এর কাছাকাছি হওয়ার কথা?

কারণ এই সিমুলেটর M7-এর পাইপলাইনড ডিজাইন নয় — এটি M6/L31-এর মাল্টি-সাইকেল ডিজাইন, যেখানে একটি ইনস্ট্রাকশনের সব স্টেজ (FETCH→DECODE→EXECUTE→...) সম্পূর্ণ শেষ হওয়ার পরই পরের ইনস্ট্রাকশন শুরু হয় — কোনো ওভারল্যাপ নেই। পাইপলাইনিং-এ ভিন্ন ইনস্ট্রাকশনের ভিন্ন স্টেজ একই সাইকেলে সমান্তরালে চলে, যা CPI-কে ১-এর কাছাকাছি নামিয়ে আনে। এই দুটো সম্পূর্ণ ভিন্ন আর্কিটেকচারাল কৌশল — মাল্টি-সাইকেল ডিজাইন একক-ইনস্ট্রাকশন দক্ষতা বাড়ায় (M6/L28-এর তুলনায়), কিন্তু পাইপলাইনিং-এর মতো ইনস্ট্রাকশন-ওভারল্যাপ প্রদান করে না।

প্র ০৩ যদি ADD/ADDI-এর জন্যও MEMORY স্টেজ বাদ না দিয়ে সবসময় চালানো হতো, CPI-তে কী প্রভাব পড়ত?

এই প্রোগ্রামে ADD ৯ বার (৫ বার লুপে i=i+1-এর জন্য, ৫ বার sum=sum+i-এর জন্য) ও ADDI ৪ বার চলে — মোট ১৩টি ইনস্ট্রাকশন প্রতিটিতে একটি করে অতিরিক্ত অপ্রয়োজনীয় সাইকেল যোগ হতো, অর্থাৎ মোট সাইকেল ১৩ বেড়ে যেত, ইনস্ট্রাকশন-সংখ্যা একই থেকে যেত — তাই CPI আরও বেড়ে যেত। এটাই ঠিক দেখায় কেন M6/L31-এর "প্রতিটি ইনস্ট্রাকশন শুধু প্রয়োজনীয় স্টেজ দিয়ে যায়" নীতিটা genuinely কার্যকর — অপ্রয়োজনীয় স্টেজ বাদ দেওয়াই মাল্টি-সাইকেল ডিজাইনের মূল দক্ষতার উৎস।

অনুশীলন

  1. চিন্তা করুন: যদি এই প্রোগ্রামে limit ৫-এর বদলে ১০ হতো (১ থেকে ১০ পর্যন্ত যোগফল), এক্সিকিউশন ট্রেসে কী কী পরিবর্তন হতো — instructions_executed, total_cycles, এবং ক্যাশ হিট/মিস সংখ্যায়?

    লুপ-বডি (PC ৪-৭, ৪টি ইনস্ট্রাকশন) ৫ বারের বদলে ১০ বার চলত — অর্থাৎ instructions_executed ও total_cycles দুটোই লুপ-অংশের জন্য প্রায় দ্বিগুণ বেড়ে যেত। কিন্তু ক্যাশ হিট/মিস সংখ্যা একই থাকত — কারণ STORE ও LOAD এখনো ঠিক একই একটি অ্যাড্রেসে (মেমরি অ্যাড্রেস ০) মাত্র একবার করেই ঘটে, লুপ-ইটারেশন সংখ্যা নির্বিশেষে। এটাই দেখায় ক্যাশ-আচরণ নির্ভর করে কোন অ্যাড্রেস কতবার অ্যাক্সেস হয় তার ওপর, শুধু মোট ইনস্ট্রাকশন সংখ্যার ওপর নয়।

  2. পরীক্ষা করুন (কোড পরিবর্তন করবেন না): এই প্রোগ্রামে ছোট ক্যাশ (মাত্র ৪টি লাইন) থাকা সত্ত্বেও L38-এর মতো কোনো কনফ্লিক্ট-মিস দেখা যায়নি কেন?

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

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

  • কোর্সের সম্পূর্ণ সিলেবাস আবার দেখুন ৫৭টি পাঠ গেট থেকে CPU পর্যন্ত পুরো যাত্রাটা আরেকবার দেখে নিন — প্রতিটি টুকরো কীভাবে একসাথে ফিট করে।
  • Operating Systems কোর্স সহোদর কোর্স এই কোর্স যে হার্ডওয়্যার বানায়, সেই হার্ডওয়্যার সফটওয়্যার-স্তরে কীভাবে ব্যবস্থাপনা করা হয় — এই SimpleCPU-এর ওপরই ঠিক সেই OS-স্তর দাঁড়িয়ে আছে।
  • Computer Networks কোর্স সঙ্গী কোর্স একটি মেশিনের ভেতরের হার্ডওয়্যার এই কোর্সে, আর মেশিনে-মেশিনে যোগাযোগের হার্ডওয়্যার/প্রোটোকল সেই কোর্সে — দুটো মিলিয়েই সম্পূর্ণ কম্পিউটিং হার্ডওয়্যার-চিত্র।
আগের পাঠ
কেস স্টাডি: মুরস ল ও আর্কিটেকচারের ভবিষ্যৎ