The Jerusalem PostANU adds three new artworks to 'October 7' exhibit as Israel marks third anniversary of massacrePunchOne dead, many injured as bus catches fire on Kwara expresswayESPNTransfer rumors, news: Bayern brace for tough contract talks with OliseBollywood HungamaRakul Preet Singh BREAKS silence on Income Tax searches, DENIES involvement in illegal foreign remittances: "Have been paying my due taxes since the age of 20"InquirerFoul odor prompts shoreline inspection in MamburaoInvesting.comGoldman Sachs anticipe des gains pour le real brésilien après les électionsInvesting.comGoldman Sachs prevé ganancias del real brasileño tras eleccionesNumeramaL’IA a tué (pour l’instant) l’un des programmes de sécurité de GoogleBBC NewsScott out of England squad and faces injury lay-off7sur7Trump dévoile le numéro de portable d’un collègue de son parti opposé à l’heure d’été permanente: “Appelez-le!”Times of India EntertainmentSRK's net worth is Rs 12,000 crore which is 4x times more than Salman, Aamir, Akshay, says analystGMA NewsSara Duterte had over P10M in withdrawals, check encashment in Dec. 2024 -- AMLC witness
The Daily Newsstand · Free, Always
Monday, October 5, 2026

Zynq 7000. Камера MIPI-CSI-2 и вывод видеопотока через HDMI и Ethernet UDP. Часть 2

Translate

Продолжаем рассмотрение темы вывода изображения взятого с камеры на базе сенсора OV5647 (CSI-2, RAW10). В первой части мы разобрали подключение камеры к Zynq-7020, подготовили проект в Vivado и получили живое изображение на HDMI, используя битстрим производителя и отладку через JTAG. Продолжение начинается с главы 22: переходим к собственному битстриму и разбираемся с ошибками, которые проявились после пересборки.

Начнем с искажений изображения: входных задержек MIPI, неверной конфигурации DDR, перестановки цветовых компонент и разрывов кадра. Затем добавим аппаратное сжатие JPEG, проверим обвязку энкодера в симуляции и настроим загрузку Linux через Buildroot. Завершим проект передачей MJPEG по HTTP с возможностью перепаковки в RTP/UDP на хосте. По ходу разберем конкретные симптомы, проверки и исправления, а в конце измерим производительность всего тракта: от примерно 30,6 кадра в секунду на приеме до 23 кадров в сетевом потоке.

Всем заинтересованным - добро пожаловать под кат!

Дисклеймер. Перед началом повествования, хотелось бы заранее оговориться, что основная цель, которую я преследую при написании этой статьи — рассказать о своем опыте. Я не являюсь профессиональным разработчиком под ПЛИС на языке Verilog и могу допускать какие-либо ошибки в использовании терминологии, использовать не самые оптимальные пути решения задач, etc. Но отмечу, что любая конструктивная и аргументированная критика только приветствуется. Что ж, поехали…

Репозиторий с артефактами

Репозиторий со всеми артефактами: https://github.com/megalloid/zynq_mipi_csi_to_hdmi_ethernet_udp

Часть V. Четыре болезни почти правильной картинки

В части IV картинка появилась на чужом битстриме, и симптомы там были честные: нет пакетов, нет кадров, поток замирает. Здесь ситуация меняется на более коварную. Свой битстрим даёт изображение сразу — и это изображение почти правильное. Четыре раза подряд. Каждый раз выглядит как «камера шумит», и каждый раз причина в другом месте: в калибровке задержек, в микросхеме памяти, в порядке байт и в согласовании каналов. Эта часть — про то, как «почти правильно» разбирается на отдельные вопросы.

Глава 22. Свой битстрим: что значит пересобрать эталон

22.1. Зачем свой, если чужой уже показывает картинку

Три причины, и ни одна не про самолюбие.

Приёмник должен быть собран под реальную скорость линии. На чужом битстриме приём держался на одном вручную подобранном значении, потому что все внутренние константы масштабированы под 1000 Мбит/с (часть II, глава 10.2). Собранный под 408 Мбит/с приёмник работает на всём плато допуска и не требует подкрутки.

Ничего не должно программироваться перед появлением картинки. У вендора пиксельная частота делается программируемым генератором, которому надо записать регистры. Это ещё один шаг, который можно молча пропустить, и тогда монитор тёмный при исправном тракте. У нас частота фиксированная, а сериализатор делает свою пятикратную сам — картинка не зависит от того, вспомнил ли софт про инициализацию.

Дизайн должен быть воспроизводимым. Причина, разобранная в части III, глава 14.1: скрипт — одновременно сборка и спецификация, а блок-дизайн из графического редактора через неделю не воспроизводится.

22.2. Что оставлено вендорским намеренно

Адреса всех блоков. Полностью, включая пустое окно там, где у вендора стоял программируемый генератор частоты.

Это сознательный контракт совместимости: все скрипты, написанные в части IV во время отладки на чужом битстриме, использовали эти числа. Оставив адреса, мы получаем возможность запустить любой из них против своего дизайна без правок — то есть сравнивать два битстрима одним и тем же инструментом. В главе 23 это окажется решающим.

Из той же логики фаза цветового фильтра оставлена вендорской. Правда, как выяснится в главе 25, до определённого момента это значение вообще не имело смысла.

22.3. Как устроен выход изображения

Тракт показа собирается из четырёх блоков, и полезно понимать, кто за что отвечает, потому что дальше их придётся различать по симптомам.

Блок

Что делает

канал показа (DMA на чтение)

достаёт кадр из памяти и отдаёт потоком

генератор видеотаймингов

формирует строчные и кадровые синхроимпульсы

стыковщик потока с таймингами

согласует поток пикселей с развёрткой, держит буфер строки

сериализатор

кодирует и выводит четыре дифференциальные пары

Здесь стоит разобрать одну арифметику, потому что она объясняет, почему видеотракт может работать медленнее пиксельной частоты и это не ошибка.

Монитор в режиме 1080p60 тактируется частотой 148,5 МГц, но из 2200 тактов на строку активными являются только 1920:

в среднем по строке: 148,5 МГц × 1920 / 2200 ≈ 129,6 МГц
частота видеотракта                            142,857 МГц

Считать надо именно по строке, а не по кадру: строчное гашение коротко, и тракт обязан покрывать расход в пределах строки. Усреднение по всему кадру дало бы 124,4 миллиона пикселей в секунду (там добавляются 45 неактивных строк) — то же число, что стоит за 373 МБ/с в части 0, глава 2.5, — но запас надо считать по более жёсткому из двух.

Итак, 129,6 против 142,857: запас есть, хотя и небольшой. Внутри активной части строки дисплей потребляет быстрее, чем тракт подаёт, и этот дефицит поглощает буфер строки в стыковщике. Отсюда два вывода: повышать частоту видеотракта дальше смысла нет, а понижение до 142,857 (история — в части VII) ничего не сломало.

22.4. Что пересборка вытащила на свет

Самый неожиданный итог этой главы: чужой дизайн нёс в себе решения, о которых мы не знали, что они решения.

Пока мы работали на вендорском битстриме, в нём уже были правильно расставлены входные задержки приёмника и правильно сконфигурирована микросхема памяти. Ни то, ни другое не было видно снаружи: приёмник просто работал, память просто работала. Пересобрав дизайн с нуля, мы получили два параметра со значениями по умолчанию — и обе следующие главы посвящены тому, как это выглядело.

Урок на будущее, применимый к любому переносу: список параметров, которые вы скопировали, короче списка параметров, которые на что-то влияют. Разница между этими списками и есть то, что вы будете отлаживать.

22.5. Результат

Живое видео 1080p30 на собственном битстриме, без единой вендорской строчки в контуре: чистая картинка, верные цвета, без разрывов на движении, приёмник без единой ошибки за секунду измерения, около 30,8 кадра в секунду.

Занятость этой стадии — без JPEG-энкодера: примерно 26,6 % логических таблиц, 20,8 % регистров, 25,4 % блочной памяти, 3,6 % умножителей. Сколько стоит энкодер и как это соотносится с итоговой сборкой, посчитано в части 0, глава 2.4.

make bit      # сборка
make live     # запуск на плате

Глава 23. Болезнь первая: чистый приём и рваная картинка

23.1. Симптом, который не виден обычными индикаторами

Приёмник собран, поток идёт, конвейер настроен. Все индикаторы, которыми мы пользовались в части IV, зелёные:

строки приходят         ~33 000 в секунду
кадры размечаются       флаг «кадр принят» появляется
канал захвата           running, кадры досчитываются
картинка на мониторе    живая

И при этом на экране рваные полосы, а измерение частоты ошибок даёт ошибку контрольной суммы полезной нагрузки в ста окнах из ста по десять миллисекунд.

Обратите внимание, почему это новый класс проблемы. В части IV отказы были бинарными: пакетов нет, кадров нет, поток встал. Здесь всё работает, и «работает ли» — неправильный вопрос. Правильный вопрос: с какой частотой ошибается.

Различение из части II, глава 10.3 здесь пригождается впервые: ошибка заголовка означала бы потерю целого фрагмента строки, а ошибка контрольной суммы нагрузки — что байты доехали и часть искажена. Строки на месте, но с шумом.

23.2. Улика: сравнение двух битстримов на одном кабеле

Найти причину помогло то, ради чего в 22.2 сохранены вендорские адреса: один и тот же скрипт измерения можно запустить против чужого и своего битстрима, не меняя ни строки.

Результат оказался предельно ясным: вендорский приёмник за целую секунду измерения не дал ни одной ошибки, наш ошибался в каждом окне. Одна камера, один кабель, один способ измерения — значит, дело не в физике шлейфа и не в сенсоре, иначе страдали бы оба.

23.3. Причина: задержки, которые никто не расставил

Разница между двумя сборками оказалась в одном параметре приёмника — режиме калибровки входных задержек. У вендора он в положении «фиксированное значение» с номером тапа 5. У нас стоял «никакой», и это означает не «калибровка по умолчанию», а входные задержки не расставлены вовсе: точка выборки оказывается там, куда её занесла трассировка платы.

Арифметика того, почему пять тапов имеют значение, посчитана в части II, глава 9.4: при нашей скорости это примерно шестая часть длительности бита. Мелочь, отделяющая «каждая строка верна» от «одна из нескольких сотен строк испорчена».

Пересборка с фиксированным режимом и тапом 5 подтвердила диагноз полностью: приёмник чист за секунду измерения, ни одной ошибки, 30,8 кадра в секунду. Регистр задержек читается обратно как 0x05050505, окно установления — как заданные 130 нс. То самое «прочитал обратно» из части III, глава 13.3.

23.4. Почему тап нельзя было подобрать на живой плате

Важное следствие, которое стоило отдельного перебора.

При режиме «никакой» регистр задержек читается, но не пишется. Перебор шестнадцати значений даёт шестнадцать одинаковых результатов — и именно так выглядит регистр только для чтения. Соблазн истолковать это как «задержки ни на что не влияют» очень велик, а вывод прямо противоположен правильному.

Отсюда правило, которое уже формулировалось в части II, глава 12.4, а здесь получило цену: прежде чем перебирать значение, убедитесь, что оно вообще имеет эффект. Простейшая проверка — записать и прочитать обратно. Шестнадцать одинаковых результатов — это не данные, это сигнатура неработающей записи.

23.5. Спутник, без которого сборка не проходит

Вместе с фиксированным режимом обязательно идёт разрешение общего блока калибровки задержек, и это не оптимизация, как можно подумать по названию параметра.

Если его не выставить, подсистема расставляет элементы задержки в отдельной группе и не создаёт для них блок калибровки, после чего имплементация останавливается на проверке правил проектирования — ещё до размещения. У вендора этот параметр включён, а отдельного блока калибровки в их дизайне нет нигде; значит, именно это значение заставляет подсистему создать его самой.

Полезная деталь для тех, кто будет собирать похожий приёмник: сборка падает рано, до размещения, так что цена ошибки — минуты, а не сорок минут.

23.6. Правило главы

Параметр, при котором «всё работает», проверяйте частотой ошибок, а не наличием картинки. Приёмник с нерасставленными задержками даёт живое изображение — просто каждая несколько сотая строка в нём испорчена. На статичной сцене это видно как шум; на движении — как рваные полосы; в измерении — как сто окон из ста.

Глава 24. Болезнь вторая: шум, который приходит не из камеры

24.1. Симптом: линк чист, картинка нет

После починки задержек приёмник перестал ошибаться вообще. А на экране остался постоянный шум и мелкая «клетка».

Это самый неприятный момент всего проекта, потому что все инструменты, наработанные к этой минуте, показывают зелёное. Приёмник чист. Кадры размечаются. Канал захвата пишет. Частота кадров правильная. И картинка плохая.

24.2. Соблазн, который был очень хорош

Клетчатая структура — характерный признак неверной фазы цветового фильтра. Гипотеза идеальная: объясняет вид дефекта, проверяется одной записью в регистр, имеет всего четыре варианта.

Перебор всех четырёх фаз её и убил. Мерой служила энергия пиксельной «пилы» — насколько сильно соседние пиксели не связаны друг с другом:

фаза 0: 72     
фаза 1: 72     
фаза 2: 69     
фаза 3: 74

Одинаково. И одинаково огромно: для восьмибитных данных средний модуль второй разности около семидесяти означает, что соседние пиксели не связаны между собой вообще. Так выглядит не неверный демозаик, а шум.

Здесь стоит остановиться на самом приёме. Перебор четырёх фаз мог дать один из двух результатов: одна фаза заметно лучше остальных (тогда дело в фазе) или все четыре одинаковы (тогда дело не в фазе). Второй исход — тоже информация, причём она снимает гипотезу целиком, а не ослабляет её. Измерение, которое умеет опровергнуть свою гипотезу, стоит дороже измерения, которое умеет только подтвердить.

24.3. Приём: подменить таблицу гаммы константой

Дальше пригодилась проверка, которую стоит запомнить как отдельный инструмент.

Гамма-таблица — это соответствие «вход → выход» для каждого значения яркости. Если заполнить её одним и тем же значением, из конвейера обязан выходить равномерно серый кадр — независимо от того, что видит камера, какая стоит фаза фильтра и что происходит на линии.

Правильный ответ известен заранее. Значит, любая неоднородность в памяти приходит откуда-то после гаммы. Режим lut у скрипта живого видео (часть IV, глава 21.5) делает именно это.

Результат: большинство слов в кадровом буфере действительно читались как 0x80808080, но примерно каждое десятое — нет:

0x80808080   ожидаемое
0xBFBA80FF   ?
0x8080FC80   ?
0x00000000   ?

Порча вносится после гаммы, на дороге в память. Камера, приёмник, демозаик и фаза фильтра из списка подозреваемых вышли целиком — одной записью в таблицу.

24.4. Прямой тест памяти

Следующий шаг напрашивался: проверить саму память, не вовлекая видео вообще. scripts/jtag_ddr_test.tcl пишет шаблон и читает обратно при остановленном видеотракте и остановленном процессоре, когда к памяти не обращается никто, кроме самого теста:

наш ps7_init:                  13 628 испорченных слов из 65 536
ps7_init от загрузчика:        ноль

Пятая часть слов приходит изменённой. Никакой камеры в этом измерении нет.

24.5. Причина: память не была сконфигурирована

В блок-дизайне применялся пресет платы «ничего» и все шло на TCL-скриптах в которых "потерялся" блок инициализации подходящей памяти. Инициализация процессорной системы, сгенерированная из этого дизайна, сконфигурировала контроллер под микросхему, которую Vivado предлагает по умолчанию:

было в дизайне

стоит на плате

партномер

MT41J128M8 JP-125

MT41J256M16 RE-125

ёмкость

1 Гбит

4 Гбит

ширина шины

8 бит

16 бит

адресных линий строки

14

15

итоговый объём

512 МБ

1 ГБ

То есть контроллер управлял не теми адресными линиями, с не теми таймингами и с не тем выравниванием по байтовым трактам.

Отдельно про последнее, потому что это легко пропустить. Вместе с партномером обязательно копируются длины дорожек внутри корпуса для каждого байтового тракта: именно от них отталкивается обучение выравниванию при записи и чтении. Партномер сам по себе выставит тайминги, но не выравнивание, и память останется сбоящей при формально верных таймингах.

