পাঠ ১৩ · ৫৭-এর মধ্যে · মডিউল ৩

এমবেডেড সিস্টেম ডিবাগিং — JTAG/SWD ও লজিক অ্যানালাইজার

Debugging embedded systems — JTAG/SWD and logic analyzers
৮ মিনিট পড়া মধ্যম · Intermediate Python সিমুলেশনসহ সম্পূর্ণ বাংলায়

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

  • এমবেডেড ডিবাগিং সাধারণ সফটওয়্যার ডিবাগিং থেকে কেন ও কীভাবে আলাদা
  • JTAG ও SWD হার্ডওয়্যার ডিবাগ ইন্টারফেস ঠিক কী সুবিধা দেয়
  • লজিক অ্যানালাইজার ও অসিলোস্কোপ কখন ও কেন ব্যবহার করা হয়
  • একটি সত্যিকারের সিমুলেশন — ব্রেকপয়েন্ট হিট হয়ে রেজিস্টার-ফাইল ডাম্প দেখানো

১ · এমবেডেড ডিবাগিং কেন ভিন্ন

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

২ · JTAG ও SWD — হার্ডওয়্যার ডিবাগ ইন্টারফেস

JTAG (Joint Test Action Group) মূলত একটি চিপ-টেস্টিং স্ট্যান্ডার্ড হিসেবে শুরু হলেও, আজ এটি ব্যাপকভাবে ডিবাগিং-এর জন্যও ব্যবহৃত হয় — একটি একাধিক-তার (সাধারণত ৪-৫ পিন) সিরিয়াল ইন্টারফেস যা একটি ডিবাগ প্রোব (হোস্ট PC ও চিপের মাঝে একটি ছোট হার্ডওয়্যার অ্যাডাপ্টার) দিয়ে চিপের একটি বিশেষ "ডিবাগ পোর্ট"-এ সংযুক্ত হয়। এই সংযোগের মাধ্যমে ডেভেলপার সরাসরি ব্রেকপয়েন্ট বসাতে পারেন (নির্দিষ্ট ইনস্ট্রাকশনে প্রোগ্রাম থামিয়ে দেওয়া), সিঙ্গল-স্টেপ চালাতে পারেন (একটি একটি করে ইনস্ট্রাকশন এক্সিকিউট করা), এবং যেকোনো মুহূর্তে চিপের রেজিস্টার ও মেমরির বর্তমান অবস্থা লাইভ পড়তে পারেন — এমনকি প্রোগ্রামের নিজের কোডে কোনো ডিবাগ-স্পেসিফিক লাইন না লিখেই।

SWD (Serial Wire Debug) হলো ARM-এর নিজস্ব, সরলীকৃত ২-তারের বিকল্প — একই কাজ করে (ব্রেকপয়েন্ট, সিঙ্গল-স্টেপ, রেজিস্টার/মেমরি অ্যাক্সেস) কিন্তু কম পিন ব্যবহার করে, যা ছোট, পিন-সীমিত মাইক্রোকন্ট্রোলার প্যাকেজের জন্য সুবিধাজনক — আজকাল বেশিরভাগ ARM Cortex-M-ভিত্তিক বোর্ডে (L04-এর পরিচিত পরিবার) SWD-ই প্রধান ডিবাগ ইন্টারফেস হিসেবে ব্যবহৃত হয়।

কোড-লেভেল ডিবাগ (JTAG/SWD) হোস্ট PC (ডিবাগার সফটওয়্যার) JTAG/SWD ডিবাগ প্রোব (অ্যাডাপ্টার) ডিবাগ পোর্ট টার্গেট চিপ ব্রেকপয়েন্ট/রেজিস্টার ইলেকট্রিক্যাল-সিগন্যাল-লেভেল পর্যবেক্ষণ টার্গেট চিপ (GPIO/UART পিন) সরাসরি ভোল্টেজ লজিক অ্যানালাইজার / অসিলোস্কোপ ওয়েভফর্ম টাইমিং/ডেটা
দুটো ভিন্ন, পরিপূরক দৃষ্টিভঙ্গি — উপরেরটি "কোড কী করছে" দেখায়, নিচেরটি "পিনে বাস্তবে কী ভোল্টেজ যাচ্ছে" দেখায়।

৩ · লজিক অ্যানালাইজার ও অসিলোস্কোপ

