পাঠ ০৬ · ৫৭-এর মধ্যে · মডিউল ২
Home / Courses / Microprocessors, Embedded Systems & IoT / মেমরি ম্যাপ

মেমরি ম্যাপ — Flash, SRAM ও পেরিফেরাল রেজিস্টার

The memory map: Flash, SRAM & peripheral registers
৯ মিনিট পড়া মধ্যম · Intermediate Python সিমুলেশনসহ সম্পূর্ণ বাংলায়

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

  • মাইক্রোকন্ট্রোলারের একক অ্যাড্রেস স্পেসে Flash, SRAM ও পেরিফেরাল রেজিস্টার কীভাবে সহাবস্থান করে
  • মেমরি-ম্যাপড I/O ধারণা — কেন একটি রেজিস্টারে রাইট করা মানে হার্ডওয়্যার সরাসরি বদলে যাওয়া
  • Flash কেন রিড-অনলি, আর এটি সাধারণ রাইটেবল মেমরির থেকে কীভাবে আলাদা আচরণ করে
  • একটি সত্যিকারের, চলমান অ্যাড্রেস-ডিকোডিং সিমুলেশন — রেঞ্জ অনুযায়ী read()/write() ডিসপ্যাচ

১ · কেন একক অ্যাড্রেস স্পেস

Computer Architecture কোর্সের সাথে সম্পর্ক

RAM/ROM-এর প্রযুক্তিগত বিস্তারিত এবং পোর্ট-ম্যাপড বনাম মেমরি-ম্যাপড I/O-এর সাধারণ ধারণা Computer Architecture & Digital Logic কোর্সে কভার করা হয়েছে। এই পাঠ সেই সাধারণ ধারণাকে সরাসরি একটি বাস্তব মাইক্রোকন্ট্রোলারের অ্যাড্রেস ম্যাপে প্রয়োগ করে দেখায়।

একটি মাইক্রোকন্ট্রোলারের CPU যখন কোনো ঠিকানায় (address) রিড বা রাইট করে, তখন সেই ঠিকানাটি আসলে কোথায় "পড়ে" তা নির্ভর করে চিপ প্রস্তুতকারকের সংজ্ঞায়িত মেমরি ম্যাপে। এই ম্যাপে একই ধরনের ঠিকানা-ফরম্যাটে (যেমন 0x0000 থেকে 0xFFFF) কয়েকটি ভিন্ন "রিজিয়ন" থাকে:

  • Flash — প্রোগ্রাম কোড সংরক্ষিত থাকে; রানটাইমে সাধারণত রিড-অনলি (নতুন ফার্মওয়্যার ফ্ল্যাশ করা হয় একটি আলাদা প্রোগ্রামিং প্রক্রিয়ায়, সাধারণ রাইট ইনস্ট্রাকশন দিয়ে নয়)।
  • SRAM — চলমান প্রোগ্রামের ভ্যারিয়েবল, স্ট্যাক, হিপ ডেটা থাকে; সম্পূর্ণ রিড/রাইট।
  • পেরিফেরাল রেজিস্টার — GPIO, টাইমার, ADC-র মতো হার্ডওয়্যার ব্লকের কন্ট্রোল/স্ট্যাটাস রেজিস্টার; এখানে রাইট করলে মেমরিতে একটি সংখ্যা জমা থাকে না, বরং সত্যিকারের হার্ডওয়্যার আচরণ পরিবর্তিত হয় (যেমন একটি পিন HIGH হয়ে যাওয়া) — একেই বলে মেমরি-ম্যাপড I/O।
Flash 0x0000–0x7FFF (প্রোগ্রাম, রিড-অনলি) SRAM 0x8000–0xBFFF (ডেটা, রিড/রাইট) GPIO 0xC000–0xC00F টাইমার 0xC010–0xC01F মেমরি-ম্যাপড I/O (রাইট করলেই হার্ডওয়্যার বদলায়)
একই অ্যাড্রেস স্পেসে Flash, SRAM ও পেরিফেরাল রেজিস্টার পাশাপাশি ম্যাপ করা — CPU-র কাছে সবই "একটি ঠিকানায় রিড/রাইট", কিন্তু প্রতিটি রিজিয়নের প্রকৃত আচরণ ভিন্ন।