Именно поэтому в скрипте сборки стоит проверка, о которой говорилось в части III, глава 14.4: верхний адрес памяти обязан быть ровно 1 ГБ. Это одно число, которое следует из всех остальных настроек сразу — четырёхгигабитная микросхема шириной 16 бит на 32-битной шине даёт гигабайт и ничего другого. Сходится оно — сходится и геометрия под ним.

24.6. Почему исходное рассуждение было верным и всё равно ошибочным

Память не настраивали не по забывчивости. Рассуждение было такое: дизайн грузится под Linux, где память поднимает загрузчик задолго до битстрима, значит, мнение блок-дизайна о памяти никем не читается.

Это верно. И перестаёт быть верным ровно в одном случае: когда плату забирают через JTAG. Тогда инициализация, выгруженная из этого же дизайна, становится единственной, кто конфигурирует контроллер — то есть в точности в том режиме, в котором мы отлаживали камеру всю часть IV.

Обобщение, которое стоит унести: у конфигурации бывает больше одного потребителя, и «этот параметр никто не читает» надо проверять по всем режимам работы, а не по основному.

24.7. Главный урок этой части

Он не про память.

Неверно настроенная память не отказывает честно. Код исполняется, картинка появляется, форма изображения правильная — и только пятая часть слов приходит изменённой. На экране это ровно то же самое, что плохой приём с камеры. Три дня можно потратить на входные задержки, стоя не у того пациента.

Разделяет их один дешёвый вопрос: что должно быть в памяти, если конвейер работает правильно? Пока на него нет точного ответа, любое наблюдение картинки — гадание. Как только ответ есть — константа из гамма-таблицы, — вся проверка занимает минуту.

Глава 25. Болезнь третья: две перестановки цвета

25.1. Симптом и почему он оказался ловушкой

Память починили, шум ушёл, изображение стало резким. И цвета неправильные.

Дальше была сделана ошибка, стоившая двух лишних заходов: цвета начали подбирать фазой цветового фильтра. Логика выглядела безупречной — фаза действительно переставляет цвета, вариантов четыре, проверка одна запись.

Ответы приходили всё более странные:

фаза 2  →  «красный и синий поменяны»
фаза 1  →  снова то же самое
фаза 3  →  «зелёный это синий, а синий это зелёный»

Первые две строки противоречат друг другу: фазы 1 и 2 отличаются ровно перестановкой красного с синим, поэтому одна из них обязана была оказаться верной. Третья строка невозможна в принципе — фаза не умеет менять зелёный с синим.

25.2. Причина: перестановок было две

Вторая жила в упаковщике, который складывает три компоненты в 32-битное слово. И пока она была на месте, каждая фаза выглядела неправильно, причём по-разному.

Так и устроена эта ловушка: две независимые перестановки взаимно маскируются. Перебирать одну из них глазами, пока вторая не зафиксирована, бесполезно — ни один вариант не даст правильную картинку, а значит, и обратной связи не будет. Вы получаете четыре неправильных ответа и никакого способа понять, какой из них «менее неправильный».

Это общий случай, и он стоит формулировки: если наблюдаемая величина — композиция двух неизвестных, перебор одного из них не сходится. Нужно либо зафиксировать второе, либо построить измерение, которое от него не зависит.

25.3. Измерение, которое не зависит от фазы

Разорвать круг помогла особенность гамма-блока: таблиц там три, по одной на компоненту. Если одну загнать в максимум, а две другие обнулить, экран заливается ровным цветом — и вопрос «какой это цвет» имеет заранее известный правильный ответ.

Заодно чтение памяти показывает, в какой байт пикселя компонента легла. Три записи, никакой пересборки, полная перестановка как на ладони:

Компонента на выходе гаммы

Байт в памяти

Цвет на экране

0

2

красный

1

1

синий

2

0

зелёный

Читается это так: тракт вывода воспринимает средний байт как синий, а младший как зелёный — не тот порядок, который подсказывают названия. Упаковщик же клал туда зелёный и синий соответственно. Отсюда и «зелёный это синий». Лечится перестановкой двух полей в настройке упаковщика.

Режим colors у скрипта живого видео делает эти три заливки автоматически.

25.4. Почему правильный ответ нельзя было прочитать в документации

Здесь сходятся три независимых соглашения о порядке: порядок компонент на выходе демозаика, порядок байт у упаковщика и порядок байт, в котором сериализатор читает память обратно.

Наблюдаема только их композиция. Каждый блок по отдельности документирован корректно, но чтобы вывести из трёх документов итоговый порядок, нужно не ошибиться ни в одном из трёх толкований — а проверить себя нечем, потому что промежуточные результаты не наблюдаемы.

Измерение тремя заливками экрана даёт ответ за минуту и не требует ни одного толкования. Это ровно тот контракт, о котором предупреждала часть I, глава 8.6: соглашение о порядке байт в кадровом буфере нигде не записано, и каждый новый потребитель этой памяти обязан свериться измерением. В части VI, глава 30 появится второй потребитель — и наступит на ту же перестановку, потому что будет написан «по учебнику».

Глава 26. Болезнь четвёртая: разрывы кадра

26.1. Симптом и почему его нельзя вылечить подгонкой

Последний видимый дефект: на движении изображение разрывается по горизонтали — верхняя часть от нового кадра, нижняя от старого.

Принцип уже разобран в части I, глава 8.4: оба канала ходили через один буфер, вывод шёл вниз по кадру, который захват ещё дописывал. Подгонять частоты бесполезно — один канал привязан к сенсору на 30 Гц, другой к монитору на 60, совпасть они не могут в принципе. Лечится не синхронизацией темпа, а тем, чтобы каналы никогда не оказывались в одной памяти.

Здесь — как это настраивается и на что можно наступить.

26.2. Три буфера и один провод

Захват настраивается ведущим в схеме динамического согласования: закончив кадр, он публикует индекс буфера на своём выходе и пропускает тот буфер, который сейчас читает вывод. Вывод настраивается ведомым: следит за индексом на своём входе и повторяет кадр, когда камера нового ещё не отдала — при 60 против 30 это каждый второй раз.

Ветка сжатия из следующей части подключается к тому же набору буферов обычным ведомым, без динамического режима.

26.3. Три вещи, о которые легко споткнуться

Разрешение согласования нужно на обоих концах. Это отдельный бит в регистре управления каналом. Ведущий без него не пропускает буфер, занятый ведомым, и разрывы возвращаются — хотя провод на месте и всё выглядит настроенным.

Номер ведущего, за которым следует ведомый, — отдельное поле. В прежнем управляющем слове там стояла единица: случайный бит из значения, написанного тогда, когда согласование было выключено и поле никто не читал. Пока выключено — безвредно, и именно поэтому такую мелочь замечаешь только после включения.

Число кадровых ячеек должно совпадать у обоих каналов, потому что публикуемый индекс — это индекс внутри этого набора.

Обратите внимание на природу первых двух ошибок: обе относятся к битам, которые ничего не значили до того, как включили функцию. Это отдельный класс — поля, становящиеся значимыми задним числом. Управляющее слово, собранное когда-то по принципу «остальные биты не важны», после включения новой функции начинает означать что-то конкретное.

26.4. Проверка, которая не требует смотреть на движение

Проверять согласование на глаз необязательно и не стоит. Достаточно прочитать один и тот же пиксель из всех трёх буферов дважды с паузой:

  • меняются все три — захват действительно раскладывает кадры по кругу;

  • меняется один — он пишет в одно место, и картинка будет рваться при любых правильных настройках остального.

Печатается это как LIVE_GENLOCK в конце каждого запуска. Ещё одно измерение с заранее известным правильным ответом, и оно не зависит ни от сцены перед камерой, ни от того, движется ли она.

Глава 27. Что общего у четырёх болезней

27.1. Одинаковый симптом, четыре разные причины

Соберём главы 23–26 в таблицу — она и есть главный результат этой части.

Что было видно

Где оказалась причина

Чем разделили

шум, рваные полосы

входные задержки приёмника не расставлены

частота ошибок вместо «есть ли картинка»

шум, мелкая клетка

микросхема памяти сконфигурирована не та

гамма-константа: кадр обязан быть серым

неправильные цвета

две перестановки байт накладывались

заливка одной компоненты

разрывы на движении

оба канала в одном буфере

чтение пикселя из трёх буферов

Все четыре на экране выглядят похоже. Ни одна не различается по симптому. И каждая была вскрыта не размышлением, а измерением, у которого правильный ответ известен заранее.

27.2. Порядок имеет значение: болезни маскировали друг друга

Второе наблюдение, важное для планирования работы. Эти четыре дефекта нельзя было отлаживать в произвольном порядке.

Пока приёмник ошибался в каждом окне, шум от памяти был неотличим от шума с линии. Пока память портила пятую часть слов, любое суждение о цветах было бессмысленным — измерение заливкой дало бы неверный ответ. Пока упаковщик переставлял байты, фаза цветового фильтра не имела наблюдаемого смысла.

Отсюда практический вывод: отлаживайте от источника к потребителю и на каждом шаге добивайтесь измеримой чистоты, а не приемлемого вида. «Картинка стала лучше» — не критерий; «ошибок ноль за секунду» и «кадр ровно серый» — критерии.

27.3. Что осталось нехорошего

Одно нарушение таймингов, и оно сознательное: минимальная ширина импульса −0,124 нс в десяти точках внутри сериализатора HDMI. Развёртка 1080p60 требует последовательной частоты 742,5 МГц, примитив на этом кристалле аттестован примерно до 680.

Решение оставить его принято не по оптимизму, а по доказательству: вендорский рабочий дизайн гонит эту же плату ровно с теми же таймингами — те же 2200×1125 при 148,5 МГц, те же положения синхроимпульсов, сверено с их исходником — и даёт стабильную картинку. Проверка примитива консервативна, превышение на девять процентов — хорошо изученная территория, и сериализатор поставляется его автором именно для такого применения.

Честная альтернатива, если однажды появятся искры или выпадения пикселей: 1080p30 с пиксельной частотой 74,25 МГц. Последовательная частота падает до 371 МГц, что с запасом внутри аттестации, и вдобавок точно совпадает с частотой сенсора, который всё равно даёт тридцать кадров.

27.4. Отдельно: почему нужен свой файл инициализации

Из этой части вытекает требование, которое сработает в части VII и стоит того, чтобы быть названным здесь.

Инициализация процессорной системы, выгруженная из этого дизайна, — часть дизайна, а не отдельная сущность. В ней сидит конфигурация памяти из главы 24 и настройка трёх частот, одна из которых является опорной для калибровки задержек из главы 23. Чужой файл, даже от той же платы, настраивает две частоты из трёх и конфигурирует другую память.

То есть обе болезни этой части умеют вернуться разом, если подложить не тот файл инициализации. Симптом при этом будет тот же — шум на экране.

Дальше — часть VI: третий потребитель кадрового буфера, энкодер JPEG. Там выяснится, что размер сжатого кадра работает как контрольная сумма арифметики, что чужое ядро не переваривает непрерывный поток, и что измерение на модели иногда ставит диагноз за восемь секунд там, где плата молчит сутки.

Часть VI. Чужое ядро в своём дизайне

К этой точке тракт замкнут: сенсор доезжает до монитора, картинка чистая. Всё, что делалось до сих пор, собиралось из блоков, которые для этого и предназначены. Здесь впервые появляется чужое ядро, написанное не для нашей платы и не для нашего дизайна, — и почти вся глава про то, что происходит на его границе. Шесть вещей пришлось решить снаружи, ни разу не залезая внутрь, и это не аккуратность, а стратегия.

Глава 28. Что вообще можно сжать на этом кристалле

28.1. Почему разговор начинается с отказа

Вопрос, с которого начался этот этап, звучал как «нельзя ли сжимать в H.265 и забирать поток по RTSP». Отсутствие аппаратного кодека в этом кристалле уже названо в части 0, глава 2.10; здесь стоит показать, почему не проходят и два обходных пути, потому что оба выглядят разумно.

Кодировать процессором. Арифметика уже посчитана в части I, глава 5.2: на оба ядра приходится порядка двадцати одного такта на пиксель, и это весь бюджет, включая чтение и запись. Кодек современного стандарта в такой бюджет не попадает на порядок.

Синтезировать кодер в логике. Внутрикадровое предсказание с полным набором режимов, фильтр деблокинга и адаптивное смещение выборки — это самостоятельный проект, больше всего описанного в этой серии вместе. И он не поместится: у нас 53 200 логических таблиц и 220 умножителей, из которых четверть уже занята камерой и дисплеем. Даже если бы поместился, отлаживать одновременно камеру и кодер — способ не закончить ни то, ни другое.

28.2. Почему MJPEG проходит

Ключ в том, чего в этом формате нет. Каждый кадр сжимается независимо: нет предсказания между кадрами, а значит, нет ни буферов опорных кадров, ни оценки движения. Именно эти две части и делают современные кодеки большими — и по логике, и по памяти, и по сложности отладки.

Цена честная: при одинаковом качестве MJPEG требует примерно впятеро больше полосы, чем H.264. Для гигабитной сети это приемлемо — измеренные позже 50 Мбит/с составляют двадцатую часть канала.

Побочная выгода, которая пригодится в части VIII: полезная нагрузка JPEG для протокола реального времени стандартизована, а сам JPEG понимает любой плеер и любой браузер. То есть выбор формата заодно снимает вопрос «чем это смотреть».

28.3. Как выбирают чужое ядро

Раз своё писать не будем, стоит проговорить критерии — они пригодятся и в других проектах.

Критерий

Почему важен

Как у mjpegZero

Интерфейсы стандартные

иначе обвязка превращается в проект

потоковый вход и регистровый интерфейс

Лицензия позволяет

иначе всё остальное неважно

свободная

Есть охарактеризованная конфигурация

цифры автора можно проверить

есть, и это оказалось важно (30.1)

Есть модель и стенд

диагноз без платы

есть, и это спасло этап (глава 32)

Автор описал ограничения

меньше сюрпризов

частично — главное ограничение нашлось само

Последняя строка — не претензия. Ограничение, из-за которого ядро не переваривает непрерывный поток (глава 31), для его исходного применения просто не возникало. Это нормальная ситуация с чужим кодом: вы попадаете в режим, который автор не рассматривал, и узнаёте об этом сами.

28.4. Чего стоил энкодер

Числа занятости приведены в части 0, глава 2.4. Здесь полезно их истолковать.

Самый дорогой ресурс оказался не логика, а блочная память: она выросла примерно на семнадцать процентных пунктов. Это буфер восьми строк, который нужен для блочного преобразования, плюс таблицы квантования и коды Хаффмана. Умножители подскочили почти с нуля — это преобразование цветовых компонент и само преобразование блока.

Вывод для планирования: у сжатия в логике основной расход — память, а не вентили. Если бы блочной памяти в кристалле было мало, разговор про MJPEG тоже пришлось бы закрыть, несмотря на свободные логические таблицы.

Глава 29. Граница между ядром и дизайном

29.1. Шесть вещей, которые решаются снаружи

Между ядром и работающим блок-дизайном стоит шесть несоответствий. Полезно увидеть их списком сразу, потому что это типовой набор, а не особенность именно этого ядра.

Несоответствие

Как решено

ядро ждёт YUV, конвейер даёт RGB

преобразование отдельным модулем перед ядром

ширина входа — производный параметр

не переопределяем его вообще

адрес регистров пять бит, инструментам нужна страница

расширение адреса в обвязке

у выхода нет сигнала готовности

обвязка принимает готовность и фиксирует потерю байта

ядро не переваривает непрерывный поток

ворота на входе

ядро предполагает свой порядок байт

параметр перестановки

Всё это живёт в rtl/mjpeg_wrap.v и rtl/rgb_to_yuyv.v — двух файлах, которые целиком существуют ради границы.

29.2. Правило: обвязка снаружи, а не правка внутри

Соблазн поправить чужое ядро возникает на каждом из шести пунктов, и он обоснован ровно наполовину: технически это часто проще. Против него два довода, и оба практические.

Локальная правка невидима и недолговечна. Каталог с ядром — клон чужого репозитория. Правка в нём не попадает в историю нашего проекта, не видна в обзоре изменений и исчезает при следующем клонировании. Через месяц вы получите «загадочно переставшее работать» на чистой машине.

