
مسبار تصحيح SWD لأنوية RISC-V (Hazard3) الخاصة بـ RP2350. جهاز Pico2 واحد ينقّح جهازًا آخر عبر سلكي GPIO.
حوالي 80% من الكود مُولَّد بالذكاء الاصطناعي (vibe coded)؛ وملف README مُولَّد بالكامل تقريبًا (باستثناء قسم تحذير الكود المُولَّد بالذكاء الاصطناعي بأكمله). قضيت ليالي طويلة مع راسم الإشارة والوثائق وصنعت نموذجًا أوليًا يعمل، استطاع تنفيذ sba/قراءة/كتابة السجلات وتنفيذ الأوامر المجردة وprogbuf، أما الباقي فتم باستخدام claude code. الاختبارات هي مجموعة اختبارات شاملة إلى حد ما، وأستخدم قلب المكتبة في مشاريعي الخاصة، لكن، كما يقولون: "هنا توجد التنانين" (hic sunt dracones). كما قرأت ملف README والكود ولم ألاحظ أي شيء خاطئ (وأزلت الأجزاء الخاطئة/غير الواضحة).
كان هذا المشروع بمثابة دراسة حالة لي في توليد الكود بالذكاء الاصطناعي (vibecoding) لمشروع أكثر تعقيدًا لا أفهمه 100% ولا يوجد له كود سابق واضح يمكن "استخدامه". بدأ ككود من حوالي 1000 سطر كتبته بنفسي وكنت أعرفه جيدًا، بعد قراءة وثائق rp2350 وarm swd وriscv debug، والتقاط البيانات براسم الإشارة وopenocd ثم فك تشفيرها وتحليل تسلسل الاستيقاظ ثم أوامر القراءة/الكتابة. بعد أن جعلته يعمل، أعطيته إلى claude ليحوّله إلى مكتبة يمكنني استخدامها في مشاريع أخرى، ثم بنيته ببطء.
بعد حوالي 3-4 آلاف سطر من الكود فقدت المسار تمامًا لما يحدث، ولن أعتبر هذا الكود كودًا كتبته، لكن إضافة المزيد والمزيد من الاختبارات كانت تبدو "لطيفة"، أو على الأقل مطمئنة.
كان هناك قدر من التضليل (gaslighting)، خاصة عندما أساء فهم dap_read_mem32 ظنًّا منه أنه يقرأ من ذاكرة RAM وليس بروتوكول MEM-AP TAR/DRW/RDBUFF، مما أدى إلى كمية لا تصدق من الهراء.
بشكل عام سأقول إنها كانت تجربة مروعة؛ فرغم أن كتابة ما يقارب 10000 سطر من الكود استغرقت 10 ساعات، لا أعتبر هذا مشروعي، ولا أشعر بأي إنجاز أو نمو.
في المقابل، استخدام الذكاء الاصطناعي لقراءة جميع الوثائق (التي يبلغ طولها آلاف الصفحات) وكتابة نصوص برمجية مفيدة لفك تشفير بيانات راسم الإشارة، وإنشاء بنى C محزومة (packed structs) من الوثائق، وما إلى ذلك، كان جميلًا جدًا، وشعرت بالرضا بعد ذلك. اللحظة التي قرأت فيها أول سجل ثم تمكنت من قراءة الذاكرة عبر SBA شعرت بشعور رائع.
المشكلة الأساسية هي taste (الذوق)؛ فعندما أكتب كودًا أشعر إن كان جيدًا أم سيئًا، وأنا أكتبه أعرف إن كان خاطئًا. لكن مع claude code أُصاب بالتبلّد سريعًا جدًا ولا أستطيع التمييز؛ فهو "يُقرأ" بشكل مقبول، لكنني لا أعرف كيف يبدو شعوره. في هذه الحالة حدث ذلك عندما نما الكود حوالي 4 أضعاف، من 1000 إلى 4000 سطر. والأسوأ من كل ذلك، أن نموذجي الذهني للكود قد اختفى تمامًا، ومعه شعوري بملكيته.
الرموز (tokens) ليس لها سبب أو غرض، مما يجعل قراءة الكود صعبة بشكل سخيف، لأن كل رمز يمكن أن يكون هراءً تامًا. عند قراءة كود بشري، للرموز غرض؛ فقد فكّر شخص ما "سأضع هذا في متغير، لاحقًا سأتحقق من حالته". لذلك أتظاهر أنني أنا هو، وأفكر: why (لماذا) كتب هذا؟ وبعد وقت قصير أفهم، لأنه إنسان وأنا إنسان. لكن رموز الذكاء الاصطناعي ليس لها سبب، والأسوأ من كل ذلك أنها جميعًا تبدو صحيحة بشكل مخادع، لذا عليّ التفكير 10 مرات أكثر لمعرفة إن كانت خاطئة. مع أي كود بشري (بما في ذلك كودك الخاص) من السهل جدًا تقدير مدى الثقة به، وهو متسق تمامًا؛ أما مع كود الذكاء الاصطناعي، فقد تكون دالة واحدة أفضل بكثير مما كنت ستكتبه، والكود الذي يليها بسطرين قد يكون هراءً ناتجًا عن التقليد الأعمى (cargo cult) يبدو رائعًا بشكل لا يصدق لكنه خاطئ بنيويًا.
في النهاية سأقول إنني اكتسبت فهمًا جيدًا للأسلاك والتوقيتات وآليات المستوى المنخفض لـ ap/dp وsba وprogbuf، لكنني نادم على عدم كتابة كل شيء بنفسي، حتى لو كان سيتطلب 10 أضعاف الوقت.
أنا أكره هذا بشدة.
ولا يسعني إلا أن أشعر بالاشمئزاز والخزي. هل هذه هي البرمجة الآن؟ آمل حقًا أن تكون هذه مرحلة وسيطة وأن تتغير نحو الأفضل؛ المشكلة أنني لا أعرف ما هو "الأفضل". يبدو أن البعض لا يريد كتابة الكود، والبعض الآخر لا يريد نمذجة المشكلة، وفئة ثالثة لا تريد التفكير. بالنسبة لي، لست متأكدًا؛ أنا بالفعل أريد أن أصنع الأشياء، وفي كثير من الأحيان لا أريد أن أعرف شيئًا ما بل أريد استخدامه فقط، مثلًا وحدة تحكم مضيف USB في rp2350؛ الطريقة التي يجب بها إعادة تسليح المقاطعات والطريقة التي يُشارك بها سجل epx مزعجة للغاية، لأسباب وجيهة على الأرجح، لكنني أريد فقط استخدامها لصنع برنامج تشغيل CBI الخاص بي.
أعتقد أن السؤال هو: ما هو الشيء الذي أريد أن أصنعه؟ لأنه يمكنك الصعود في سلم التجريد، من سجلات رقاقة USB إلى CBI إلى UFI إلى FAT16 إلى نظام تشغيل الحاسوب القديم الطراز الذي أصنعه. لكن لماذا نتوقف؟ اصنع المخططات، ولوحات الدوائر المطبوعة (PCBs)، وملفات CAD، وربما أرسلها تلقائيًا إلى المصنع؟ ثم شحنها إليّ فقط؟ لكن لماذا نتوقف؟ اصنع متجري الإلكتروني، وابدأ البيع، واصنع مجتمعًا، والإعلانات، والتسويق، وولّد بعض فيديوهات فتح العلبة (unboxing)، وربما بعض الميمات الفيروسية؟ عالج الطلبات مباشرة إلى المصنع حسب الطلب، وإن كانت هناك مشكلة فليكن دعم العملاء جاهزًا.
ماذا أفعل في هذه الأثناء؟ أجلس على الشاطئ؟ أنا أكره الشاطئ.
أين يتوقف هذا؟
ملاحظة: كما قال الشاعر: ثمن الحصول على ما تريد، هو الحصول على ما أردته يومًا ما.
Application
|
rp2350.c RISC-V Debug Module (halt/resume/step, registers, memory, trace)
|
dap.c Debug Access Port (DP/AP registers, bank caching, MEM-AP)
|
swd_protocol.c SWD wire protocol (PIO bit-banging, packet encoding, retry)
|
swd.pio PIO state machine (4-cycle SWCLK, bidirectional SWDIO)
كل طبقة تحافظ على حالتها الخاصة وتتواصل مع جارتها فقط.
swd_config_t config = swd_config_default();
config.pin_swclk = 2;
config.pin_swdio = 3;
swd_target_t *target = swd_target_create(&config);
swd_connect(target);
rp2350_init(target);
rp2350_halt(target, 0);
swd_result_t pc = rp2350_read_pc(target, 0);
rp2350_resume(target, 0);
swd_target_destroy(target);
rp2350_halt(target, 0);
rp2350_step(target, 0);
rp2350_resume(target, 0);
rp2350_reset(target, 0, true);
swd_result_t pc = rp2350_read_pc(target, 0);
rp2350_write_pc(target, 0, 0x20000000);
swd_result_t val = rp2350_read_reg(target, 0, 5);
rp2350_write_reg(target, 0, 5, 0xDEADBEEF);
uint32_t regs[32];
rp2350_read_all_regs(target, 0, regs);
swd_result_t csr = rp2350_read_csr(target, 0, 0x300);
rp2350_write_csr(target, 0, 0x300, value);
كلا النواتين (0 و1) قابلتان للتحكم بشكل مستقل.
غير تدخّلي عبر الوصول إلى ناقل النظام (System Bus Access). يعمل أثناء تشغيل النواة.
swd_result_t val = rp2350_read_mem32(target, 0x20000000);
rp2350_write_mem32(target, 0x20000000, 0xDEADBEEF);
rp2350_read_mem16(target, addr);
rp2350_write_mem8(target, addr, byte);
uint32_t buf[256];
rp2350_read_mem_block(target, 0x20000000, buf, 256);
rp2350_write_mem_block(target, 0x20000000, buf, 256);
تستخدم النقلات الكتلية خاصية الزيادة التلقائية في SBA (auto-increment) لتحقيق الأداء.
const uint32_t program[] = {
0x200415b7, // lui a1, 0x20040
0xabcd0537, // lui a0, 0xabcd0
0x00a5a223, // sw a0, 4(a1)
0x0000006f, // j . (loop)
};
rp2350_execute_code(target, 0, 0x20000000, program, 4);
يرفع الكود إلى SRAM الخاص بالهدف، ويتحقق منه، ويضبط PC، ويستأنف التنفيذ.
bool on_instruction(const trace_record_t *rec, void *ctx) {
printf("0x%08x: 0x%08x\n", rec->pc, rec->instruction);
return true;
}
int traced = rp2350_trace(target, 0, 100, on_instruction, NULL, false);
ينفّذ التعليمات خطوة بخطوة عبر DCSR.step. حوالي 5 مللي ثانية لكل تعليمة دون التقاط السجلات، وحوالي 80 مللي ثانية مع التقاط كامل للسجلات.
تنفيذ مباشر لتعليمات RISC-V في سياق التصحيح:
uint32_t progbuf[] = {
0x34202473, // csrr s0, mcause
0x00100073 // ebreak
};
rp2350_execute_progbuf(target, 0, progbuf, 2);
swd_result_t mcause = rp2350_read_reg(target, 0, 8);
يُستخدم الوصول إلى CSR داخليًا عبر هذه الآلية لأن Hazard3 لا يدعم أوامر CSR المجردة (abstract CSR commands).
add_subdirectory(lib/pico2-swd-riscv)
target_link_libraries(your_app pico2_swd_riscv)
مستوى تفصيل التصحيح (وقت الترجمة):
set(PICO2_SWD_DEBUG_LEVEL 3) # 0=none, 1=warn, 2=info, 3=debug
يستخدم SWD حزم طلب من 8 بت (start، APnDP، RnW، addr[3:2]، parity، stop، park)، وACK من 3 بت (OK=1، WAIT=2، FAULT=4)، ومراحل بيانات من 33 بت مع بت تماثل (parity). تتعامل دورات التحويل (turnaround cycles) مع تغييرات اتجاه SWDIO. تُعاد محاولة استجابات WAIT تلقائيًا (الافتراضي: 5 محاولات، مهلة تراجع 100 ميكروثانية).
غير قياسي: [15:12]=APSEL, [11:8]=0xD, [7:4]=bank, [0]=ctrlsel. قيمة 0xD في البتات [11:8] مطلوبة لكنها غير موثّقة.
مصافحة من ثلاث مراحل عبر Bank 1 من CSW: إلغاء التفعيل (0x00000000)، التفعيل (0x00000001)، الإعداد الكامل (0x07FFFFC1). استجابة الحالة المتوقعة: 0x04010001.
سجلات GPR عبر الأوامر المجردة (regno 0x1000+n، نقل 32 بت). سجلات CSR عبر المخزن المؤقت للبرنامج: حفظ s0، تنفيذ csrr s0, <csr> أو csrw <csr>, s0، قراءة/استعادة s0.
يتم ضبط SBCS مع sbaccess=32bit وsbreadonaddr. كتابة SBADDRESS0 تؤدي إلى تشغيل قراءة الناقل، وتكون البيانات متاحة فورًا في SBDATA0. تفعّل النقلات الكتلية خاصية sbautoincrement للقراءة/الكتابة المتدفقة دون إعداد عنوان لكل كلمة.
قراءة DCSR عبر progbuf، وتعيين بت الخطوة (bit 2)، واستئناف النواة. تنفّذ النواة تعليمة واحدة ثم تعود إلى وضع التصحيح. امسح بت الخطوة بعد ذلك.
MIT. انظر LICENSE.