২ · একটি সত্যিকারের সিমুলেশন — অ্যাড্রেস ডিকোডিং

নিচে একটি MemoryBus ক্লাস সিমুলেট করা হয়েছে যা উপরের ডায়াগ্রামের ঠিকানা রেঞ্জগুলো ব্যবহার করে। প্রতিটি read()/write() কল প্রথমে ঠিকানাটি কোন রিজিয়নে পড়ে তা ডিকোড করে, তারপর সেই রিজিয়ন অনুযায়ী ভিন্ন আচরণ করে — Flash-এ রাইট করলে জেনুইন এরর, আর পেরিফেরাল রিজিয়নে রাইট করলে একটি সত্যিকারের সাইড-ইফেক্ট লগ।

Python
class MemoryBus:
    # মাইক্রোকন্ট্রোলারের একক অ্যাড্রেস স্পেসের সিমুলেশন -- Flash, SRAM, পেরিফেরাল সব একসাথে
    def __init__(self):
        self.memory_map = [
            (0x0000, 0x7FFF, "FLASH", "প্রোগ্রাম Flash"),
            (0x8000, 0xBFFF, "SRAM", "ডেটা SRAM"),
            (0xC000, 0xC00F, "PERIPHERAL", "GPIO রেজিস্টার ব্লক"),
            (0xC010, 0xC01F, "PERIPHERAL", "টাইমার রেজিস্টার ব্লক"),
        ]
        self.flash_data = {0x0010: 0xAB}  # সিমুলেটেড প্রি-লোডেড ফার্মওয়্যার বাইট
        self.sram_data = {}               # রানটাইম ডেটা ও পেরিফেরাল স্টেটের জন্য ফাঁকা ডিকশনারি
        self.peripheral_log = []          # পেরিফেরাল রাইটের সাইড-ইফেক্ট লগ

    def _decode(self, addr):
        for start, end, region_type, name in self.memory_map:
            if start <= addr <= end:
                return region_type, name
        return None, None

    def read(self, addr):
        region_type, name = self._decode(addr)
        if region_type is None:
            raise ValueError(f"ঠিকানা 0x{addr:04X} মেমরি ম্যাপের বাইরে -- আনম্যাপড অ্যাক্সেস")
        if region_type == "FLASH":
            value = self.flash_data.get(addr, 0x00)
            print(f"READ  0x{addr:04X} [{name}] -> {value:#04x}")
            return value
        value = self.sram_data.get(addr, 0x00)
        tag = " (পেরিফেরাল স্ট্যাটাস রিড)" if region_type == "PERIPHERAL" else ""
        print(f"READ  0x{addr:04X} [{name}] -> {value:#04x}{tag}")
        return value

    def write(self, addr, value):
        region_type, name = self._decode(addr)
        if region_type is None:
            raise ValueError(f"ঠিকানা 0x{addr:04X} মেমরি ম্যাপের বাইরে -- আনম্যাপড অ্যাক্সেস")
        if region_type == "FLASH":
            raise PermissionError(f"0x{addr:04X} [{name}] Flash-এ সরাসরি রাইট করা যায় না -- রিড-অনলি রিজিয়ন")
        self.sram_data[addr] = value & 0xFF
        if region_type == "SRAM":
            print(f"WRITE 0x{addr:04X} [{name}] <- {value:#04x} (সাধারণ ডেটা মেমরি রাইট)")
        else:
            self.peripheral_log.append((addr, value & 0xFF))
            print(f"WRITE 0x{addr:04X} [{name}] <- {value:#04x} -> পেরিফেরাল সাইড-ইফেক্ট: হার্ডওয়্যার স্টেট পরিবর্তিত হলো!")


bus = MemoryBus()

print("প্রি-লোডেড Flash থেকে রিড:")
bus.read(0x0010)

print("\nSRAM-এ ডেটা রাইট ও রিড:")
bus.write(0x8000, 0x2A)
bus.read(0x8000)

print("\nGPIO পেরিফেরাল রেজিস্টারে রাইট (পিন সেট করার সিমুলেশন):")
bus.write(0xC000, 0x01)