Вы не понимаете последствий. Это выяснилось на практике: попытка починить внутреннее замыкание ядра (31.3) сдвинула тупик на шаг дальше и вдобавок изменила размер первого кадра так, что объяснить не удалось. Форк чужого ядра, в котором не понимаешь собственной правки, хуже обхода снаружи.

Отсюда формулировка, которую стоит унести: граница чужого ядра — место, где добавляют приборы, а не заплатки.

29.3. Производный параметр — ловушка, а не настройка

Самая коварная деталь этой главы, потому что она про инструмент, а не про логику.

У ядра есть режим, в котором оно принимает RGB и само переводит его в YUV. Но ширина входного порта в нём вычисляется из этого режима:

parameter VID_DATA_W = RGB_INPUT ? 24 : 16

А ссылка на модуль в блок-дизайне фиксирует ширины портов при первой элаборации. Переопределять параметр, от которого зависит ширина другого параметра, — лотерея: иногда ссылка перечитывает модуль и порт вырастает, иногда нет.

Проигрыш в этой лотерее выглядит так: валидация проходит чисто, а кодируются не те две трети каждого пикселя. Ни одной ошибки, ни одного предупреждения. Это в точности класс молчаливых отказов из части III, только с другой стороны.

Решение — не переопределять его вообще. Ядро остаётся в конфигурации по умолчанию, преобразование выносится наружу. У этого решения есть и вторая причина, про тайминги, и она в главе 30.

29.4. Регистровое окно

У ядра пятибитный адрес регистров. Это честно — регистров шесть. Но назначение адресов в блок-дизайне хочет выдать блоку хотя бы страницу, и все инструменты ниже ожидают, что периферия владеет страницей.

Обвязка расширяет адрес до двенадцати бит. Ядро по-прежнему декодирует младшие пять, поэтому шесть регистров просто повторяются каждые 32 байта по всей странице. Это нормально и никому не мешает — но знать об этом надо, иначе чтение «другого» регистра однажды даст неожиданно знакомое значение.

29.5. Почему в память пишет отдельный канал

Принцип уже сформулирован в части I, глава 8.5: тип канала выбирается по тому, кто знает длину. Здесь — конкретика.

Длину знает только энкодер, и знает он её в момент окончания кадра. Значит, канал должен останавливаться по признаку конца данных от источника, а не по заранее заданной геометрии. Отсюда цепочка:

кадровый буфер → канал чтения → преобразование в YUV → энкодер
→ буфер → канал записи по признаку конца → память

И первая же ловушка на этом пути — арифметическая.

Симптом. Канал записи взведён, выглядит абсолютно здоровым в своём регистре состояния и не принимает ни одного байта.

Причина. Область под готовый кадр была заведена на 8 МиБ, и каналу просили принять ровно столько. Регистр длины у этого канала — 23 бита, максимум 8 388 607 байт. Запись значения 8 МиБ не приводит к ошибке — она заворачивается в ноль, и канал, взведённый на ноль байт, честно не принимает ничего.

Правило. Границы разрядности регистров проверяются до, а не после. Окно уменьшено до 4 МиБ — вдесятеро больше самого большого кадра, который выдаёт это ядро.

29.6. Отдельный порт в память

Готовый файл уходит в память через свой порт, а не через тот, которым пользуется дисплей. Это сознательно: ветка сжатия и ветка показа конкурируют за пропускную способность, и разделение портов убирает один источник взаимного влияния — притом что суммарная полоса, как посчитано в части 0, глава 2.5, с запасом укладывается в возможности памяти.

Глава 30. Арифметика, которую проверяет размер файла

30.1. Почему конверсия вынесена наружу: тайминги

У ядра встроенное преобразование считает все три компоненты за один такт:

Y  =  19595·R + 38470·G +  7471·B + 32768Cb 
   = -11056·R - 21712·G + 32768·B + 32768Cr 
   =  32768·R - 27440·G -  5328·B + 32768

Девять умножений и шесть сложений. Инструмент раскладывает каждую строку на три умножителя, соединённых каскадом без регистра между ними, поэтому критический путь проходит умножение и два сложения подряд: 5,287 нс чистой логики при бюджете 6,667 нс. Итог — отрицательный запас 0,823 нс, и это был единственный нарушенный путь во всём дизайне.

Здесь стоит обратить внимание на деталь, которая объясняет, почему цифры автора ядра не врали. Его собственный результат — небольшой положительный запас на той же частоте — относится к конфигурации по умолчанию, в которой преобразование вообще не создаётся. Мы включили режим, который в характеристике не участвовал.

Мораль: цифры производительности относятся к конфигурации, в которой их измеряли. Включив параметр, вы выходите за пределы того, что автор проверял.

30.2. Как это починено

rtl/rgb_to_yuyv.v делает то же самое в четыре ступени: разбор пикселя, девять произведений каждое в свой регистр, суммирование, сдвиг с ограничением и упаковка.

Регистр на каждое произведение — не произвольное решение: это в точности то, для чего в умножителе есть внутренний регистр результата. Умножение и суммирование перестают делить один такт, и каждое получает свой период целиком.

Цена — один дополнительный такт задержки на пути, где задержка не имеет значения: энкодер за ним всё равно буферизует восемь строк.

Модуль побитно совпадает с преобразованием ядра, включая округление и ограничение, поэтому ядро видит на входе ровно то, чего ждёт эталонная модель его автора.

30.3. Дефект, который не портил картинку

Симптом. Первый запуск на плате: 4 МБ на кадр вместо ожидаемых трёхсот килобайт. И этот файл декодируется в правильную картинку.

Ложная гипотеза. Настройки качества, таблицы квантования, «ядро не сжимает». Сочетание странное — картинка верная, сжатия нет, — и это как раз должно было насторожить.

Улика. Деление на 65536 было записано как срез разрядов [25:16]. Это верно для 26-разрядного знакового регистра, где знак живёт в бите 25. Мои регистры были 27-разрядные, знак в бите 26, и срез его молча терял.

Причина. Яркость этого не заметила: комбинация с положительными коэффициентами никогда не бывает отрицательной. А цветоразностные компоненты отрицательны примерно у половины пикселей, и каждый такой пиксель превращался в большое положительное число. Цветность стала шумом. Шум не сжимается.

Правило. Исправление — честный арифметический сдвиг вместо среза, проверено побитно на модели.

30.4. Размер файла как контрольная сумма

Обобщение предыдущего пункта, и это одно из самых полезных правил всей серии.

Коэффициент сжатия стоит проверять так же, как проверяют контрольную сумму. Он ловит целый класс арифметических ошибок, которые на глаз незаметны, потому что яркость — а её глаз и разбирает в первую очередь — остаётся правильной.

Обратите внимание на структуру этой проверки: она такая же, как гамма-константа из части V, глава 24.3. Есть величина, правильное значение которой известно заранее (кадр 1080p при качестве 75 — сотни килобайт, а не единицы мегабайт), и её расхождение с ожиданием указывает на область поиска.

Измеренные значения, для ориентира:

Качество

Размер кадра

Сжатие к сырому RGB

75

233 КБ

26,7 : 1

85

354 КБ

17,6 : 1

30.5. Та же перестановка цвета, второй раз

В части V, глава 25 порядок байт в кадровом буфере был измерен и зафиксирован. Тракт показа о нём знает. Ветку сжатия добавили позже, и модуль преобразования написали «по учебнику»: старший байт красный, средний зелёный, младший синий.

Симптом на потоке: сиреневый уклон, который легко принять за баланс белого. Я и принял. Точнее описание такое: зелёное показано синим, жёлтое розовым, красное верным — красный на месте, два других переставлены. Это в точности сигнатура из части V.

Проверять на глаз не пришлось. linux/app/fbdump.c снимает кадр из памяти, и корреляция трёх байтовых плоскостей с тремя каналами JPEG того же момента даёт ответ без догадок:

канал JPEG   байт 0   байт 1   байт 2
R            +0.987   +0.951   +1.000
G            +0.985   +1.000   +0.950
B            +1.000   +0.985   +0.986

Единицы стоят по диагонали справа налево: энкодер читает слово буквально так, как написано в модуле, а буфер устроен иначе.

Исправлено параметром перестановки, и по умолчанию он нулевой. Это тоже решение, а не мелочь: модуль с именем «RGB в YUV» не должен молча предполагать чужой порядок байт. Странность живёт в блок-дизайне, рядом с комментарием, который её объясняет, а модуль остаётся переносимым.

Правило, повторяющееся третий раз: соглашение, известное одному потребителю памяти, не известно второму. Каждая новая ветка, читающая кадровый буфер, обязана свериться измерением, а не тем, что кажется естественным.

Глава 31. Ядро, которое не переваривает непрерывный поток

31.1. Симптом

энкодер пишет мегабайты entropy-данных и не останавливается
маркер конца изображения не появляется никогда
канал записи ждёт признак конца данных, которого не будет

Из этого симптома причина не следует никак. «Ядро не останавливается» — это описание, а не диагноз.

31.2. Внутреннее замыкание

Причина нашлась на модели (глава 32), и она устроена как замкнутый цикл.

Энкодер считает закодированные блоки, чтобы понять, когда кадр закончился. Этот счётчик сбрасывается началом каждого приходящего кадра — независимо от того, вышел ли предыдущий кадр из ступеней кодирования и упаковки.

Канал чтения кадрового буфера выдаёт кадры вплотную, без промежутка. Дальше:

счётчик обнуляется каждые 33 мс
↓
до полной суммы не доходит никогда
↓
заголовки следующего кадра ждут импульса «накопилось восемь строк»
↓
импульс приходит раньше закрытия предыдущего кадра и теряется       
↓
без заголовков блоки не пускают в конвейер       
↓
без движения блоков буфер не пустеет       
↓
без пустого буфера нового импульса не будет

Замыкание. Причём каждое звено по отдельности выглядит разумно.

31.3. Попытка починить ядро и почему она откачена

Правка была очевидной: защёлкнуть пропавший импульс, чтобы он не терялся. Она сработала — и сдвинула тупик на шаг дальше. Вдобавок изменился размер первого кадра, причём объяснить это изменение я не смог.

Правка откачена. Аргумент — из 29.2: форк, в котором не понимаешь последствий собственного изменения, хуже обхода снаружи. Непонятое изменение в чужом конечном автомате — это не исправление, а вторая неизвестная в задаче, где уже была одна.

31.4. Обход: ворота на входе

Ядру показывают ровно один кадр, после чего вход закрывают до тех пор, пока ядро само не скажет, что закончило. Три состояния: ждём начала кадра, пропускаем кадр, ждём освобождения энкодера.

Очевидная альтернатива — держать ядро в сбросе между кадрами — неверна: в том же домене сброса лежит регистровый файл, а с ним таблицы квантования. Тогда качество приходилось бы перезагружать программно на каждый кадр.

Попутная деталь, которая объясняет, почему геометрия кадра зашита в параметры: ворота считают пиксели, то есть знают размер кадра из параметра сборки. Ещё одно место, где размер изображения фиксируется при сборке, а не в рантайме.

31.5. Кадры отбрасываются, а не притормаживаются

Тонкое решение, которое стоит объяснить, потому что интуиция подсказывает обратное.

Кадры, пришедшие пока энкодер занят, именно отбрасываются: сигнал готовности остаётся поднятым, а данные никуда не идут. Притормозить источник обратным давлением было бы «аккуратнее» — но этот канал делит порт памяти с трактом дисплея, и канал, зажатый на время целого кадра, — это канал, удерживающий кредиты чтения. Ценой аккуратности стал бы дисплей.

Плата за это решение — частота кадров. Если кодирование кадра длиннее кадрового периода, каждый второй кадр пропадает и на выходе будет половина входной частоты. Насколько всё плохо на самом деле, показывает только измерение.

31.6. Как измеряется частота

Измерять надо по счётчику кадров самого энкодера, без участия канала записи: иначе вы измеряете не энкодер, а связку из энкодера, канала и программы. В скриптах это jpeg_rate, печатается как LIVE_JPEG_RATE.

Измеренное на плате: около 24 кадров в секунду при 30,8 на входе. То есть не половина, которой я опасался, закладывая ворота, — пропускается примерно каждый пятый кадр. При 24 кадрах и 354 КБ поток выходит около 68 Мбит/с.

31.7. Два бита переполнения, и почему одного мало

У выхода ядра нет сигнала готовности. Автор пишет, что это безопасно, потому что сжатие делает выходной поток намного медленнее входного, и в среднем это правда: десять мегабайт в секунду против канала, способного принять больше гигабайта.

Но «в среднем» — не гарантия, а отказ молчаливый: байт, предложенный полному буферу, просто исчезает, и JPEG с дыркой не сообщает о себе вежливо, а декодируется в мусор от дырки и до конца. Поэтому обвязка принимает сигнал готовности, пропускает данные насквозь, как задумано автором, и фиксирует факт потери байта.

Одного флага при этом недостаточно, и причина поучительная. Энкодер не останавливается между кадрами. Как только канал записи закончил передачу, он перестаёт принимать, буфер за энкодером заполняется примерно за миллисекунду, и следующий кадр начинает переполняться — а программа, читающая регистры по миллисекунде на обращение, ещё не успела спросить. Флаг, липкий с момента сброса, оказался бы установлен следующим кадром каждый раз и не говорил бы ничего о кадре, который лежит в памяти.

Поэтому флагов два:

// [0] кадр, который только что закончился, потерял хотя бы один байт
// [1] хоть один байт был потерян когда-либо с момента сброса
output wire [1:0]  ovf;

Первый защёлкивается на признаке конца своего кадра и невосприимчив к тому, что происходит потом. Второй отвечает на вопрос «ломалось ли вообще».

И ещё одна деталь, которая иллюстрирует тему главы: у ядра нет регистра, где эти биты могли бы жить, а добавлять регистр внутрь — снова форк. Поэтому они выведены наружу и подключены ко входному каналу блока GPIO — того же, на котором светят светодиоды. Прибор на границе, буквально.

31.8. Накопительный счётчик и ложная тревога

Последняя ловушка этапа, стоившая ложного диагноза.

Скрипт проверял целостность файла, сравнивая счётчик выданных энкодером байт с числом байт, записанных каналом. Числа не сходились, и вердикт был «файл, возможно, обрезан» — про файл, который на хосте открывался безупречно.

Регистр размера считает нарастающим итогом с момента сброса: на восьмом кадре в нём сумма восьми кадров. Совпасть с одним кадром он может только сразу после сброса, да и то если успеть прочитать раньше, чем начнётся следующий.

Настоящих признаков целостности два, и оба надёжные:

  • канал записи поднимает признак завершения, а в простом режиме он делает это только по признаку конца данных — значит, передача кончилась там, где энкодер объявил конец кадра, а не там, где кончился буфер;

  • покадровый бит переполнения из 31.7 говорит, потерял ли лежащий в памяти кадр хоть один байт.

Правило то же, что и с фазой цветового фильтра в части V: сравнивать имеет смысл только те величины, чью семантику вы проверили, а не те, чьи имена похожи.

Глава 32. Модель дешевле платы

32.1. Чем этот этап отличался по способу отладки

Впервые в проекте диагноз поставила модель, а не плата. Это стоит записать отдельно, потому что до сих пор всё решалось чтением регистров живого железа.

На плате замыкание из 31.2 выглядело как «энкодер пишет четыре мегабайта и не останавливается». Симптом не сужает гипотезы: так же выглядели бы неверная настройка качества, битые таблицы, ошибка в канале записи, неверный размер кадра. И каждая проверка стоит цикла сборки или как минимум подъёма платы.

32.2. Что видно на модели

На кадре 320×64 с синтетическим источником всё воспроизводится за восемь секунд: подаёшь два кадра — выходит один, второй встаёт. Дальше достаточно распечатать внутренние сигналы ядра из стенда, и замыкание видно целиком — то, что в 31.2 изложено списком.

Почему это работает: замыкание — свойство протокола, а не масштаба. Условие «новый кадр приходит раньше, чем предыдущий вышел» не зависит ни от разрешения, ни от частоты. Значит, воспроизводить его на 1080p и на плате не обязательно — достаточно любого размера, на котором модель считается быстро.

