
SWD-отладочный пробник для RISC-V (Hazard3) ядер RP2350. Один Pico2 отлаживает другой через два провода GPIO.
Около 80% кода написано в стиле vibe coding; README почти полностью сгенерирован (кроме всего раздела с предупреждением о vibe-коде). Я провёл много ночей с осциллографом и документацией и сделал рабочий прототип, который мог выполнять sba/чтение/запись регистров, абстрактные команды и progbuf, остальное было сделано с помощью Claude Code. Тесты представляют собой довольно исчерпывающий набор тестов, и я использую ядро библиотеки в своих собственных проектах, но, как говорится, «hic sunt dracones». Я также перечитал README и код, ничего неправильного не заметил (и убрал неверные/непонятные части).
Этот проект был моим исследовательским примером vibe-кодинга более сложного проекта, который я не понимаю на 100%, и для которого нет очевидного существующего кода, который можно было бы «использовать». Началось это с ~1000 строк кода, которые я написал сам и знал очень хорошо, читая документацию по rp2350, arm swd и riscv debug, захватывая данные осциллографом и openocd, затем декодируя их и анализируя последовательность пробуждения, а затем команды чтения/записи. После того как я заставил это работать, я отдал его Claude, чтобы превратить в библиотеку, которую я мог бы использовать в других проектах, и затем я медленно развивал её.
После примерно 3–4 тысяч строк кода я полностью потерял представление о том, что происходит, и я бы не считал этот код написанным мной, но добавление всё новых и новых тестов ощущалось «приятным», или по крайней мере успокаивающим.
Было немного газлайтинга, особенно когда он неправильно понял dap_read_mem32, решив, что это чтение из ОЗУ, а не протокол MEM-AP TAR/DRW/RDBUFF, что привело к невероятному количеству бессмыслицы.
В целом я бы сказал, что это был ужасный опыт, даже несмотря на то, что на написание почти 10 000 строк кода ушло 10 часов, я не считаю этот проект своим, и у меня нет ни чувства достижения, ни роста.
В отличие от этого, использование ИИ для чтения всей документации (это тысячи страниц) и написания полезных скриптов для декодирования данных с осциллографа, создания упакованных C-структур из документации и т. д. было очень приятным, и после этого я чувствовал себя хорошо. В тот момент, когда я прочитал первый регистр, а затем, когда я смог прочитать память через SBA, я почувствовал себя потрясающе.
Основная проблема — это вкус. Когда я пишу код, я чувствую, хорош он или плох; пока я его пишу, я знаю, если он неправильный, но при использовании Claude Code я очень быстро теряю чувствительность и просто не могу определить. Он «читается» нормально, но я не знаю, как он ощущается. В данном случае это произошло, когда код вырос примерно в 4 раза, с 1 до 4 тысяч строк. И хуже всего то, что моя мысленная модель кода полностью исчезла, а вместе с ней и моё ощущение авторства.
У токенов нет ни причины, ни цели, что делает чтение кода невероятно сложным, поскольку каждый токен может быть полной бессмыслицей. При чтении человеческого кода символы имеют назначение: кто-то подумал «я положу это в переменную, позже проверю её состояние», поэтому я притворяюсь им и думаю, зачем они это написали? Вскоре я понимаю, потому что они люди и я человек. Но у символов ИИ нет причины, и хуже всего то, что все они выглядят обманчиво правильными, поэтому мне приходится думать в 10 раз усерднее, не ошибочно ли это. С любым человеческим кодом (включая ваш собственный) довольно легко оценить, насколько ему можно доверять, и он довольно последователен; с ИИ-кодом одна функция может быть намного лучше того, что вы бы написали, а код через две строки ниже может быть карго-культовой ерундой, которая выглядит невероятно хорошо, но структурно неверна.
В итоге я бы сказал, что приобрёл хорошее понимание проводов, таймингов и низкоуровневой механики ap/dp, sba и progbuf, но я сожалею, что не написал всё это сам, даже если бы это заняло в 10 раз больше времени.
Я чертовски это ненавижу.
И я не могу не чувствовать отвращение и стыд. Это теперь программирование? Я очень надеюсь, что это промежуточная стадия и всё изменится к лучшему. Проблема в том, что я не знаю, что такое «лучше»: для одних это не писать код, для других — не моделировать проблему, а для третьих — не нужно думать. Для меня я не уверен. Я действительно хочу создавать вещи, и часто я не хочу что-то знать, но хочу это использовать. Например, USB host-контроллер rp2350: то, как нужно перевзводить прерывания, и то, как разделяется регистр epx, — это супер раздражающе, вероятно, по уважительным причинам, но я просто хочу использовать его для своего драйвера CBI.
Думаю, вопрос в том, что именно я хочу сделать, потому что можно подняться высоко по стеку: от регистров USB-чипа к CBI, к UFI, к FAT16, к ОС компьютера старой школы, который я делаю. Но зачем останавливаться? Сделать схемы, печатные платы, CAD-файлы, возможно, автоматически отправить их на фабрику? А затем просто отправить это мне? Но зачем останавливаться? Сделать мой интернет-магазин, начать продажи, создать сообщество, рекламу, маркетинг, сгенерировать несколько видео распаковки, возможно, вирусные мемы? Обрабатывать заказы напрямую на фабрике, по требованию; если есть проблема — уже готово с поддержкой клиентов.
Что мне делать тем временем? Сидеть на пляже? Я ненавижу пляж.
Где это остановится?
PS: как сказал поэт: цена получения того, чего ты хочешь, — это получение того, чего ты когда-то хотел.
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 для производительности.
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-команды.
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), 3-битный ACK (OK=1, WAIT=2, FAULT=4) и 33-битные фазы данных с битом чётности. Циклы разворота (turnaround) обрабатывают смену направления 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, установка бита шага (бит 2), возобновление ядра. Ядро выполняет одну инструкцию и снова входит в режим отладки. После этого сбросить бит шага.
MIT. См. LICENSE.