print("\nটাইমার পেরিফেরাল রেজিস্টারে রাইট (রিলোড ভ্যালু সেট):")
bus.write(0xC010, 0xFF)

print(f"\nমোট পেরিফেরাল সাইড-ইফেক্ট লগ: {[(hex(a), hex(v)) for a, v in bus.peripheral_log]}")

print("\nFlash-এ রাইট করার চেষ্টা (এটি ব্যর্থ হওয়া উচিত):")
try:
    bus.write(0x0010, 0x99)
except PermissionError as e:
    print(f"ত্রুটি ধরা পড়ল -> {e}")

print("\nআনম্যাপড ঠিকানায় (0xFFFF) রিড করার চেষ্টা:")
try:
    bus.read(0xFFFF)
except ValueError as e:
    print(f"ত্রুটি ধরা পড়ল -> {e}")

    
লক্ষ্য করুন — bus.write(0xC000, 0x01) এবং bus.sram_data-তে মান জমা রাখাটা শুধু "মেমরিতে সংখ্যা লেখা" মনে হলেও, প্রিন্ট আউটপুট স্পষ্ট করে দেয় যে এটি আসলে একটি পেরিফেরাল সাইড-ইফেক্ট — বাস্তব হার্ডওয়্যারে এই একই রাইট অপারেশন সত্যিই একটি GPIO পিনকে HIGH করে দিত। অন্যদিকে Flash-এ রাইট করার চেষ্টা PermissionError দিয়ে সরাসরি প্রত্যাখ্যাত হয় — ঠিক যেভাবে বাস্তব হার্ডওয়্যারও রানটাইমে সাধারণ রাইট ইনস্ট্রাকশন দিয়ে Flash বদলাতে দেয় না।
মূল কথা · Key takeaway

একটি মাইক্রোকন্ট্রোলারের সব মেমরি ও হার্ডওয়্যার একই অ্যাড্রেস স্পেসে বাস করে, কিন্তু ঠিকানা কোন রেঞ্জে পড়ে তার উপর নির্ভর করে সম্পূর্ণ ভিন্ন আচরণ ঘটে — এই "অ্যাড্রেস ডিকোডিং" ধারণাটিই M4-M5-এর GPIO/টাইমার/ADC পেরিফেরাল প্রোগ্রামিং এবং L11-এর পয়েন্টার-ভিত্তিক রেজিস্টার অ্যাক্সেসের ভিত্তি।

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

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

প্র ০১ একটি পেরিফেরাল রেজিস্টারে "রিড" করা কেন সবসময় নিরাপদ নাও হতে পারে, যদিও রিড সাধারণত "কিছু বদলায় না" বলে মনে হয়?

কিছু পেরিফেরাল রেজিস্টার (যেমন একটি UART-এর "রিসিভ বাফার" বা একটি স্ট্যাটাস ফ্ল্যাগ) রিড করলেই হার্ডওয়্যার সাইড-ইফেক্ট ঘটে — যেমন একবার রিড করলে ফ্ল্যাগটি স্বয়ংক্রিয়ভাবে ক্লিয়ার হয়ে যায় (একে বলা হয় "read-to-clear" আচরণ), অথবা পরবর্তী বাইট বাফারে শিফট হয়ে আসে। তাই মেমরি-ম্যাপড I/O-তে রিড অপারেশনকেও সাধারণ ভ্যারিয়েবল রিডের মতো "সাইড-ইফেক্ট-মুক্ত" ধরে নেওয়া ভুল।

প্র ০২ উপরের কোড সেলে GPIO ও টাইমারকে দুটো আলাদা ঠিকানা রেঞ্জ (0xC000-0xC00F ও 0xC010-0xC01F) দেওয়া হয়েছে কেন একটি একক বড় "PERIPHERAL" রেঞ্জ না দিয়ে?