Отсюда практический критерий, когда идти на модель: если симптом на плате не сужает гипотезу, а условие дефекта не зависит от масштаба. Оба признака здесь были налицо.

32.3. Два стенда и что каждый доказывает

# побитная сверка преобразования с эталоном
iverilog -o /tmp/tb_yuyv rtl/tb_rgb_to_yuyv.v rtl/rgb_to_yuyv.v && /tmp/tb_yuyv
# весь тракт кодирования вместе с ядром, кадры подряд
# (файлы ядра подключаются из ip_repo/mjpegZero/rtl)

tb_rgb_to_yuyv.v сверяет выход с эталонной моделью побитно — именно он ловит дефект из 30.3, который на картинке не виден. И он же проверяет параметр перестановки: стенд поднимает второй экземпляр модуля с включённой перестановкой и переставленным входным словом и сверяет выходы такт в такт. То есть параметр либо делает ровно то, что обещает, либо симуляция падает.

tb_mjpeg_wrap.v прогоняет весь тракт кодирования, подавая кадры подряд, — именно тот режим, в котором ядро клинит без ворот.

Оба стенда дёшевы: одна команда, секунды счёта. Они окупились в первый же день.

32.4. Лабораторная 3: один JPEG с платы

После make live скрипт умеет закодировать кадр и выгрузить его на хост (аргумент качества — в шапке jtag_camera_live.tcl).

Что должно получиться:

LIVE_JPEG quality=85 dma_wrote=354012 frames=1 overflow=0 dma=idle,ioc
LIVE_JPEG_RATE 24.0 frames per second out of the encoder
LIVE_JPEG_INTACT the transfer ended on the encoder's tlast and nothing overflowed

Файл обязан открываться на хосте и разбираться по маркерам как положено: начало изображения, служебный сегмент, две таблицы квантования, кадровый заголовок, четыре таблицы Хаффмана, начало скана и ровно один маркер конца в самом конце. Геометрия — 1920×1080, ровная по всему полю.

Если строки INTACT нет — не отправляйте это в сеть. Сначала ворота, буфер, длина канала записи.

32.5. Тайминги и занятость с энкодером

По отчётам разведённого дизайна: запас по установлению +0,338 нс, по удержанию +0,015 нс, единственное нарушение — ширина импульса в сериализаторе HDMI, то самое из части V, глава 27.3. Занятость и разбор того, сколько стоит энкодер, — в части 0, глава 2.4.

То есть вынесенное наружу преобразование и ядро в конфигурации по умолчанию закрывают тайминги, а встроенный режим их не закрывал. Решение из 29.3 и 30.1 подтвердилось цифрами.

32.6. Что унести из этой части

  • Граница чужого ядра — место для приборов, а не для заплаток. Шесть несоответствий решены снаружи, ни одного изменения внутри.

  • Цифры производительности относятся к конфигурации, в которой их измеряли.

  • Производный параметр не переопределяют: выигрыш неочевиден, проигрыш молчаливый.

  • Размер сжатого кадра — контрольная сумма арифметики, потому что яркость может быть права, когда цветность уже шум.

  • Флаг состояния должен относиться к тому же объекту, о котором вы спрашиваете — отсюда покадровый бит переполнения рядом с липким.

  • Модель стоит восемь секунд там, где плата стоит цикл сборки — если условие дефекта не зависит от масштаба.

Дальше — часть VII: всё, что доказано по JTAG, должно заработать после нажатия кнопки питания. Это самая дорогая часть серии, и почти все её мины лежат не в камере, а в одном файле инициализации и в одном фронте сброса.

Часть VII. Передача состояния: четыре участника загрузки

В части IV платой владели мы целиком: процессор остановлен, инициализация наша, адресное пространство плоское. Здесь всё это надо получить после нажатия кнопки питания — и появляются четыре независимых участника, каждый со своим представлением о железе. Ни один дефект этой части не находится в камере. Все они — дефекты передачи состояния: кто-то предположил, что другой уже что-то сделал.

Глава 33. Кто теперь распоряжается платой

33.1. Что изменилось по сравнению с отладкой по JTAG

В части IV участник был один — отладчик. Он сбрасывал систему, останавливал процессор, исполнял нашу инициализацию, заливал битстрим и поднимал конвейер. Всё состояние железа создавалось в одном месте и в известном порядке.

После кнопки питания участников четыре, они работают последовательно, и каждый следующий наследует состояние от предыдущего:

BootROM
   находит boot.bin в первом разделе карты и запускает его.  
   Про плату не знает ничего     

U-Boot SPL   ← на Zynq-7000 играет роль первичного загрузчика
   вызывает ps7_init(): контроллер памяти и её тайминги,  
   мультиплексирование выводов MIO, PLL, три частоты FCLK.      

U-Boot  
   читает битстрим с карты и конфигурирует логику,  
   включает преобразователи уровней, импульсирует сброс логики,  
   грузит ядро и описание железа.      
     
ядро Linux  
   применяет описание железа, поднимает драйверы,  
   распоряжается памятью, монтирует корневую систему.      
     
mjpeg_pipe 
   поднимает конвейер и отдаёт поток.

33.2. Три вида передачи состояния, и все три ломались

Полезно различать, что именно передаётся между участниками, потому что дефекты у трёх видов разные.

Что настроено. Частоты, выводы, тайминги памяти. Ломается, когда участник настраивает не то: чужая инициализация процессорной системы (глава 35).

Что не тронуто. Битстрим, который загрузчик залил, и частоты, которые он запрограммировал, должны дожить до программы. Ломается, когда следующий участник их перезаписывает или гасит: делитель частоты в загрузочном скрипте, погашенная ядром тактовая (главы 36 и 37).

Что произошло однажды. Фронт сброса, по которому калибруется приёмник. Ломается, когда участник выполняет похожую операцию вместо нужной: снимает сброс вместо того, чтобы его выставить и снять (глава 37).

Третий вид самый коварный, потому что состояние после него выглядит правильным. Регистры читаются как надо, тактовая на месте, битстрим залит — а калибровки не было.

33.3. Почему участников не пять

Два решения этой части приняты ради того, чтобы не добавить пятого участника.

Отдельного первичного загрузчика от производителя кристалла нет. Его роль играет U-Boot SPL. Два параллельных механизма инициализации процессорной системы — это два источника истины, и когда они расходятся, плата поднимается «наполовину», а понять, какой из них сработал, крайне трудно.

Битстрим грузится из загрузчика, а не из Linux. В ядре есть механизм загрузки конфигурации логики, и он выключен. Причина та же: один механизм вместо нескольких. Условие обратного перехода записано рядом с выключенной опцией — включать её можно, только одновременно убрав шаг из загрузочного скрипта.

33.4. Как это вообще проверять

Правило «записал — прочитай обратно», которое ведётся с части III, глава 13.3, здесь становится защитным механизмом.

mjpeg_pipe при старте читает из процессорной системы все три частоты и отказывается работать, если хоть одна не та:

clocks iopll=1000.0 fclk0=100.000 fclk1=142.857 fclk2=200.000

Это три строки кода, и они превращают многочасовую охоту за шумом на экране в одну понятную строку в консоли. Каждая из трёх проверок появилась после того, как соответствующая частота однажды оказалась неправильной, — и каждая история разобрана ниже.

Глава 34. Buildroot: почему снаружи и почему именно эта версия

34.1. Механизм подключения

Buildroot — отдельный проект со своей версией и сотней тысяч файлов. Внутрь учебного репозитория его класть нельзя: обновление становится невозможным, обзор изменений нечитаемым. Штатный механизм для этого — внешнее дерево: наш каталог описывает плату, пакеты и конфигурацию ядра, а сам Buildroot клонируется рядом.

git clone --branch 2026.02.3 https://gitlab.com/buildroot.org/buildroot.git

Версия обязательна, и не для порядка: конфигурация опирается на символы, которых в старых сериях нет, а один из них при отсутствии молча меняет библиотеку C в собранной системе. Собрать «ближайшим удобным тегом» — получить образ, который не грузится, с причиной в совершенно другом месте.

34.2. Что лежит во внешнем дереве

Что

Зачем

конфигурация платы

закреплённые версии ядра и загрузчика, список пакетов

фрагмент конфигурации ядра

то, что выключено и почему, с условиями обратного включения

описание образа карты

разделы, размеры, порядок

загрузочный скрипт

глава 37

пакет mjpeg-pipe

собирает нашу программу в корневую систему

пакет rk7020-uboot-ps7

подкладывает инициализацию процессорной системы (34.3)

Драйвер камеры в ядре не собирается — его нет, и почему, разобрано в части I, глава 7.4. Шаблон пакета для внешнего модуля ядра лежит на будущее: он понадобится, если однажды захочется прерывание вместо опроса.

34.3. Пакет, который нельзя делать опциональным

Здесь стоит остановиться, потому что механизм неочевиден и однажды уже выстрелил.

Загрузчик выбирает файл инициализации по цепочке подстановок: сначала явно указанный в конфигурации, иначе — файл из каталога того описания платы, с которым собирается загрузчик. Наш проект стартовал от описания отладочной платы ZC702, поэтому без этого пакета SPL молча берёт инициализацию от ZC702.

Насколько «молча» и насколько неправильно — видно из сравнения двух файлов:

эта плата

ZC702

записей инициализации памяти

80

81, и содержимое другое

записей мультиплексирования выводов

71

73

записей настройки тактовых

14

17

регистр конфигурации памяти

0x00001082

0x00001081

Другая конфигурация памяти и другое назначение выводов. Симптомы при этом непрямые: тишина в консоли, сеть с горящим индикатором и нулевым трафиком, карта памяти, которую ядро не находит. Ни один из симптомов не указывает на инициализацию процессорной системы. Все три были наблюдены в предыдущем проекте на этой плате до того, как причина нашлась.

Отсюда практическая проверка, что в образ попал нужный файл, — двоичная: поищите в boot.bin значение 0x1082. Оно должно быть, а 0x1081 — нет.

34.4. Почему пакет подключён к двум шагам сборки

Копирование файла в дерево загрузчика привязано и к шагу конфигурирования, и к шагу сборки. Это не подстраховка ради подстраховки.

Естественное место — конфигурирование: именно тогда применяется фрагмент, указывающий на файл. Но команда пересборки загрузчика не перезапускает шаг конфигурирования. Хук, привязанный только к нему, оставляет в дереве загрузчика устаревшую копию, и ваша правка «не действует» — ровно это и случилось однажды при разработке предыдущего проекта. Привязка ко второму шагу делает копирование идемпотентным и сохраняет честность цикла «поправил — пересобрал».

Глава 35. Один файл инициализации: три роли в одном

35.1. Что этот файл делает и откуда берётся

ps7_init настраивает четыре вещи: контроллер памяти с таймингами, мультиплексирование выводов MIO, умножители частоты и три частоты, отдаваемые в логику.

Генерируется он из конфигурации процессорной системы в блок-дизайне, то есть является выходным продуктом сборки, а не отдельной сущностью (часть III, глава 16.1). Практическое следствие приятное: когда меняется только конфигурация процессорной системы, netlist логики не затрагивается, существующий битстрим остаётся годным, и двух минут регенерации блок-дизайна достаточно вместо сорока минут полной сборки.

unzip -j build/camera/system_wrapper.xsa ps7_init_gpl.c ps7_init_gpl.h \      
      -d linux/uboot/

После каждой регенерации файл требует одной правки: объявления вида int ps7_init() надо превратить в int ps7_init(void). Загрузчик собирается с запретом нестрогих прототипов, а штатное подавление этого предупреждения привязано к штатному имени объекта, которого у файла, подложенного через конфигурацию, нет. В выводе версии 2025.2 таких мест пять; искать их лучше все сразу, а не по одному за сборку, потому что каждая сборка — минута:

rg -n '\(\s*\)\s*[{;]' linux/uboot/ps7_init_gpl.c linux/uboot/ps7_init_gpl.h

Под этот же образец попадают вызовы функций — их трогать не надо, правки нужны только определениям и объявлениям.

35.2. Три неправильных файла и один правильный

Самое полезное в этой главе — понять, что «не тот файл инициализации» бывает трёх разных видов, и каждый ломает своё.

Файл от отладочной платы ZC702 — подставляется сам, если удалить пакет из 34.3. Неправильно всё: память, выводы, тактовые.

Файл от заводского проекта этой платы. Выводы правильные — он месяцами грузил эту плату. Тактовые почти правильные: он настраивает первую и вторую, а третью не трогает вообще. Третья — опорная для калибровки входных задержек приёмника, и без неё калибровка не завершается (часть II, глава 12.1). Последствие не «частота будет другая», а рассыпающаяся картинка, неотличимая от плохого шлейфа. Искать это, глядя на монитор, можно очень долго.

Наш файл, но сгенерированный из блок-дизайна без периферии MIO. Тактовые идеальные. А записей в диапазон регистров мультиплексирования выводов — ноль. Проверяется одной командой:

rg -c "0XF80007" linux/uboot/ps7_init_gpl.c    # было: 0, должно быть порядка тысячи

Что происходит дальше, стоит проследить по шагам, потому что симптом абсолютный. BootROM читает boot.bin с карты своими средствами, не пользуясь настройками выводов, и успешно запускает SPL. SPL исполняет ps7_init — и после этого у процессора нет ни консоли на выводе, ни контроллера карты, ни сетевого контроллера. SPL пытается прочитать следующую ступень с карты, до которой больше не дотягивается, через консоль, которой больше нет.

Плата молчит, и отличить это от «не включилась» невозможно.

Правильный файл — один: наш, сгенерированный из блок-дизайна, в котором сконфигурирована и память, и периферия MIO. Причём периферия там нужна не логике — логика не пользуется ни консолью, ни сетью, ни картой. Она нужна файлу.

Забавная деталь: предупреждение об этом стояло в проекте с первого дня, в шапке скрипта сборки скелета. Мы пришли к той же мине с другой стороны — взяли файл с верными частотами вместо файла с верными выводами, тогда как нужен был один файл с обоими.

Карта выводов взята из вендорского блок-дизайна: консоль на выводах 10–11, сеть на 16–27 с управлением на 52–53, карта на 40–45, флеш на 1–6, USB на 28–39 со сбросом на 13.

35.3. Мина третья: верь выводу, а не файлу

После починки выводов плата заговорила и встала на первой же собственной операции:

U-Boot SPL 2026.01
Silicon version:        3
Trying to boot from MMC1
MMC: no card present
spl: mmc init failed with error: -123

Карта при этом в разъёме: с неё только что прочитан сам SPL, который это печатает.

Противоречия нет, и объяснение — ровно из главы 33. BootROM читает карту вслепую, по фиксированному протоколу, и на вывод определения присутствия не смотрит вовсе. Смотрит на него SPL, и у него ответ отрицательный.

За маршрут этого вывода отвечает отдельный регистр, и сравнение двух файлов разошлось ровно в нём:

заводской (грузится)

наш (не грузится)

регистр выбора вывода

вывод 9

вывод 47

вывод 9

вход с подтяжкой, банк 3,3 В

выход, ничем не занят

вывод 47

не задействован

вход с подтяжкой, банк 1,8 В

Номер 47 я взял из вендорского блок-дизайна, то есть из авторитетного, как казалось, источника. Он неправ. Правы заводской файл, который месяцами грузил эту плату с карты, и здравый смысл: вывод 9 лежит в банке 3,3 В и настроен как вход с подтяжкой — это в точности механический контакт в разъёме, замыкающийся на землю при вставленной карте.

Правило, которое уже формулировалось в части 0, глава 2.8, здесь получило цену: вендорский референсный проект — не описание платы, а один из способов её сконфигурировать, и неиспользованные в нём поля вполне могут содержать умолчания инструмента. Проверяемый источник истины — файл, который на этой плате работает.

Правка — одна строка в скрипте сборки, и пересобирать битстрим ради неё не нужно: стадии блок-дизайна достаточно (35.1).

35.4. Чем заплатили: почему видеотракт получил 142,857 МГц

Включение гигабитной сети перерешало задачу о частотах, и это стоит разобрать, потому что иначе кто-нибудь однажды «починит» это число обратно.