JTAG/SWD প্রোগ্রামের যুক্তিগত অবস্থা (রেজিস্টার, মেমরি, ইনস্ট্রাকশন) দেখায় — কিন্তু কখনো কখনো সমস্যা থাকে ইলেকট্রিক্যাল স্তরে, যেমন একটি GPIO পিনের প্রকৃত টাইমিং প্রত্যাশিত মতো হচ্ছে কিনা, বা একটি UART লাইনে (M6-এ বিস্তারিত আসবে) সঠিক বিট-প্যাটার্ন পাঠানো হচ্ছে কিনা। এখানেই একটি লজিক অ্যানালাইজার (একাধিক ডিজিটাল পিনের HIGH/LOW অবস্থা সময়ের সাথে রেকর্ড করে) বা অসিলোস্কোপ (একটি পিনের প্রকৃত অ্যানালগ ভোল্টেজ ওয়েভফর্ম দেখায়, ডিজিটাল সিগন্যালের রাইজ/ফল টাইম বা নয়েজ ধরার জন্য উপযোগী) কাজে আসে — এরা কোডের ভেতরে কোনো "রেজিস্টার" ধারণা ছাড়াই সরাসরি তারে কী ভোল্টেজ যাচ্ছে তা দেখায়।

৪ · সত্যিকারের সিমুলেশন — ব্রেকপয়েন্ট হিট ও রেজিস্টার ডাম্প

নিচের কোড সেলে L05-এর ধাঁচের একটি ছোট রেজিস্টার-ফাইল সিমুলেশন ব্যবহার করে দেখানো হয়েছে — একটি সরলীকৃত "প্রোগ্রাম" ধাপে ধাপে চলে, এবং একটি নির্দিষ্ট ইনস্ট্রাকশনে (যেন একটি JTAG/SWD ব্রেকপয়েন্ট হিট হলো) থেমে গিয়ে সেই মুহূর্তের রেজিস্টার অবস্থা ডাম্প করে দেখানো হয় — ঠিক যেমন একটি বাস্তব ডিবাগার JTAG/SWD দিয়ে চিপ থামিয়ে রেজিস্টার উইন্ডো দেখায়।