বাস্তব চিপেও প্রতিটি পেরিফেরাল ব্লকের নিজস্ব, নির্দিষ্ট ঠিকানা রেঞ্জ থাকে — কারণ প্রতিটি পেরিফেরালের নিজস্ব কন্ট্রোল/স্ট্যাটাস/ডেটা রেজিস্টারের আলাদা সেট থাকে, এবং হার্ডওয়্যার ডিকোডার লজিককে জানতে হয় ঠিক কোন রেজিস্টার ব্লকে সিগন্যাল পাঠাতে হবে। আলাদা রেঞ্জ ব্যবহার করাটা এই বাস্তব হার্ডওয়্যার সংগঠনকে সততার সাথে প্রতিফলিত করে, শুধু কোডের সরলতার জন্য একত্রিত করা হয়নি।

প্র ০৩ যদি bus.write()-এ Flash-চেকটি ভুলবশত বাদ দেওয়া হতো, তাহলে সিমুলেশনটি কী ভুল আচরণ দেখাত?

তাহলে bus.write(0x0010, 0x99) চুপচাপ সফল হয়ে যেত এবং sram_data-তে মান জমা করত — যা ভুলভাবে বোঝাত যে Flash-এ রানটাইমে সরাসরি রাইট করা সম্ভব। বাস্তবে এমন একটি বাগ একটি চেকসাম/ভেরিফিকেশন ব্যর্থতা বা এমনকি একটি "corrupted firmware" পরিস্থিতির সাথে তুলনীয় ভুল ধারণা তৈরি করত — তাই এই এররটি ইচ্ছাকৃতভাবেই স্পষ্ট ও তাৎক্ষণিক রাখা হয়েছে।

অনুশীলন

  1. চিন্তা করুন: উপরের কোড সেলে bus.read(0xC005) কল করলে (GPIO রেঞ্জের মধ্যে, কিন্তু এখনো কোনো রাইট হয়নি এমন একটি ঠিকানা) কী প্রিন্ট হবে বলে আপনার ধারণা?

    0xC005 ঠিকানাটি sram_data ডিকশনারিতে এখনো নেই, তাই self.sram_data.get(addr, 0x00) ডিফল্ট মান 0x00 রিটার্ন করবে, এবং যেহেতু রিজিয়ন টাইপ "PERIPHERAL", আউটপুটে "(পেরিফেরাল স্ট্যাটাস রিড)" ট্যাগসহ READ 0xC005 [GPIO রেজিস্টার ব্লক] -> 0x00 (পেরিফেরাল স্ট্যাটাস রিড) দেখাবে।

  2. পরীক্ষা করুন: উপরের কোড সেলে memory_map-এ একটি নতুন লাইন যোগ করুন — (0xC020, 0xC02F, "PERIPHERAL", "ADC রেজিস্টার ব্লক") — তারপর bus.write(0xC020, 0x80) কল করে Run চেপে দেখুন।

    নতুন রেঞ্জ যোগ করার পর _decode(0xC020) এখন ("PERIPHERAL", "ADC রেজিস্টার ব্লক") রিটার্ন করবে (আগে এটি আনম্যাপড থাকায় ValueError দিত), এবং bus.write(0xC020, 0x80) সত্যিই একটি নতুন পেরিফেরাল সাইড-ইফেক্ট লগ এন্ট্রি (0xC020, 0x80) যোগ করবে — দেখাচ্ছে যে অ্যাড্রেস ডিকোডিং সম্পূর্ণভাবে memory_map লিস্টের উপর নির্ভরশীল, হার্ডকোড করা নয়।

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

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ মাইক্রোপ্রসেসর আর্কিটেকচার, এমবেডেড C, GPIO, টাইমার/PWM/ADC, সিরিয়াল প্রোটোকল, RTOS, সেন্সর/অ্যাকচুয়েটর, IoT আর্কিটেকচার, ওয়্যারলেস প্রোটোকল, MQTT/CoAP ও IoT সিকিউরিটি — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • Computer Architecture & Digital Logic কোর্স সহোদর কোর্স RAM/ROM প্রযুক্তি ও সাধারণ মেমরি-ম্যাপড I/O ধারণার গভীর ভিত্তি সেই কোর্সেই তৈরি হয়েছে।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Design and Analysis of Algorithms ও আরও অনেক কোর্স — সব এক জায়গায়।
আগের পাঠ
এমবেডেড সিস্টেমের জন্য CPU/ALU/রেজিস্টার রিক্যাপ