Сеть на гигабите требует ровно 125 МГц. Из частоты умножителя 1800 МГц это целым делителем не получается, поэтому инструмент перевёл умножитель на 1000 МГц — а там недостижимы 150, ближайшее 1000/7 = 142,857.

Общего решения нет: чтобы получить 125, 150 и 200 целыми делителями из одной частоты, она должна быть кратна 3000 МГц, а потолок умножителя — около 2000. Одну из трёх приходится отдать.

Отдали 150, и это оказалось дёшево:

  • конвейер обрабатывает один пиксель за такт против 81,7 миллиона пикселей в секунду от сенсора — запас больше двукратного, а средний расход дисплея, как посчитано в части V, глава 22.3, составляет 129,6 МГц;

  • пиксельная частота HDMI синтезируется из первой частоты, а не из второй, поэтому 148,5 МГц не сдвинулись ни на герц;

  • по таймингам стало только легче.

Единственная реальная плата — около пяти процентов частоты кадров энкодера.

Значение продублировано в программе константой, и программа падает, если реальная частота от неё отличается. Дублирование намеренное: оно превращает рассинхронизацию битстрима и инициализации в понятное сообщение вместо тонко неправильной картинки.

Глава 36. Описание железа для ядра

36.1. Три файла и честная времянка

zynq-rk7020-camera.dts          верхний файл: включения и модель
   xilinx/zynq-zc702.dts         база: «какой-то Zynq-7020»
   zynq-rk7020-base.dtsi         правки ЭТОЙ платы
   zynq-rk7020-fpga-camera.dtsi  память логики и шаблоны узлов

База чужая, и это осознанная времянка. Описание ZC702 даёт рабочее описание процессорной системы, достаточное чтобы загрузиться и работать с логикой, — а логика и есть предмет камерного проекта. Правильный путь — описание, выведенное из заводского проекта; пока эта работа не сделана, честнее иметь короткий файл правок, где каждая правка снабжена симптомом, который она предотвращает, чем большой файл, про который никто не помнит, зачем в нём что.

Верхний файл существует отдельным трёхстрочным файлом не из аккуратности: фрагмент нельзя собрать сам по себе, что-то должно предоставить базу, к которой он прикрепится.

36.2. Промолчать — не то же самое, что отказаться

Главный дефект этой главы, и он показателен.

mjpeg_pipe: sensor id 0xFFFFFFFF, expected 0x5647

Значение 0xFFFFFFFF здесь — не показания датчика, а -1 от системного вызова: транзакция не состоялась вовсе. Выглядит как мёртвый сенсор или отошедший шлейф. Подсказка стоит строкой выше в журнале загрузки:

pca954x 0-0074: probe failed

Микросхема, которой на этой плате нет, не отвечает на шине, до которой не дотянуться. Обе строки — про одно и то же.

Вместе с полезным из описания ZC702 приезжает описание шины I²C для чужой платы: настройка выводов, выводящая контроллер на выводы 50 и 51, линии восстановления шины на тех же выводах и мультиплексор по адресу 0x74. Ядро применяет настройку выводов при подключении драйвера, контроллер начинает слушать два вывода, на которых ничего нет, а камера остаётся на выводах, выведенных в логику битстримом.

Мораль: наследуя чужое описание, недостаточно не упоминать узел — всё лишнее нужно снять явно.

&i2c0 {    
   status = "okay";    
   clock-frequency = <100000>;    
   /delete-property/ pinctrl-names;    
   /delete-property/ pinctrl-0;    
   /delete-property/ pinctrl-1;    
   /delete-property/ scl-gpios;    
   /delete-property/ sda-gpios;    
   /delete-node/ i2c-mux@74;
};

36.3. Правки того же класса, уже оплаченные

Остальные правки базового описания — из предыдущего проекта на этой плате. Полный разбор каждой в ../board_notes.md; здесь таблица, чтобы был виден класс.

Симптом

Причина в чужом описании

полная тишина в консоли от SPL до Linux

консоль назначена на другой контроллер

консоль обрывается на середине строки, ядро живо

настройка выводов переназначает выводы консоли как GPIO

паника сразу после инициализации выводов

таблица частот процессора не соответствует состоянию тактовых

«ожидание корневого устройства» при исправной карте

определение присутствия карты ожидается на другом выводе

индикатор сети горит, трафика нет

настройка выводов навязывает сети другой электрический стандарт

Последняя строка стоит отдельного правила, шире, чем сеть: горящий индикатор соединения — не доказательство работы линий данных. Управление физическим уровнем идёт по отдельному выводу и работает, а приём данных превращается в мусор.

36.4. Тактовая, которую ядро вправе погасить

Тонкое место, и оба его проявления связаны с тем, что модель тактовых в ядре описывает нашу частоту как вентиль, выключаемый установкой бита.

Первое. Тактовую, которую никто из драйверов не запросил, ядро вправе погасить как неиспользуемую. Погашенная частота останавливает видеотракт молча. Лечится указанием держать все три живыми плюс аргументом ядра, запрещающим гасить незатребованные.

Второе, более коварное. Потребитель, который честно указал, что использует эту тактовую, своим запросом на включение может её остановить — потому что для вентиля с обратной логикой «включить» означает записать бит, гасящий частоту. Поэтому у узлов логики в описании намеренно нет свойства с указанием тактовой. Это выглядит как забывчивость и является решением, записанным в комментарии рядом.

Симптом в обоих случаях один: встроенный драйвер доходит до подключения, обращается к логике, ответа нет, и процессор зависает без единого сообщения. Именно поэтому узлы логики по умолчанию выключены: сначала убедитесь, что логика отвечает, из пользовательского пространства, где зависание переживаемо (часть III, глава 16.6).

36.5. Память, которую нельзя отдавать ядру

Контракт, отложенный в части I, глава 8.8, здесь становится конкретным:

reserved-memory {    
   video@10000000 { reg = <0x10000000 0x01200000>; no-map; };  /* 3 × 6 МиБ */
   jpeg@18000000  { reg = <0x18000000 0x00400000>; no-map; };  /* 4 МиБ */
};

Оба слова важны, и по разным причинам.

Резервирование нужно потому, что логика пишет в физическую память по адресам, зашитым в битстрим, а распределитель страниц ядра про это не знает. Без резервирования портится сначала картинка, и только потом ядро — то есть симптом появляется на стороне, которая ни при чём.

no-map дополнительно убирает область из линейного отображения ядра, и поэтому отображение через /dev/mem получается некэшированным: чтение видит то, что записала логика. Кэшированное отображение возвращало бы устаревшие строки без единой ошибки, а «первые несколько кадров декодируются, дальше нет» — крайне неприятный симптом. Тем же свойством область перестаёт считаться обычной памятью для проверки прав доступа, так что работа возможна и при включённом ограничении доступа к физической памяти.

Адреса дублируются в трёх местах: здесь, в программе и в скриптах. Менять — все три в одном коммите.

Глава 37. Загрузочный скрипт: две строки, которые всё решают

37.1. Две мелочи про загрузку битстрима

Обе оплачены в предыдущем проекте и обе дают короткое загадочное сообщение, а настоящий симптом появляется позже и в другом месте.

Адрес загрузки. У используемой конфигурации загрузчика штатная переменная адреса равна нулю, поэтому команда с ней падает с жалобой на нулевой адрес и оставляет логику незапрограммированной. Используется другая переменная.

Формат файла. Файл от Vivado имеет заголовок, поэтому нужна команда загрузки с распознаванием заголовка, а не «сырая»: иначе заголовок зальётся как конфигурация.

Настоящий симптом в обоих случаях — молчаливое зависание ядра на первом обращении к логике.

37.2. Константа, которую надо было удалить, а не комментировать

Симптом. Плата загрузилась, сеть поднялась, программа запустилась — и первой же строкой сообщила, что первая частота 55,556 МГц вместо 100.

Улика. Инициализация процессорной системы пишет туда правильные делители, проверено в самом boot.bin. Значит, переписал кто-то после SPL.

Причина. В загрузочном скрипте стояла запись делителей: значение, означавшее 100 МГц при умножителе 1800 МГц. Когда включение сети увело умножитель на 1000 (35.4), та же константа стала означать 55,6 МГц и молча затёрла то, что только что записал SPL.

Правило. Комментарий рядом честно предупреждал, что константа непереносима между дизайнами. Он был прав — и всё равно недостаточен: непереносимую константу не надо комментировать, её надо удалить. Значение уже задаётся блок-дизайном, а два места для одного числа — на одно больше, чем нужно.

Коварство здесь в том, куда уходит эта частота. Она тактирует всю сторону регистров — они читаются и пишутся как ни в чём не бывало, просто медленнее — и одновременно является входом умножителя, из которого синтезируется пиксельная частота HDMI. При 55,6 вместо 100 на выходе получается 82,5 МГц вместо 148,5 — режим, который не опознает ни один монитор. Ни одной ошибки при этом нигде.

37.3. Главная мина части: снять сброс и сбросить — разные операции

Симптом. Самый безнадёжный в проекте, потому что не выглядит никак: сенсор - отвечает по I²C, идентификатор верный, настроен на 1920×1080, D-PHY - обе линии и тактовая инициализированы, ни одного флага аварии, cчётчик посылок растёт на 35 тысяч в секундуконтроллер CSI-2 ноль пакетов и НИ ОДНОЙ ошибки.

Снять питание с камеры — счётчик замирает; вернуть — растёт снова. Значит, считает он именно сенсор. Все регистры обоих ядер читаются ровно так, как должны.

Что было проверено и оказалось ни при чём. Перебор окна установления от 20 до 220 нс с шагом 10 — ноль на всём диапазоне, включая заведомо верные значения из расчёта части II. Все четыре комбинации режима тактовой линии сенсора — ноль в каждой. Домен обработки жив: демозаик на том же клоке и том же сбросе хранит размеры кадра и отвечает на запись. Отчёт таймингов — нарушена только ширина импульса в сериализаторе HDMI, к приёму отношения не имеющая.

Улика. Отсутствие ошибок при растущем счётчике посылок — уже знакомая сигнатура из части II, глава 10.5: байты не доходят до разбора. Но окно установления перебрано целиком. Остаётся то, что настраивается однажды — калибровка входных задержек.

Причина. В загрузочном скрипте стояла строка, снимающая сброс логики, — и ничего, что бы его выставляло. Загрузчик сам об этом предупреждает в журнале: шаг, который дёргает этот регистр, не выполнялся. Без выставления логика выходит из конфигурации с уже снятым сбросом, и каждый блок сброса в дизайне стартует от сигнала, у которого не было фронта.

Большинству блоков всё равно. Приёмнику MIPI — нет: его калибровка входных задержек происходит ровно один раз, при выходе из сброса (часть II, глава 12.1). Пропущен фронт — задержки остаются какими проснулись, и связь не поднимается никогда, молча.

Правило. devmem 0xF8000008 32 0xDF0D, затем выставить и снять:

devmem 0xF8000240 32 0xF

devmem 0xF8000240 32 0

Две записи разделены микросекундами, а калибровке нужны десятки наносекунд. В загрузочном скрипте теперь выставление и снятие, а не одно снятие.

После этой правки конвейер поднялся с первой попытки: тип данных 0x2B, 2400 байт на строку, 33 061 строка в секунду, 30,6 кадра, признак завершённого кадра у канала захвата.

37.4. Почему импульс сброса делает ещё и программа

Из той же истории вытекает второе следствие, и оно объясняет строчку в mjpeg_pipe.

Контроллер CSI-2 не умеет перезахватывать уже работавшую связь — свойство, найденное ещё в части IV, глава 19.4. Второй запуск программы подряд снова читал нули, пока приёмник под ним продолжал считать посылки: состояние линий защёлкивается при выходе из сброса и переживает перезапуск программы.

Поэтому импульс сброса логики программа делает сама, первым делом — но после остановки всех мастеров на шине. Сброс, выставленный посреди пачки транзакций канала DMA, оставляет порт памяти ждать биений, которых уже не будет, и порт залипает до сброса всей процессорной системы.

37.5. Обобщение

Три мины этой главы складываются в одно наблюдение: у операции бывает потребитель, которого вы не видите.

У фронта сброса есть потребитель — калибровка. У частоты, записанной SPL, есть потребитель — умножитель пиксельной частоты. У порядка «сначала остановить мастеров» есть потребитель — порт памяти. Ни один из них не сообщает о своём существовании, и все три ошибки выглядят как проблемы совсем в другом месте.

Глава 38. Сборка образа и первая загрузка

38.1. Команды

make bit                            # если битстрима ещё нет
make BUILDROOT=<путь> br-defconfig
make BUILDROOT=<путь> br            # первый прогон около трёх часов

Переменную можно не указывать, если путь совпадает со значением по умолчанию в Makefile. Три часа — это сборка инструментальной цепочки с нуля; повторные сборки существенно быстрее.

Запись на карту, устройство проверить дважды — команда не переспрашивает:

sudo dd if=<buildroot>/output/images/sdcard.img of=/dev/sdX \
     bs=4M conv=fsync status=progress

Перемычки загрузки — в положение карты. Консоль 115200 8N1.

38.2. Контрольные точки первой загрузки

Что должно появиться, по порядку:

Признак

Если нет

баннер SPL с версией кремния

инициализация: 35.2, проверка 0x1082

нет строки «карта не вставлена»

вывод определения присутствия: 35.3

приглашение загрузчика

питание, карта, разделы

PL configured

битстрим на разделе, формат команды: 37.1

загрузка ядра, приглашение системы

описание железа: глава 36

сеть отвечает

36.3

три верные частоты в выводе программы

35.2 и 37.2

38.3. Чек-лист соответствия

Перед первой загрузкой сверьте, что четыре вещи описывают одно и то же железо:

  1. битстрим на загрузочном разделе — из текущей сборки;

  2. инициализация процессорной системы в образе — из той же сборки;

  3. адреса в описании железа для ядра — из карты адресов, напечатанной сборкой;

  4. константы частот в программе — из таблицы тактовых сигналов отчёта.

Три из четырёх расхождений не дадут ни одной ошибки сборки. Все четыре дадут шум на экране или чёрный монитор.

38.4. Заметка про среду сборки

Если сборка Buildroot встала на копировании нескольких файлов локального пакета — дело не в Buildroot. В песочнице предыдущего проекта инструмент синхронизации, которым копируются локальные пакеты, зависал наглухо.

Дальше — часть VIII: одна программа в пользовательском пространстве делает то, что делал скрипт по JTAG, и отдаёт кадры в сеть. Там же — почему сначала HTTP, а потом UDP, и что именно надо измерять, чтобы не обмануться.

Часть VIII. Поток: программа, транспорт и цепочка частот

Всё, что доказано в частях IV–VII, здесь превращается в одну программу. Она не изобретает ничего нового — она переводит последовательность подъёма из скриптов отладчика в код, который исполняется после кнопки питания. И отдаёт кадры в сеть. Главный технический сюжет части — цепочка частот: 30,6 кадра в секунду у приёмника, около 24 на выходе энкодера, около 23 в сети, и каждая потеря по своей причине.

Глава 39. Программа как исполняемая форма доказанного

39.1. Чем этот файл не является

linux/app/mjpeg_pipe.c — не драйвер, не библиотека и не демон в обычном смысле. Это перевод: каждое значение регистра, каждый порядок операций и каждый обход в нём установлены сначала по JTAG и объяснены в предыдущих частях.

Такая постановка задачи звучит скромно и была принята сознательно. Когда вы ещё не уверены, что окно установления приёмника выбрано верно, возможность сверить две реализации строка к строке стоит дороже любой абстракции. Файл заявляет в шапке ограничения, на которые опирается, и не переспоривает их заново.

39.2. Соответствие скрипту

Что делал скрипт по JTAG

Что делает программа

сброс системы, ранняя остановка ядра

не нужно: система уже загружена

исполнение инициализации процессорной системы

не нужно: это сделал загрузчик

чтение трёх частот обратно

то же, и отказ при расхождении

заливка битстрима

не нужно: это сделал загрузчик

импульс сброса логики

то же, но после остановки мастеров

светодиоды как проба моста

то же

тайминги и канал показа

то же

демозаик и гамма

то же

шина I²C, цикл питания модуля, идентификатор

то же, через /dev/i2c-0

таблица режима сенсора

то же

взведение канала захвата, размер кадра

то же

разрешение передачи