Python
class RegisterFile:
    """একটি ছোট মাইক্রোকন্ট্রোলারের CPU রেজিস্টার-ফাইলের সরলীকৃত সিমুলেশন (L05-এর ধাঁচে)।"""
    def __init__(self):
        self.regs = {f"R{i}": 0 for i in range(8)}
        self.regs["SP"] = 0x20010000   # স্ট্যাক পয়েন্টার -- SRAM-এর উপরের দিকে শুরু (L06/L12-এর মেমরি ম্যাপের ধাঁচে)
        self.regs["PC"] = 0            # প্রোগ্রাম কাউন্টার

    def dump(self, label):
        print(f"--- {label} ---")
        for name in ["R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "SP", "PC"]:
            print(f"  {name:3s} = 0x{self.regs[name]:08X}")

# একটি সরলীকৃত "প্রোগ্রাম" -- প্রতিটি এন্ট্রি একটি (রেজিস্টার, নতুন মান) পরিবর্তন প্রতিনিধিত্ব করে
program = [
    ("R0", 5),
    ("R1", 10),
    ("R2", 15),   # <- এই ইনস্ট্রাকশনের ঠিক পরে একটি ব্রেকপয়েন্ট বসানো হয়েছে
    ("R0", 20),
    ("R3", 99),
]

BREAKPOINT_INDEX = 2   # ডিবাগারে ইনস্ট্রাকশন #2 ("R2 = 15") এক্সিকিউট হওয়ার পরে থামার জন্য সেট করা

rf = RegisterFile()
print("প্রোগ্রাম রান শুরু (হোস্ট PC থেকে JTAG/SWD-এর মাধ্যমে চিপ নিয়ন্ত্রণ করা হচ্ছে)...")
for i, (reg, value) in enumerate(program):
    rf.regs[reg] = value
    rf.regs["PC"] += 4   # প্রতিটি ইনস্ট্রাকশন ৪ বাইট ধরে PC এগিয়ে যায় (সরলীকৃত)
    if i == BREAKPOINT_INDEX:
        print(f"\n>>> ব্রেকপয়েন্ট হিট! ইনস্ট্রাকশন #{i} ('{reg} = {value}') চালানোর পরে থামলো <<<")
        rf.dump("ব্রেকপয়েন্টে রেজিস্টার ডাম্প (ডিবাগার এই মুহূর্তে যা দেখাবে)")
        break
else:
    print("ব্রেকপয়েন্ট কখনো হিট হয়নি")

print()
print("(ব্রেকপয়েন্ট রিলিজ করলে বাকি ইনস্ট্রাকশনগুলো চলতে থাকবে...)")
for reg, value in program[BREAKPOINT_INDEX + 1:]:
    rf.regs[reg] = value
    rf.regs["PC"] += 4
rf.dump("প্রোগ্রাম শেষে চূড়ান্ত রেজিস্টার অবস্থা")

    
লক্ষ্য করুন — ব্রেকপয়েন্টে থামার মুহূর্তে R3 এখনও 0 (কারণ ইনস্ট্রাকশন #৪ তখনও চলেনি), কিন্তু প্রোগ্রাম শেষে ডাম্পে R3 = 0x00000063 (দশমিক ৯৯) দেখা যায় এবং R0 বদলে গিয়ে 20 হয়েছে। ঠিক এভাবেই একটি বাস্তব ডিবাগার ব্রেকপয়েন্টে থামার সময়ের "স্ন্যাপশট" ও প্রোগ্রাম চলতে দেওয়ার পরের চূড়ান্ত অবস্থা — দুটোই আলাদাভাবে দেখাতে পারে।
মূল কথা · Key takeaway

JTAG/SWD প্রোগ্রামের যুক্তিগত (রেজিস্টার/মেমরি) অবস্থা লাইভ দেখার সুযোগ দেয়, যখন সাধারণ print-নির্ভর ডিবাগিং সহজলভ্য নয়। লজিক অ্যানালাইজার/অসিলোস্কোপ এর পরিপূরক — যখন সমস্যা কোডের যুক্তিতে নয় বরং প্রকৃত ইলেকট্রিক্যাল সিগন্যালে থাকে। বাস্তব এমবেডেড ডেভেলপমেন্টে এই দুই ধরনের টুলই একসাথে ব্যবহৃত হয় — একটি "কী ভুল হচ্ছে তা কোডে দেখায়", আরেকটি "তারে সত্যিই কী ঘটছে তা দেখায়"। এই মডিউল (M3) এখানেই শেষ — পরের মডিউলে (M4, শুরু L14 থেকে) আমরা এই সব ধারণা প্রয়োগ করে সরাসরি GPIO নিয়ন্ত্রণে যাচ্ছি।

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

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

প্র ০১ কোড সেলে ব্রেকপয়েন্টে থামার মুহূর্তে PC-এর মান কত, এবং কেন এটি ঠিক সেই মানই?

ব্রেকপয়েন্টে থামার সময় PC = 0x0000000C (দশমিক ১২)। কারণ, প্রতিটি ইনস্ট্রাকশন এক্সিকিউট হওয়ার সাথে সাথে PC ৪ বাইট করে বাড়ে, আর ব্রেকপয়েন্ট ইনস্ট্রাকশন #২ (তিন নম্বর, ইনডেক্স ০-থেকে-গোনা) চালানোর পরে

প্র ০২ একটি বাস্তব ডিবাগ সেশনে, ব্রেকপয়েন্টে থামার পর ডেভেলপার যদি একটি রেজিস্টারের মান ম্যানুয়ালি বদলে দেন (যেমন একটি ভুল ভ্যারিয়েবল ঠিক করতে), এটি কি সম্ভব — এবং কীভাবে?

হ্যাঁ, সম্ভব — এটাই JTAG/SWD-ভিত্তিক ডিবাগিং-এর একটি শক্তিশালী দিক। যেহেতু ডিবাগ প্রোব সরাসরি চিপের রেজিস্টার/মেমরিতে রিড এবং রাইট উভয়ই করতে পারে (শুধু রিড নয়), ডেভেলপার ডিবাগারের "watch/registers" উইন্ডোতে নতুন মান লিখে চিপের প্রকৃত রেজিস্টার সেই মুহূর্তেই আপডেট করে দিতে পারেন — পরীক্ষামূলকভাবে একটি "যদি এই ভ্যারিয়েবলের মান অন্যরকম হতো" পরিস্থিতি লাইভ চিপে যাচাই করার জন্য এটি খুবই কার্যকর একটি কৌশল।

প্র ০৩ একটি টাইমিং-সংবেদনশীল বাগ (যেমন একটি সিগন্যাল খুব দ্রুত বদলাচ্ছে বলে মিস হয়ে যাচ্ছে) ডিবাগ করার জন্য JTAG/SWD-এর বদলে লজিক অ্যানালাইজার বেশি উপযোগী কেন হতে পারে?

কারণ JTAG/SWD দিয়ে ব্রেকপয়েন্টে থামালে প্রোগ্রামের প্রকৃত সময়-নির্ভরতা নষ্ট হয়ে যায় — চিপ থেমে থাকা অবস্থায় বাহ্যিক সিগন্যাল/টাইমিং স্বাভাবিকভাবে চলতে থাকে না, তাই একটি দ্রুত, ক্ষণস্থায়ী টাইমিং সমস্যা ব্রেকপয়েন্টে ধরা পড়ে না ("Heisenbug"-এর মতো আচরণ)। একটি লজিক অ্যানালাইজার প্রোগ্রামকে থামায় না — এটি নিরবিচ্ছিন্নভাবে প্রকৃত সময়ে পিনের অবস্থা রেকর্ড করে, তাই আসল টাইমিং সম্পর্ক অক্ষত থাকে এবং পরে বিশ্লেষণ করা যায়।

অনুশীলন

  1. চিন্তা করুন: যদি BREAKPOINT_INDEX-এর মান 4 (শেষ ইনস্ট্রাকশন) করা হয়, ব্রেকপয়েন্টে থামার মুহূর্তের ডাম্পে R3-এর মান কত দেখাবে বলে আপনার ধারণা?

    ইনস্ট্রাকশন #৪ হলো ("R3", 99) — BREAKPOINT_INDEX=4 মানে এই ইনস্ট্রাকশন চালানোর পরে থামবে, তাই ব্রেকপয়েন্ট ডাম্পেই R3 = 0x00000063 (৯৯) দেখাবে — অর্থাৎ ব্রেকপয়েন্টের স্ন্যাপশট আর "প্রোগ্রাম শেষে" ডাম্প এক্ষেত্রে অভিন্ন হবে, কারণ এটিই শেষ ইনস্ট্রাকশন।

  2. পরীক্ষা করুন: কোড সেলে program লিস্টে একটি নতুন এন্ট্রি ("R4", 7) ব্রেকপয়েন্টের আগে (ইনডেক্স ২-এর আগে) যোগ করে Run চেপে দেখুন ব্রেকপয়েন্ট হিট হওয়ার সময় PC-এর মান কীভাবে বদলায়।

    একটি নতুন ইনস্ট্রাকশন যোগ করলে BREAKPOINT_INDEX=2-এ পৌঁছানোর আগে এখন চারটি (আগে তিনটি ছিল) ইনস্ট্রাকশন চলবে (নতুনটিসহ, ব্রেকপয়েন্ট-ইনস্ট্রাকশন পর্যন্ত), তাই PC $4 \times 4 = 16$ বাইট (0x00000010) হবে, আগের 0x0000000C-এর বদলে — দেখাচ্ছে PC সবসময় "এতগুলো ইনস্ট্রাকশন চলেছে × ৪" এই সম্পর্ক মেনে চলে।

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

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ মাইক্রোপ্রসেসর আর্কিটেকচার, এমবেডেড C, GPIO, টাইমার/PWM/ADC, সিরিয়াল প্রোটোকল, RTOS, সেন্সর/অ্যাকচুয়েটর, IoT আর্কিটেকচার, ওয়্যারলেস প্রোটোকল, MQTT/CoAP ও IoT সিকিউরিটি — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • Computer Architecture & Digital Logic কোর্স সহোদর কোর্স রেজিস্টার ও ইনস্ট্রাকশন এক্সিকিউশনের সাধারণ ভিত্তি সেই কোর্সেই তৈরি হয়েছে — এই কোর্স হার্ডওয়্যার-লেভেল ডিবাগিং টুলের প্র্যাকটিক্যাল দিকে গভীরে যায়।
  • সব 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 ও আরও অনেক কোর্স — সব এক জায়গায়।