то же

измерение строк и кадров

то же, и печать

съём кадра JPEG в файл

плюс отдача в сеть

Совпадение построчное не случайно: именно оно позволяло, увидев расхождение поведения, за минуту понять, отличается ли программа от доказанного скрипта.

39.3. Три проверки при старте — это накопленная защита

При запуске программа печатает и проверяет частоты:

clocks iopll=1000.0 fclk0=100.000 fclk1=142.857 fclk2=200.000

Каждая из трёх проверок появилась после конкретного дефекта, и полезно видеть их вместе — это компактная сводка самых дорогих ошибок серии:

Проверка

Что ловит

Где разобрано

третья частота = 200 МГц

чужая инициализация; калибровка задержек невозможна

VII.35

вторая = 142,857 МГц

битстрим и инициализация из разных сборок

VII.35

первая = 100 МГц

кто-то переписал делители после SPL; режим HDMI уезжает

VII.37

Дальше в том же духе: программа отказывается работать, если не открывается шина I²C (значит, узел не включён в описании железа) и если идентификатор сенсора не тот (значит, не тот модуль или мёртвая шина — IV.18).

Смысл всех этих отказов один: первое несоответствие останавливает программу с текстом причины, а не оставляет разбираться с чёрным экраном. Стоит это несколько строк кода и заменяет часы охоты.

39.4. Чего в программе нет намеренно

Драйвера ядра нет, и это решение из части I, глава 5: в видеотракте процессор не участвует, поэтому остаются запись регистров и копирование готового файла — то, что пользовательское пространство делает через отображение физической памяти. Условия, при которых драйвер вернётся, перечислены там же, и глава 43 добавит к ним измеренный аргумент.

V4L2 нет по причине из части I, глава 7.4: ни одна из трёх задач, ради которых он существует, здесь не возникает. В конфигурации ядра он оставлен закомментированным с условием включения.

39.5. От чего программа зависит

Список короткий, и каждый пункт — результат предыдущих частей:

  • битстрим уже в логике (это делает загрузчик, VII.37);

  • три частоты правильные (VII.35);

  • кадровые буферы и область под сжатые кадры забраны у ядра с запретом отображения (VII.36) — иначе чтение видело бы устаревшие строки кэша;

  • узел шины I²C включён, устройство доступно.

Нужны права суперпользователя и включённый доступ к физической памяти.

Глава 40. Дисциплина двух слотов

40.1. Почему один буфер даёт битые кадры

Ошибка, найденная до первой прошивки, но стоящая разбора, потому что вылезла бы редкими испорченными кадрами — худший вид дефекта.

Логика была такой: дождаться конца передачи, взвести канал заново, отправить кадр. Взводить надо немедленно — энкодер не ждёт, — а отправка 275 КиБ в сокет занимает миллисекунды. С одним буфером канал пишет следующий кадр в ту же память, которую читает отправка, и получатель видит голову одного кадра и хвост другого.

Область поделена на два слота, используемых по очереди. Стоит это ничего: слот вшестеро больше самого большого кадра, который выдаёт энкодер.

40.2. Порядок в цикле, и почему взведение раньше чтения

1. дождаться признака завершения передачи
2. прочитать число записанных байт
3. прочитать покадровый бит переполнения
4. ЗАПОМНИТЬ адрес текущего слота
5. переключить слот и ВЗВЕСТИ канал на него
6. только теперь отправлять кадр из запомненного адреса

Пункты 5 и 6 нельзя менять местами. Всё после пятого может занимать миллисекунды, а энкодер не ждёт: ворота на его входе (VI.31) оставляют его простаивающим между кадрами, поэтому время есть — но только если взведение произошло первым.

Обратите внимание, что это тот же принцип, что и в части IV, глава 21.1: адреса и геометрия раньше бита запуска. Разница лишь в том, что здесь «раньше» относится к отправке в сеть.

40.3. Единственный признак целого файла

Программа ждёт признак завершения передачи у канала записи, а в простом режиме он выставляется только по признаку конца данных от энкодера. То есть передача кончилась там, где энкодер объявил конец кадра, а не там, где кончился буфер. Обоснование — часть VI, глава 31.8.

Ожидание с ограничением по времени: две секунды, опрос раз в миллисекунду. Если признака нет — канал сбрасывается, взводится заново и цикл продолжается. Это не косметика: остановленный по ошибке канал не оживает повторной установкой бита запуска, ему нужен сброс (IV.20).

40.4. Две проверки перед отправкой

Перед тем как отдать кадр в сеть, программа смотрит две вещи.

Покадровый бит переполнения — тот, который защёлкнут на признаке конца своего кадра и невосприимчив к тому, что произошло потом (VI.31.7). Если он поднят, кадр потерял хотя бы один байт, и такой кадр не отправляется, а считается отброшенным.

Первые два байта должны быть маркером начала изображения. Проверка тривиальная и ловит целый класс ситуаций, когда в слоте лежит не то, что мы думаем.

Оба бита читаются, кстати, не из регистра энкодера — у него нет для них регистра, — а из входного канала блока GPIO, куда обвязка их вывела (VI.31.7).

40.5. Что происходит, когда никто не смотрит

Пока клиент не подключился, программа ждёт соединения. Энкодер при этом продолжает работать, а его выход отбрасывается — именно для этого и нужен бит переполнения. Остановить энкодер было бы «экономнее», но тогда его пришлось бы заново синхронизировать при подключении, а кадры дешёвы.

При подключении клиента канал сбрасывается и взводится на нулевой слот, чтобы первый кадр зрителя начинался на границе кадра, а не там, где застал отбрасываемый поток.

Запрос клиента при этом не разбирается — отдавать нечего, кроме одного потока по одному адресу, — но прочитать его надо: иначе клиент ждёт, пока его запись кто-нибудь заберёт.

40.6. Почему всё это работает без блокировок

Полезное наблюдение об архитектуре, которое объясняет, почему программа однопоточная и в ней нет ни одного примитива синхронизации.

Производитель один и он в железе. Потребитель один и он в программе. Владение слотом передаётся ровно одним действием — взведением канала на другой слот, — и это действие атомарно с точки зрения обеих сторон: до него слот принадлежит железу, после него — программе.

То есть дисциплина двух слотов — не оптимизация, а способ сделать так, чтобы синхронизация вообще не понадобилась.

Глава 41. Транспорт первого захода: MJPEG поверх HTTP

41.1. Критерий, по которому выбран транспорт

Просили поток по RTSP/UDP. Сделан на первом заходе MJPEG поверх HTTP, и это последовательность, а не упрощение навсегда.

Критерий выбора — тестируемость. Полезная нагрузка JPEG для протокола реального времени требует резать кадр на фрагменты и снабжать каждый своим заголовком. Делать это осмысленно тогда, когда уже известно, что сами кадры доходят по сети целыми. Иначе первый же артефакт у зрителя порождает вопрос, в котором два неизвестных сразу: кадр битый или нарезка неверная. Это ровно та ситуация с двумя наложенными неизвестными, которая в части V, глава 25.2 стоила двух заходов.

HTTP проверяется тем, что и так стоит на любой машине: браузером, плеером, curl. А перепаковать его в UDP можно на хосте одной командой (глава 42) — то есть требование «поток по UDP» закрывается уже сейчас, без риска отлаживать два неизвестных одновременно.

41.2. Что уходит в сокет

HTTP/1.0 200 OK
Cache-Control: no-store
Pragma: no-cache
Connection: close
Content-Type: multipart/x-mixed-replace; boundary=frame

--frameContent-Type: image/jpeg
Content-Length: <n>

<n байт файла JFIF>
--frame
Content-Type: image/jpeg
Content-Length: <n>
...

Каждая часть — законченный файл от маркера начала до маркера конца. Приёмник, умеющий этот тип содержимого, показывает живое видео; тип придуман для обновляемых картинок и поддерживается давно и широко.

Ключевое свойство формата для нас: он не требует ничего знать о предыдущих кадрах. Клиент может подключиться в любой момент и начать с любого кадра. Это прямое следствие выбора MJPEG из части VI, глава 28.2 и причина, по которой при подключении достаточно взвести канал на границу кадра.

41.3. Что программа сознательно не делает

  • Не разбирает запрос. Один поток, один адрес.

  • Не обслуживает несколько клиентов. Очередь на одно соединение, следующее принимается после обрыва. Для лаборатории достаточно; несколько зрителей — прокси на хосте.

  • Не умирает от ушедшего клиента. Сигнал разорванного соединения игнорируется, обрыв обрабатывается как закрытие соединения и ожидание нового.

41.4. Проверка с хоста

ffplay http://<адрес платы>:8080/
vlc    http://<адрес платы>:8080/
curl -N http://<адрес платы>:8080/ > /tmp/mjpeg.bin

Программа на плате раз в две секунды печатает частоту, битрейт и счётчики отправленных и отброшенных кадров.

Глава 42. UDP: перепаковка сегодня, нативный путь завтра

42.1. Поток по UDP одной командой на хосте

gst-launch-1.0 souphttpsrc location=http://<адрес платы>:8080/ \
   ! multipartdemux ! rtpjpegpay ! udpsink host=<получатель> port=5004

У получателя — файл описания сессии:

v=0
m=video 5004 RTP/AVP 26
c=IN IP4 <адрес получателя>
a=rtpmap:26 JPEG/90000
ffplay -protocol_whitelist file,udp,rtp -i stream.sdp

Это и есть «поток по UDP» в смысле цели проекта: сжатые на плате кадры едут датаграммами. Сессионного протокола на плате нет, и он не нужен — поток открывается описанием сессии.

Важно, что перепаковка не прячет дефекты: если кадр битый, упаковщик честно повезёт битый кадр. То есть проверка целостности из главы 40 остаётся единственной и работает для обоих транспортов.

42.2. Почему энкодер не отдаёт UDP сам из логики

Вопрос закономерный: у автора использованного ядра есть пример, где кадры уходят в сеть прямо из логики, минуя процессор.

Здесь так нельзя. Физический уровень сети этой платы висит на выводах процессорной системы и в логику не выведен вовсе (часть 0, глава 2.10). Собственный сетевой контроллер в логике было бы некуда подключить. Поэтому транспорт — через сокет в Linux, а процессору достаётся то, что ему по силам: 6 мегабайт в секунду вместо 380 (часть I, глава 5.4).

42.3. Как устроена нагрузка RTP для JPEG

Если однажды понадобится нативный UDP из программы, вот структура, чтобы не изобретать её заново. Полезный груз описан стандартом RFC 2435, тип нагрузки 26.

Кадр не влезает в одну датаграмму: при типичном размере пакета 1500 байт заголовки съедают десятки байт, и на фрагмент скана остаётся около 1400.

IP | UDP | RTP (12 байт) | заголовок JPEG (8 байт)
                         | [только в первом пакете кадра: таблицы квантования]
                         | фрагмент данных скана

Что важно не перепутать:

  • маркерный бит RTP ставится на последнем пакете кадра;

  • метка времени одна на весь кадр, шкала 90 кГц;

  • смещение фрагмента в заголовке JPEG считается внутри данных скана, а не внутри файла. Перепутать со смещением в файле — типичная ошибка, и выглядит она как серый прямоугольник у зрителя;

  • размеры передаются в макроблоках: 1920/8 = 240 и 1080/8 = 135, оба укладываются в ограничение стандарта;

  • признак того, что таблицы квантования идут в потоке, а не берутся из фиксированного набора, — отдельное поле;

  • данные скана берутся после маркера начала скана вместе с байтовой подстановкой и без маркера конца.

Заголовок файла, который выдаёт наше ядро, имеет стабильную структуру, поэтому границы скана и положение таблиц находятся детерминированно.

42.4. Что нативный путь даст и что заберёт

Даст: меньшую задержку (нет промежуточного HTTP на хосте) и независимость от хоста-перепаковщика.

Заберёт: простоту проверки. Отладка станет требовать анализатора трафика или приёмного скрипта вместо браузера. Поэтому разумный порядок — оставить HTTP как диагностический выход даже после появления нативного UDP.

42.5. Чего нет и не обещано

Сессионного протокола на плате, звука, адаптивного битрейта, нескольких клиентов без прокси. Цель «рабочий вывод HDMI и поток UDP/HTTP» закрыта связкой «HTTP на плате плюс перепаковка на хосте»; нативный UDP — очевидное следующее упражнение.

Глава 43. Цепочка частот: где что теряется

43.1. Три числа и три разные причины

Это главный количественный результат части, и он полезнее любой из отдельных цифр.

Точка измерения

Частота

Почему меньше предыдущей

приёмник CSI-2

30,6 к/с

режим сенсора: 1080 строк при заданном полном размере кадра

выход энкодера

≈ 24 к/с

ворота на входе: пропускается примерно каждый пятый кадр

поток в сети

≈ 23 к/с

копирование кадра из памяти в сокет

Измерено на живой плате: за восемь секунд curl забрал 182 кадра, то есть 22,8 в секунду, средний кадр 275 КиБ при качестве 75, поток около 50 Мбит/с, и все 182 кадра различны.

Средний кадр здесь больше, чем 233 КБ из части VI, глава 30.4, и это не противоречие: там один кадр, снятый на стенде, здесь среднее по 182 кадрам живой сцены. Размер сжатого кадра зависит от содержимого, поэтому у него нет одного «правильного» значения — есть порядок величины, и по нему судят.

Главное же в таблице то, что каждая потеря имеет свою причину и своё лекарство. Складывать их в одно «медленно» — верный способ оптимизировать не то.

43.2. Сеть не виновата

50 Мбит/с против гигабитного канала — двадцатая часть. Прежде чем подозревать сеть, стоит посчитать: если бы упиралось в канал, битрейт стоял бы у гигабита, а не у пяти процентов от него.

43.3. Где предел на самом деле

Остаётся копирование. Программа читает 275 КиБ из отображённой физической памяти и пишет их в сокет; при 23 кадрах в секунду это около 6 МБ/с.

Шесть мегабайт в секунду — немного для процессора, и здесь стоит назвать причину, потому что она поучительна: чтение идёт из некэшированного отображения. Именно эту некэшированность мы обеспечили запретом отображения области в части VII, глава 36.5 — ради того, чтобы чтение видело записанное логикой, а не устаревшие строки кэша. То есть свойство, которое даёт корректность, стоит производительности.

Гипотеза проверяется дёшево: если предел определяется байтами, а не кадрами, то снижение качества (меньший кадр) должно двигать частоту напрямую. На плате так и происходит.

43.4. Что двигать, если нужно больше

Что нужно

Что сделать

Цена

больше кадров в сети

снизить качество

хуже картинка

меньше задержка

нативный UDP из программы (42.3)

сложнее отладка

убрать копирование

драйвер с отображением буфера другому устройству без копии

ядро вместо userspace

точная метка времени кадра

драйвер с прерыванием вместо опроса

то же

Последние две строки замыкают вопрос, оставленный открытым в части I, глава 5.7: драйвер станет оправдан именно тогда, когда понадобится обойти это копирование или получить прерывание. Сейчас не нужно ни то, ни другое — и теперь у этого утверждения есть измеренное основание, а не только рассуждение.

43.5. Как мерить, чтобы не обмануться

Три ловушки, каждая из которых уже срабатывала в проекте.

Статичная сцена. Два одинаковых кадра ничего не доказывают: сцена перед камерой не меняется. Проверка — что кадры различны: размеры частей в потоке должны плясать, контрольные суммы соседних файлов различаться. Пошевелите рукой перед камерой.

Частота не там, где кажется. Частоту энкодера надо мерить по его собственному счётчику кадров, без участия канала записи и программы (VI.31.6), иначе вы измеряете связку целиком и не знаете, кто в ней узкий.

Накопительные величины. Регистр размера у энкодера считает нарастающим итогом, поэтому число записанных байт берётся у канала записи, а не у энкодера (VI.31.8).

43.6. Что означает каждая комбинация отказа

Полезная таблица на случай, когда что-то из двух выходов не работает. Она опирается на то, что ветки показа и сжатия независимы и делят только кадровый буфер.

HDMI

Сеть

Где искать

живо

нет потока

ветка сжатия: ворота, слоты, признак конца передачи, клиент, брандмауэр

полосы

поток идёт

общий ресурс: кто-то зажал порт памяти; сброс посреди пачки транзакций

тёмный

поток идёт

тракт показа: тайминги, пиксельная частота, кабель, монитор

тёмный

нет потока

общее начало: частоты, битстрим, сброс логики, сенсор

Последняя строка — та, с которой начинают: если не работает ничего, смотреть надо не на камеру, а на три частоты и импульс сброса (часть VII).

43.7. Итог по цели проекта

Формулировка из части I, глава 5.1 требовала показать изображение на мониторе без разрывов и одновременно отдать сжатый поток так, чтобы его открыл обычный плеер. Это выполнено: монитор показывает живое видео, поток открывается браузером и плеером, а по UDP уходит перепаковкой на хосте.

Что осталось незакрытым, названо честно: нативный UDP, сессионный протокол, несколько клиентов, полное поле зрения. Ни одно из этого не является долгом перед поставленной целью — это следующие упражнения, и для каждого в тексте указано, с чего начинать.

Дальше — часть IX: сквозной маршрут с нуля, программа занятий и правила, которые остались после всех разобранных ошибок.

Часть IX. Маршрут, занятия и правила

Восемь предыдущих частей шли вперёд: от задумки к работающему потоку. Эта идёт назад — она нужна тому, кто уже внутри процесса. Здесь маршрут из девяти доказательств, обещанный в части 0, глава 4.2, программа занятий, если серия используется как курс, и правила, каждое из которых в этом проекте оплачено.

Глава 44. Сквозной маршрут: девять доказательств

44.1. Как пользоваться этой главой

Каждый шаг — одно доказательство и ровно один вопрос, на который он отвечает. Пока ответ отрицательный, следующий шаг делать нельзя: он будет давать правдоподобные отрицательные результаты, и вы начнёте чинить исправные вещи.

Формат каждого шага: цель, команда, ожидаемый результат, что именно доказано и куда идти, если не сошлось. Команды в копируемом виде собраны в приложении C; здесь важнее, что каждая из них проверяет.

Пропуск шага стоит дороже, чем кажется. Классический пример — начать с шага 6: картинка на мониторе зависит одновременно от приёмника, демозаика, памяти, двух каналов DMA, таймингов видеовыхода и кабеля, поэтому «нет картинки» не сужает поиск вообще.

44.2. Шаг 1: инструменты видят кристалл, проект собирается

make env
./scripts/get_ip.sh
git clone https://github.com/lcapossio/mjpegZero.git ip_repo/mjpegZero
vivado -mode batch -source scripts/check_ip.tcl
make bit

Ожидается: в списке частей есть xc7z020clg484-2, чужой IP на месте, приёмник принимает две линии и скорость 408, в конце — путь к битстриму и три отчёта.

Доказано: установка пригодна, все блоки конвейера доступны на этой части и с лицензией, дизайн собирается, тайминги сходятся по существу.

Не сошлось — часть III, главы 13–16. Две самые частые причины: не выполнен шаг с чужим IP и IPCHK_MISSING для блоков, которых мы намеренно не используем (это норма, см. III.13.2).

Отдельно сохраните лог: по нему сверяются файл ограничений, описание железа и адреса в программе.

44.3. Шаг 2: процессор виден, память отвечает

make probe
xsct scripts/jtag_ddr_test.tcl

Ожидается: на цепочке XC7Z020, видны ядра, тест памяти даёт ноль испорченных слов.

Доказано: отладочный доступ есть, инициализация процессорной системы из этого дизайна конфигурирует память правильно.

Не сошлось: пустой список целей при видимой цепочке — заклинивший отладочный порт (IV.17.4), make recover. Испорченные слова в тесте памяти — конфигурация памяти в блок-дизайне (часть V, глава 24); это тот шаг, который в проекте не сделали вовремя и потом три дня искали причину не там.

44.4. Шаг 3: процессор дотягивается до логики

make live      # среди первых строк: LIVE_LEDS

Ожидается: оба светодиода горят.

Доказано одним действием сразу пять вещей: битстрим в логике, тактовая частота жива, преобразователи уровней между половинами кристалла включены, адрес совпадает с блок-дизайном, шина отвечает.

Не сошлось — часть III, глава 16.6. Помните, что скрипт заливки логики не выполняет инициализацию процессорной системы, а преобразователи уровней включает именно она.

44.5. Шаг 4: сенсор жив

Тот же запуск, следующие строки: состояние линий, затем шина I²C.

Ожидается: стоп-состояние на всех трёх парах до первой транзакции, затем устройство 0x36 и идентификатор 0x5647.

Доказано: питание модуля, его собственный передатчик, правильная ориентация шлейфа, исправная шина управления и тот сенсор, под который написана таблица режима.

Не сошлось: линии не в стоп-состоянии — ориентация шлейфа и питание (часть II, глава 11, и там же прозвонка). Идентификатор 0x5640 — другой сенсор (часть I, глава 6.5). Значение 0xFFFFFFFF под Linux — настройка выводов шины из чужого описания железа (VII.36.2).

44.6. Шаг 5: линия передаёт пакеты

Ожидается в логе: тип данных 0x2B, 2400 байт на строку, порядка 33 000 строк в секунду, ноль ошибок за секунду измерения.

Доказано: физический уровень, окно установления, калибровка входных задержек, разметка кадров сенсором.

Не сошлось — и здесь важно различать три картины, потому что лечатся они по-разному:

Что видно

Куда идти

счётчики D-PHY не растут

питание модуля, ориентация (II.11)

растут, пакетов ноль, ошибок ноль

окно установления, фронт сброса (IV.19, VII.37.3)

пакеты есть, ошибки контрольной суммы

калибровка задержек (V.23)

строк вдвое меньше расчёта

полный размер кадра у сенсора (IV.20.2)

Расчёт того, сколько строк вы обязаны получать, — jtag_ov_timing.tcl. Сравнение с ним занимает секунды и снимает целый класс ложных диагнозов.

44.7. Шаг 6: в буфере живые пиксели, монитор показывает картинку

Ожидается: около 30,5–30,8 кадра в секунду, метка в кадровом буфере затирается, LIVE_GENLOCK показывает изменение во всех трёх буферах, на мониторе живое изображение с верными цветами и без разрывов.

Доказано: весь тракт от сенсора до монитора, включая владение буферами.

Не сошлось — часть V целиком, и порядок проверок там существенный: сначала измеримая чистота приёма, потом память, потом цвета, потом разрывы. Инструменты — диагностические режимы colors, lut, measure (IV.21.5).

Отдельно: несколько секунд шума на мониторе до подъёма камеры — норма, а не отказ (IV.21.1).

44.8. Шаг 7: энкодер отдаёт целый файл

Ожидается: строки LIVE_JPEG с числом записанных байт, частотой около 24 кадров в секунду и подтверждением целостности; выгруженный файл открывается на хосте, маркеры в правильном порядке, ровно один маркер конца.

Доказано: ветка сжатия, включая арифметику преобразования и ворота на входе ядра.

Не сошлось — часть VI. Быстрая триада: файл в мегабайты при верной картинке — арифметика (VI.30.3); нет маркера конца — ворота (VI.31); канал не пишет ничего при здоровом виде — длина окна (VI.29.5).

Если подтверждения целостности нет — не отправляйте это в сеть.

44.9. Шаг 8: система грузится с карты сама

unzip -j build/camera/system_wrapper.xsa ps7_init_gpl.c ps7_init_gpl.h -d linux/uboot/
rg -c "0XF80007" linux/uboot/ps7_init_gpl.c    # не ноль
make BUILDROOT=<путь> br-defconfig
make BUILDROOT=<путь> br
sudo dd if=<buildroot>/output/images/sdcard.img of=/dev/sdX bs=4M conv=fsync status=progress

Ожидается: контрольные точки загрузки из VII.38.2 — баннер первичного загрузчика, отсутствие жалобы на карту, PL configured, приглашение системы, отвечающая сеть, три верные частоты в выводе программы.

Доказано: передача состояния между четырьмя участниками загрузки работает.

Не сошлось — часть VII, и почти всегда это один из трёх файлов инициализации (VII.35.2) либо вывод определения присутствия карты (VII.35.3).

44.10. Шаг 9: поток открывается на хосте

mjpeg_pipe -o /tmp/one.jpg     # сначала один кадр в файл
mjpeg_pipe                     # затем поток
# на хосте:
ffplay http://<адрес платы>:8080/
gst-launch-1.0 souphttpsrc location=http://<адрес платы>:8080/ \
   ! multipartdemux ! rtpjpegpay ! udpsink host=<получатель> port=5004

Ожидается: порядка 22–24 кадров в секунду, около 50 Мбит/с при качестве 75, и кадры различны (пошевелите рукой перед камерой).

Доказано: цель проекта.

Не сошлось — часть VIII, глава 43.6: там таблица, что означает каждая комбинация «HDMI работает или нет» и «поток идёт или нет».

44.11. Что доказано к концу каждого шага

Смысл маршрута в том, что доказательства накапливаются и не переспрашиваются.

После шага

Можно больше не подозревать

1

установку, доступность IP, сборку, тайминги

2

отладочный доступ, конфигурацию памяти

3

битстрим, тактовую, мост, карту адресов

4

шлейф, питание модуля, шину управления, модель сенсора

5

физический уровень и протокол приёма

6

обработку, память кадров, владение буферами, цвета

7

ветку сжатия и целостность файла

8

загрузку и передачу состояния

9

транспорт

44.12. Сколько это занимает

Реалистичный бюджет времени для того, кто повторяет впервые, при исправном железе:

Этап

Время

окружение, чужой IP, проверки

около часа

первая полная сборка битстрима

около 40 минут

шаги 2–7 по JTAG

вечер

первая сборка образа

около трёх часов машинного времени

запись карты и первая загрузка

около часа

поток и измерения

около часа

Итого один-два дня, из которых половина — ожидание сборок. Это при условии, что плата исправна и шлейф вставлен правильно; на неисправностях время не ограничено сверху ничем.

Глава 45. Программа занятий

45.1. На что это рассчитано

Восемь занятий по два-три часа плюс самостоятельная работа на ожидании сборок. Предварительные требования — из части 0, глава 1.7: основы цифровой логики, немного Verilog, командная строка.

Одного комплекта железа хватает на подгруппу: большая часть работы — чтение отчётов, счётчиков и логов, а не физический доступ к плате.

45.2. Занятия, артефакты, критерии

№

Части

Артефакт студента

Критерий приёма

1

0–I

таблица трёх вариантов границы «логика/процессор» с расчётами

посчитал такты на пиксель и объяснил, почему вариант A мёртв

2

II

карта «контакт разъёма → сигнал» и расчёт окна установления для своей скорости

получил 100…170 нс из спецификации сам

3

III

собранный битстрим, три отчёта, разбор трёх чисел таймингов

объяснил, почему «constraints are not met» не про поломку

4

IV

живое видео по JTAG, лог с прирастающими счётчиками

показал, что мерит счётчиком, а не картинкой

5

V

результаты трёх диагностических режимов

предъявил ровно серый кадр и однотонный экран

6

VI

файл JPEG с платы, разобранный по маркерам, и прогон стенда

объяснил, почему размер файла — контрольная сумма

7

VII

образ карты, лог холодной загрузки

предъявил чек-лист соответствия из VII.38.3

8

VIII

поток в браузере и перепаковка в UDP, измерения

назвал причину каждой потери в цепочке частот

45.3. Что делать без полного комплекта

Без камеры проходятся занятия 1–3 и большая часть 7: скелет, светодиоды, образ системы, загрузка. Отсутствие камеры видно на шаге 4 маршрута и дальше.

Без монитора остаются все измерения по счётчикам, файл JPEG и поток — собственно, именно так и надо отлаживать, а монитор служит финальной проверкой.

Без платы осмысленны занятия 1–3 в части чтения и расчётов и занятие 6 целиком: оба стенда считаются на хосте, а дефект ворот воспроизводится на кадре 320×64 за восемь секунд (часть VI, глава 32). Это, пожалуй, самое ценное занятие для тех, у кого железа нет вовсе.

45.4. Три вопроса, на которые надо ответить измерением

Если проверять только один результат, проверяйте эти три. Каждый закрывает типовой способ «сдать, не сделав».

«Как вы узнали, что кадры живые, а не один застывший?» Правильный ответ — не «видно же», а прирост счётчика, различающиеся размеры частей потока или затираемая метка в буфере (часть VIII, глава 43.5).

«Сколько строк в секунду вы обязаны получать и сколько получаете?» Правильный ответ содержит два числа и объяснение расхождения, если оно есть (часть IV, глава 20.2).

«Как вы убедились, что записанное значение вообще имеет эффект?» Правильный ответ — прочитал обратно (часть III, глава 13.3); для регистра задержек это буквально спасает от ложного вывода (часть V, глава 23.4).

45.5. Как отличить сделанное от похожего на сделанное

Четыре ситуации, каждая из которых выглядит как успех.

Работает вендорский битстрим, а не свой. Проверяется тем, что скорость линии, под которую собран приёмник, — 408, и это видно в таблице тактовых сигналов отчёта: 204,04 МГц на паре (III.16.4).

Картинка есть, а измерения ошибок нет. Приёмник с нерасставленными задержками даёт живое изображение (V.23). Спрашивайте частоту ошибок за секунду.

Кадр в файл получен, поток не проверен. Один кадр не ловит ни дефект ворот, ни дисциплину слотов. Спрашивайте поток и различие кадров.

Всё поднято руками, а не с холодной загрузки. Цель включает «без единой команды руками». Спрашивайте лог загрузки с тремя частотами.

45.6. Что оценивать не стоит

Скорость прохождения маршрута и число «своих» строк кода. Первое зависит от того, насколько исправно железо; второе в этом проекте намеренно мало — весь собственный Verilog это два модуля и два стенда, а ценность работы в интеграции и измерениях, а не в объёме написанного.

Глава 46. Итоги и правила

46.1. Что получилось

Что

Измерено

приём с сенсора

408 Мбит/с на линию, ноль ошибок за секунду

строки в секунду

около 33 000, совпадает с расчётом

изображение на HDMI

1920×1080, развёртка 60 Гц, около 30,6 кадра с камеры

разрывы кадра

нет: три буфера и согласование каналов

сжатие

около 24 кадров в секунду, 230–280 КБ на кадр при качестве 75

поток в сети

около 23 кадров в секунду, около 50 Мбит/с

занятость кристалла

40,7 % логики, 42,5 % блочной памяти

запуск

холодная загрузка с карты, без команд руками

Всё это — на собственном битстриме, собранном скриптом, со своей инициализацией процессорной системы, на ядре из основной ветки и без драйвера в ядре.

46.2. Чем заплатили

Сводная таблица решений; каждое разобрано в своей части.

Решение

Цена

MJPEG вместо современного кодека

битрейт впятеро выше

сжатие в логике

своя обвязка и своя отладка

отображение физической памяти вместо драйвера

нет /dev/video0, нет прерываний

HTTP на плате, UDP перепаковкой

нативный RTP — отдельная работа

гигабитная сеть в процессорной системе

видеотракт получил 142,857 вместо 150

развёртка 1080p60

сериализатор за пределами аттестации

ворота перед энкодером

пропускается около каждого пятого кадра

режим 1080p как кроп

сужено поле зрения

вендорские адреса регистров

нельзя «навести порядок» в карте

описание платы от чужой платы

список правок, который надо помнить

46.3. Что осталось нехорошего

Одно нарушение таймингов — ширина импульса в сериализаторе HDMI, оставлено сознательно и с доказательством (V.27.3). Признак того, что пора переходить на 1080p30: искры или выпадения пикселей на экране.

Сенсор иногда не выходит на поток с первой попытки при полностью верных регистрах; лечится повтором с импульсом сброса (IV.21.6).

Базовое описание платы для ядра — заимствованное, работает благодаря списку правок (VII.36.1).

46.4. Куда двигаться дальше

Четыре направления, у каждого указано, с чего начинать.

Нативный UDP из программы. Структура нагрузки — в части VIII, глава 42.3. Главное, что легко перепутать: смещение фрагмента считается внутри данных скана, а не файла.

Полное поле зрения. Режим с биннингом даёт полный кадр, но требует уменьшения. Дешевле децимировать в логике, чем копировать процессором (часть I, глава 6.4).

Второй вывод — SPI-панель. Кадр 320×172 в формате RGB565 занимает около 110 КБ; при скорости шины 16,7 МГц это порядка 19 кадров в секунду. Контроллер шины с прямым доступом к памяти написан в предыдущем проекте на этой плате; нужны децимация и упаковка.

Драйвер ядра. Оправдан, когда понадобится прерывание вместо опроса или передача буфера другому устройству без копирования — и теперь у этого есть измеренное основание (часть VIII, глава 43.4).

46.5. Правила, каждое из которых здесь оплачено

Сгруппированы по типу, со ссылкой на место, где выведены.

Про измерение.

  1. Строй измерение, у которого правильный ответ известен заранее: серый кадр, однотонный экран, затираемая метка (0.1.4).

  2. Записал — прочитай обратно. Шестнадцать одинаковых результатов перебора — это сигнатура неработающей записи, а не данные (III.13.3, V.23.4).

  3. Посчитай, что обязан получить, прежде чем чинить физику (IV.20.2).

  4. Сравнивай величины, чью семантику проверил, а не те, чьи имена похожи (IV.20.5, VI.31.8).

  5. Если измеряешь в цикле — убедись, что после итерации система вернулась в исходное состояние (IV.19.6).

  6. Отсутствие ошибок — тоже улика: приёмник, получающий байты, обязан жаловаться (II.10.5).

  7. Размер сжатого кадра — контрольная сумма арифметики (VI.30.4).

Про порядок.

  1. Один шаг — одно доказательство; следующий блок включают, когда предыдущий зелёный (глава 44).

  2. Стоки раньше источников (I.8.3).

  3. Адреса и геометрия раньше бита запуска; взведение раньше отправки (IV.21.1, VIII.40.2).

  4. Если процедура вынуждена попадать в узкое временное окно — убери окно при сборке (IV.21.2).

  5. Отлаживай от источника к потребителю: дефекты маскируют друг друга (V.27.2).

Про соглашения и чужой код.

  1. Соглашение о порядке байт в буфере не записано нигде; каждый новый потребитель памяти сверяется измерением (V.25, VI.30.5).

  2. Граница чужого ядра — место для приборов, а не для заплаток (VI.29.2).

  3. Цифры производительности относятся к конфигурации, в которой их измеряли (VI.30.1).

  4. Референсный проект производителя — не описание платы, а один из способов её сконфигурировать (VII.35.3).

  5. Наследуя чужое описание, удаляй лишнее явно: промолчать — не то же самое, что отказаться (VII.36.2).

Про передачу состояния.

  1. Снять сброс и сбросить — разные операции; у фронта есть потребители (VII.37.3).

  2. Инициализация процессорной системы — один файл, и он про частоты, выводы и память сразу (VII.35.2).

  3. Не дублируй константы: непереносимую константу надо удалить, а не комментировать (VII.37.2).

  4. «Этот параметр никто не читает» проверяй по всем режимам работы, а не по основному (V.24.6).

  5. Кэш внеконтекстного синтеза может отдать старую логику; сверяй время контрольной точки (III.14.3).

Про инструмент.

  1. Молчаливый отказ — норма для инструмента: неверное имя параметра игнорируется, ограничение для несуществующего вывода применяется в пустоту (III.13.3, III.15.2).

  2. Читай, какая проверка таймингов не сошлась, а не итоговую фразу (III.16.2).

  3. Модель стоит восемь секунд там, где плата стоит цикл сборки — если условие дефекта не зависит от масштаба (VI.32.2).

46.6. Если запоминать только одно

Из двадцати пяти правил все, кроме одного, — следствия.

Прежде чем смотреть на результат, постройте наблюдение, правильный ответ на которое известен заранее.

Картинка на мониторе одинаково выглядит при нерасставленных задержках, битой памяти, переставленных байтах и общем буфере. Ровно серый кадр не выглядит серым ни при одной из этих причин. Разница между двумя этими предложениями и есть содержание всей серии.

Битстрим устареет вместе с платой. Привычка не доверять собственной первой гипотезе и требовать измерение с заранее известным ответом — нет.

Приложения A–E

Приложение A. Глоссарий

AXI — шина ARM для связи блоков. AXI4-Lite — простые регистры (валид/готов, адрес, данные). AXI4 / AXI-Stream / AXI HP — потоки и доступ логики в DDR.

Bayer, BGGR — мозаика цветного фильтра: в 2×2 клетке два зелёных, синий и красный. RAW10 BGGR — то, что отдаёт OV5647. Демозаик восстанавливает RGB. Фаза — какой пиксель клетки считается «началом».

Битстрим (.bit) — конфигурация PL. У файла Vivado есть заголовок, в U-Boot нужен fpga loadb.

CSI-2 — пакетный протокол камер MIPI. RAW10 имеет data type 0x2B.

D-PHY — физический уровень: LP (низкая скорость, single-ended) и HS (дифференциал). Синхробайт HS — 0xB8. HS_SETTLE — пауза после выхода из LP, прежде чем искать этот байт.

EMIO — выводы PS, пропущенные в PL. На этой плате так сидят оба I²C и CAM_GPIO (GPIO 54).

FCLK — тактовые, которые PS отдаёт в PL. У нас 100 / 142,857 / 200 МГц.

Genlock — согласование, какой кадровый буфер сейчас чей. Dynamic master публикует индекс, slave следует и пропускает занятый.

HP / GP — порты PS. GP0: CPU мастер (регистры). HP: логика мастер (DDR).

IDELAY / IDELAYCTRL — программируемая задержка входа и блок калибровки по опоре 200 МГц. Калибровка один раз по фронту сброса.

MIO — выводы PS с программируемой функцией. Их мультиплексирует ps7_init, не битстрим.

MJPEG — последовательность независимых JPEG. Motion JPEG по HTTP — multipart/x-mixed-replace.

no-map — область reserved-memory, которую ядро не кэширует и не отдаёт аллокатору.

PL / PS — programmable logic (FPGA) и processing system (Cortex-A9 + периферия) кристалла Zynq.

ps7_init — код, которым SPL настраивает DDR, MIO, PLL. Генерируется из блок-дизайна. Один на дизайн.

RTP/JPEG (RFC 2435) — полезный груз JPEG в RTP, payload type 26, обычно поверх UDP.

SoT — Start-of-Transmission, ошибка, если синхробайт найден криво. Нет SoT и нет пакетов — байт до декодера не дошёл.

TMDS — кодирование HDMI. Здесь сериализуется внутри FPGA (rgb2dvi).

VDMA — DMA для видео с кадровыми буферами и геометрией строки. S2MM — запись в память, MM2S — чтение. VSIZE последней записью взводит канал.

VTC — генератор строчных/кадровых синхроимпульсов для video out.

VTS — vertical total сенсора: активные строки + гашение. Задаёт кадровую частоту вместе с PLL.

XAPP894 — приложение AMD: пассивная сеть, чтобы HR-банк 7-й серии принял D-PHY. На плате уже распаяна.

Приложение B. Карта регистров и памяти

Адреса камерного дизайна (scripts/build_camera.tcl). Совпадают с вендором, кроме отсутствующего dynclk.

База

Окно

Блок

0x41200000

64K

AXI GPIO светодиодов (канал 2 — overflow JPEG)

0x40400000

64K

AXI DMA JPEG, S2MM с +0x30

0x43000000

64K

VDMA захват (S2MM +0x30)

0x43010000

64K

VDMA показ (MM2S у базы)

0x43020000

64K

VDMA чтение в энкодер

0x43C00000

8K

CSI-2 RX; D-PHY +0x1000

0x43C20000

64K

v_demosaic

0x43C30000

64K

v_gamma_lut

0x43C40000

64K

VTC

0x43C50000

4K

mjpegZero через wrap

0x43C10000

—

пусто (был dynclk)

Полезные смещения CSI/D-PHY: CCR 0x00, CSR 0x10, ISR 0x24, VC0INF1 0x60, VC0INF2 0x64; D-PHY CTRL 0x1000, IDELAY 0x1004, статус клока 0x1018, лейн0 0x101C, лейн1 0x1020, HS_SETTLE0 0x1030, HS_SETTLE1 0x1048.

VDMA (PG020): MM2S CR/SR 0x00/0x04, ADDR с 0x5C; S2MM CR/SR 0x30/0x34, VSIZE 0xA0. Бит 12 SR — кадр (если IRQ кадра разрешён). Бит 3 CR — genlock. Биты 11:8 CR — номер master.

Память:

Адрес

Размер

Назначение

0x10000000

3 × 6 МиБ

кадровые RGB, stride 1920×3

0x18000000

2 × 2 МиБ

JPEG, чередование слотов

I²C камеры: контроллер PS 0xE0004000, slave 0x36. CAM_GPIO: GPIO 54, 0xE000A048 / 0xE000A284 / 0xE000A288. SLCR: unlock 0xF8000008=0xDF0D, FPGA_RST 0xF8000240, FCLK0/1/2 0xF8000170/0180/0190, IO PLL 0xF8000108, сдвигатели 0xF8000900.

Прерывания xlconcat: In0 захват → SPI 61 → DTS <0 29 4>, дальше +1. Энкодер DMA — In5. Userspace IRQ не использует.

Приложение C. Шпаргалка команд

# хост, из корня репозитория
make env
./scripts/get_ip.sh
git clone https://github.com/lcapossio/mjpegZero.git ip_repo/mjpegZero
make probe
make recover
make skeleton
make bit
make rebuild          # правка rtl/*.v в ПРОЕКТЕ-СКЕЛЕТЕ (камерный собирается с нуля через make bit)
make live
make synth

vivado -mode batch -source scripts/build_camera.tcl -tclargs bd

xsct scripts/jtag_camera_live.tcl
xsct scripts/jtag_camera_live.tcl - - - - colors
xsct scripts/jtag_ddr_test.tcl

unzip -j build/camera/system_wrapper.xsa ps7_init_gpl.c ps7_init_gpl.h \
      -d linux/uboot/

git clone --branch 2026.02.3 https://gitlab.com/buildroot.org/buildroot.git
make BUILDROOT=/path/to/buildroot br-defconfig
make BUILDROOT=/path/to/buildroot br
sudo dd if=/path/to/buildroot/output/images/sdcard.img of=/dev/sdX \
    bs=4M conv=fsync status=progress

# плата
devmem 0x41200000 32 0x3
mjpeg_pipe -o /tmp/one.jpg
mjpeg_pipe
mjpeg_pipe -q 75 -p 8080
mjpeg_pipe -S

# хост, сеть
ffplay http://<плата>:8080/
vlc    http://<плата>:8080/
curl -N http://<плата>:8080/ > /tmp/mjpeg.bin
gst-launch-1.0 souphttpsrc location=http://<плата>:8080/ \
    ! multipartdemux ! rtpjpegpay ! udpsink host=<кто смотрит> port=5004

Консоль: 115200 8N1. Перемычки: загрузка с SD.

Приложение D. Каталог дефектов

Краткие имена для поиска по статье. Полный рассказ — в указанных главах.

ID

Симптом

Настоящая причина

Где

C1

0 пакетов CSI, 0 ошибок, D-PHY считает

HS_SETTLE под чужую скорость линии

IV.19

C2

Поток доли секунды, потом SoT/синхро

VDMA под потоком; CSI выключали

IV.20

C3

«Теряем 40 % строк»

VTS по умолчанию 1968, не физика

IV.20

C4

Кадров нет, метка в DDR жива

0x4800=0x04, нет line sync

IV.20

C5

Картинка вспыхнула и застыла

адреса VDMA после reset; пустые frame stores

IV.21

C6

MMU fault на GPIO, SLCR читается

CPU остановлен слишком поздно

IV.17

C7

VDMA ждёт кадр вечно

line count CSI ≠ высота

IV.20

C8

«канал сломан», счётчик кадров растёт

липкие флаги SR

IV.21

C9

Живое видео, CRC в каждом окне

C_CAL_MODE=NONE, тап не задан

V.23

C10

Шум при чистом линке, пила фазы одинакова

DDR не та микросхема в ps7_init

V.24

C11

Все фазы Байера «не те» по-разному

вторая перестановка в packer

V.25

C12

Разрывы кадра

один буфер на 30 и 60 Гц

V.26

C13

WNS −0,8 нс в энкодере

RGB_INPUT комбинационный

VI.30

C14

JPEG верный, 4 МиБ, не сжимается

срез знака вместо >>>

VI.30

C15

DMA idle, байт не пишет

длина 8 МиБ при 23-бит счётчике

VI.29

C16

EOI нет, мегабайты entropy

непрерывный поток в ядро

VI.31

C17

«файл обрезан», открывается

накопительный счётчик байт

VI.31

C18

Картинка сыпется под Linux

чужой ps7_init, нет FCLK2

VII.35

C19

Плата молчит после SPL

ps7_init без записей MIO

VII.35

C20

MMC: no card present

CD на MIO 47 вместо 9

VII.35

C21

HDMI нет, регистры живы

FCLK0=55,6 из boot.cmd

VII.37

C22

sensor id 0xFFFFFFFF

pinctrl i2c0 с zc702

VII.36

C23

D-PHY жив, CSI глух после Linux

сброс PL без фронта

VII.37

C24

JPEG/HDMI сиреневые

SWAP_GB, учебный RGB

VI.30

C25

Редкие битые JPEG в сети

один буфер DMA на send

VIII.40

C26

Правка RTL «не действует»

OOC-кэш Vivado

III.14

C27

Ядро виснет на probe PL

FCLK погашен / нет clocks-ловушки

VII.36

C28

Link Up, ping нет

HSTL pinctrl на RGMII

VII.36

Приложение E. Что смотреть, когда картинки нет

Идите сверху вниз. Не прыгайте к IDELAY, пока не закрыт пункт выше.

  1. Питание, шлейф контактами вверх, HDMI-кабель, монитор 1080p.

  2. JTAG видит XC7Z020 и ARM. Иначе recover / кабель.

  3. Светодиоды с AXI GPIO. Нет — битстрим, FCLK0, ps7_init, сдвигатели, адрес.

  4. FCLK 100 / 142,857 / 200. Нет — не тот SPL, boot.cmd затёр делитель.

  5. I²C 0x36, ID 0x5647. Нет — GPIO сброса, pinctrl, шлейф, не тот сенсор (0x5640).

  6. D-PHY, два состояния подряд. В простое все три пары обязаны стоять в LP-11 (stop = 1) — это доказывает питание модуля и ориентацию шлейфа. После старта сенсора стоп-состояние снимается и растёт счётчик посылок. Нет первого — шлейф и питание; нет второго — сенсору не сказали передавать.

  7. CSI: data_type=0x2B, 2400 байт/строка, строки ~33к/с. Нет при живом D-PHY — HS_SETTLE, 0x4800, фронт сброса, не включали CSI повторно.

  8. Метка в DDR затирается, VDMA running, бит 12 при разрешённом IRQ. Нет — порядок VSIZE/адресов, сброс канала, frame stores, высота с сенсора не из CSI line count.

  9. HDMI: режим есть до камеры (серый/константа гаммы). Нет — FCLK0, pixclk, VTC, кабель. Есть серый с шумом — DDR (тест jtag_ddr_test.tcl). Есть цветной шум при чистом CSI — packer/фаза; режим colors.

  10. JPEG открывается, размер сотни КБ, есть EOI. Нет — ворота, tlast, длина DMA, >>>, SWAP_GB.

  11. HTTP отдаёт разные кадры. Нет — слоты, клиент, файрвол. Есть HTTP, нет UDP — SDP, порт 5004, rtpjpegpay на хосте.

Печатайте регистры так же, как скрипты: D-PHY ctrl/clk/lane0/lane1, CSI ccr/csr/isr/bytes/type, VDMA SR обоих каналов, chip ID. Скриншот «чёрный монитор» без этих чисел не диагностируется.

Это дерево — для случая, когда что-то сломалось. Если вы собираете проект с нуля и хотите пройти его по порядку, нужен не он, а маршрут из девяти доказательств в части IX, глава 44.

Размещайте облачную инфраструктуру и масштабируйте сервисы с надежным облачным провайдером Beget.
Эксклюзивно для читателей Хабра мы даем бонус 10% при первом пополнении.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.