Files
Gromov_IA 525a1dd90e Описание стенда и быстрый старт
- docs/stand.md: состав стенда, схема и таблица соединений, питание,
  порядок работы, нормальные показания на ПК и на выходе ЦАП, неисправности;
- README: UART через мост BL616 вместо внешнего FT232, быстрый старт,
  структура, команды симуляции через --elab-run (работают и с mcode-сборкой
  GHDL), python вместо py -3, список локальной документации;
- .gitignore: мусор генератора IP Gowin, Python, Windows.
2026-10-02 14:21:07 +03:00

404 lines
27 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Цифровой микрофон SPH9855 на Tang Primer 25K
Прошивка ПЛИС GW5A-25 принимает поток PDM с микрофона SPH9855LM4H-C,
подключённого по линии LVDS (SN65LVDS), приводит его к **24 бит / 12 500 Гц**
и отдаёт отсчёты сразу в две стороны: кадрами по UART через встроенный мост
BL616 на ПК и по I2S на ЦАП PCM5102A, чей аналоговый выход смотрят
осциллографом.
MIC_CLK 3.125 МГц -> CIC sinc^3 /50 -> 62.5 кГц -> ФНЧ /5 -> **12 500 Гц** ->
- кадры по 50 отсчётов -> UART 921600 -> BL616 -> USB на ПК;
- пересчёт в 48 828 Гц -> I2S -> PCM5102A -> осциллограф.
**Стенд целиком** — состав, схема соединений, питание, порядок включения,
нормальные показания и неисправности — описан в [docs/stand.md](docs/stand.md).
## Быстрый старт
Сборка — в Gowin IDE: открыть `DigMic.gprj`, Run All. Битстрим —
`impl\pnr\DigMic.fs`.
Загрузка в ПЛИС через мост BL616 (DirtyJTAG):
```powershell
openFPGALoader -c dirtyJtag impl\pnr\DigMic.fs
```
Проверка: светодиод платы часто мигает (10 Гц), а на ПК идёт поток:
```bash
python -m pip install -r tools/requirements.txt
```
```bash
python tools/uart_live.py --port COM14
```
COM14 — порт моста BL616 на рабочем ПК стенда; на другом ПК номер покажет
`python tools/uart_capture.py --list`. Норма: 250 кадров/с, CRC 0, STATUS чисто.
## Структура
```
DigMic.gprj проект Gowin, GW5A-LV25MG121NC1/I0, ревизия A
impl/DigMic_process_config.json настройки синтеза и PnR (остальное в impl/ не трекается)
src/ RTL, ограничения выводов и тайминга, IP-ядро PLL
tb/ тестбенчи: сквозной, ветвь ЦАП, кнопка
tools/ скрипты ПК: приём, просмотр, прослушивание, расчёт ФНЧ
docs/stand.md описание стенда
```
## Тактирование и частоты
| Параметр | Значение | Обоснование |
|:--|:--|:--|
| Системный клок | 50 МГц (E2) | генератор платы |
| MIC_CLK | 3.125 МГц = 50/16 | Normal mode микрофона 1.1...4.8 МГц |
| CIC | sinc^3, децимация 50 | до 62.5 кГц, без умножителей, аккумуляторы 18 бит |
| ФНЧ | 221 отвод, децимация 5 | до 12 500 Гц |
| Выходная ЧД | 12 500 Гц | |
| UART | 921600, 8N1 | 157 байт на кадр, 250 кадров/с, 39.25 кБ/с (43% канала) |
| I2S | fS 48 828.125 Гц = 50/1024, BCK 64 fS, SCK 12.5 МГц = 256 fS | проверено на железе отдельной прошивкой-генератором синуса |
Делитель 16 выбран не произвольно: чтобы обойтись без PLL, частота PDM должна
быть 50 МГц / N, а чтобы децимация до 12 500 Гц была целой, N обязан делить
4000. Из подходящих делителей 16 даёт самую высокую частоту в пределах
Normal mode, то есть наибольшее передискретизирование (250).
## Дециматор
Двухступенчатый, потому что одного CIC мало: его нули стоят на кратных 12.5 кГц,
и в рабочей полосе он подавил бы сворачиваемые участки только на ~24 дБ. Поэтому
CIC снимает поток до 62.5 кГц, а всю избирательность даёт ФНЧ.
| Показатель | Значение |
|:--|:--|
| неравномерность полосы 0...5000 Гц | 0.032 дБ |
| подавление от 6250 Гц | 76 дБ |
| подавление сворачиваемых полос | 92 дБ |
Спад CIC в полосе не компенсируется отдельным звеном: он задан прямо в желаемой
АЧХ при расчёте ФНЧ, поэтому смена порядка CIC требует только перезапуска
генератора коэффициентов.
Порядок 3, а не 4, выбран не из соображений АЧХ — характеристики с ним даже
лучше — а по ограничению кристалла, см. ниже.
Коэффициенты и разрядности лежат в `src/fir_coefs_pkg.vhd`, файл генерируется:
```bash
python tools/gen_fir_coefs.py
```
## Шкала
Постоянная `'1'` на выводе данных микрофона даёт полную шкалу 24 бит
(код 8 388 596). Отсюда шкала выхода — dBFS, ровно та, в которой микрофон
специфицирован. Вольт в тракте нет, `V_REF` в приёмных скриптах не нужен.
## Формат кадра UART
| Поле | Байт | Описание |
|:--|--:|:--|
| SYNC | 2 | `A5 5A` |
| FRAME_CNT | 2 | uint16 BE, инкремент на кадр |
| STATUS | 1 | бит0 — переполнение FIFO, бит1 — насыщение дециматора |
| DATA | 150 | 50 отсчётов x 24 бита, MSB first |
| CRC16 | 2 | CCITT, poly 0x1021, init 0xFFFF, по FRAME_CNT..DATA |
SYNC в CRC не входит: он нужен приёмнику для ресинхронизации до того, как тот
начнёт что-либо считать.
UART_TX выведен на C3 — цепь `FPGA_UART_TX` платы Dock, к встроенному мосту
BL616. На мосту стоит заменённая прошивка (отдельный проект BL616): она
держит 921600 без потерь и одновременно даёт JTAG, так что загрузка и приём
идут по одному кабелю USB-C. Со штатной прошивкой Sipeed UART моста работал
плохо, а Gowin Programmer, наоборот, видит только её — с заменённой прошивкой
битстрим грузится через openFPGALoader.
## Выход на ЦАП
I2S и SCK — те же, что в отдельной прошивке-генераторе синуса, на которой
модуль PCM5102A проверялся на железе: тот же `i2s_transmitter.v` без
изменений, те же частоты. Источник отсчётов здесь — выход ФНЧ, те же
отсчёты, что уходят в кадры UART, но мимо FIFO.
Частота ЦАП не равна 12 500 Гц и не кратна ей, но обе делятся из одного
клока 50 МГц, так что их отношение точное и постоянное: 4000 / 1024 тактов.
Поэтому пересчёт обходится без буфера и без подстройки, которые нужны
`uart_audio.py` на ПК. Пересчёт — линейная интерполяция между двумя
последними отсчётами, доля берётся из числа тактов с прихода последнего.
Простое удержание давало бы ступени по 3–4 повтора с дрожанием на период ЦАП
и зеркала 12.5 кГц ± f на −21 дБ (для 1 кГц); с интерполяцией они на −43 дБ.
Задержка — один отсчёт, 80 мкс.
Полоса та же, что у тракта, — до 5 кГц. Полная шкала 24 бит — это 2.1 В скз
на выходе PCM5102A, а фон в комнате около −72 dBFS и речь около −50 dBFS —
без усиления это доли и единицы милливольт.
### Усиление ЦАП с кнопки
Усиление ветви ЦАП переключает кнопка S1 на плате (H11): каждое нажатие —
следующая ступень, ровно +6.02 дБ (сдвиг отсчёта на разряд), после
`DAC_GAIN_MAX` снова 0. После включения питания усиление 0 — та же шкала,
что на UART. На UART всегда уходят исходные отсчёты, кнопка на них не влияет.
`DAC_GAIN_MAX` в `Top.vhd` по умолчанию 8, то есть до +48 дБ: фон комнаты
там уже на −24 dBFS, а при чувствительности микрофона −39 dBFS (на 94 дБ SPL)
всё громче примерно 85 дБ SPL упирается в шкалу. Предел — 15 (+90 дБ). Выше
шкалы отсчёт ограничивается, признака этого нигде нет.
Выбранную ступень показывает светодиод: пауза, N коротких вспышек (для 0 —
одна длинная, 1 с), пауза, и он возвращается к обычной индикации. Нажатие во
время показа начинает показ заново. Дребезг отсекается: уровень кнопки
принимается, только если продержался 20 мс.
Уровень на выходе ЦАП при ступени N больше уровня у микрофона на 6.02·N дБ
минус спад интерполяции (−0.18 дБ на 1 кГц). Постоянная составляющая
микрофона около −90 dBFS и не мешает даже на верхних ступенях.
## Распиновка
Полная таблица с банками, местом на плате и тем, куда идёт каждая цепь, — в
[docs/stand.md](docs/stand.md#соединения), привязка — в `src/DigMic.cst`.
Кратко: clk E2, MIC_CLK K8, MIC_DAT K9, ЦАП G1/G2/L4/L3 (SCK/BCK/LRCK/DIN),
UART_TX C3, кнопка H11, светодиод E8, отладочный выход PLL E3.
### Точка выборки через линию LVDS
Кабель, драйверы и приёмники SN65LVDS на обоих концах и задержка выдачи самого
микрофона сдвигают данные относительно MIC_CLK, и точка выборки для микрофона
на коротких проводах сюда не подходит.
Фаза измерена на плате: MIC_CLK на K8 и гистограмма смен уровня K9 по 16
тактам периода. Все смены — только между тактами 1 и 2, то есть через
40–60 нс после спада MIC_CLK (с точностью до целых периодов), в остальное
время данные стоят: пока микрофон отпускает вывод, уровень держит вход
драйвера LVDS на его плате. Поэтому бит берётся в противоположной фазе,
`MIC_SMP_CNT = 12` в `Top.vhd`, — около 150 нс запаса в обе стороны. При
смене кабеля или микросхем линии фазу надо перемерить; для микрофона на
коротких проводах `MIC_SMP_CNT = -1` возвращает прежнюю точку.
### Светодиод для поиска обрыва
`LED_SRC` в `Top.vhd` выбирает, от чего мигает светодиод: 0 — отсчёты ФНЧ
(рабочий режим), 1 — отсчёты CIC, 2 — биты PDM, 3 — маяк ровно 1 Гц прямо
от clk, мимо всего тракта. Режимы 0...2 ищут обрыв тракта: быстрое мерцание
значит, что выбранный участок живой. Режим 3 меряет сам clk: период не 1 с
означает, что системная частота не 50 МГц, и тогда врут все производные от неё.
## Два ограничения кристалла, добытые на железе
> **Скорее всего, оба — следствие сборки под чужую ревизию.** На плате стоит
> GW5A-25 **ревизии A**, а проект до 2026-09-30 собирался под B. Проверено на
> этой плате: один и тот же проект под B не выдаёт ни одного байта (с любым
> `--device` у программатора), под A — 250 кадров/с без ошибок CRC. JTAG-код у
> ревизий одинаковый, программатор расхождения не замечает. Под A оба
> ограничения ещё не перепроверялись.
Оба проявляются одинаково: синтез и трассировка проходят без замечаний, тайминг
чистый, симуляция верная, а в кристалле тракт молча отдаёт нули.
**Разрядность аккумуляторов CIC не выше 18 бит.** Проверено перебором на плате
с приёмом по UART: 26, 24 и 20 бит — тракт мёртв, 18, 16 и 10 бит — работает.
Отсюда порядок 3: требуется `ORDER*log2(DECIM)+1`, для 3 и 50 это ровно 18, для
4 было бы 24. На аккумулятор ФНЧ (40 бит) ограничение не распространяется — он
проверен на железе и считает верно.
**Сброс по включению ставить нельзя.** Добавление счётчика, держащего `rst_n`
в нуле первые 256 тактов, убивает тракт целиком. С константой `'1'` всё
работает. Проверено прошивкой в обе стороны.
## Сборка
Проект собирается под `GW5A-LV25MG121NC1/I0`, Device Version **A**
(`gw5a25a-002`), см. выше. В Gowin IDE — открыть `DigMic.gprj` и Run All.
Битстрим — `impl/pnr/DigMic.fs`.
Выводы `clk` (E2) и `LED` (E8) на этом корпусе сидят на двойных функциях
(SSPI/CPU и READY), поэтому в свойствах проекта их надо разрешить как обычные
выводы, иначе размещение падает с PR2017. Это уже сделано в
`impl/DigMic_process_config.json`, который лежит в репозитории; в IDE это
флажки CPU, SSPI и READY, в `gw_sh` — `set_option -use_cpu_as_gpio 1
-use_sspi_as_gpio 1 -use_ready_as_gpio 1`.
IP-ядро `src/gowin_pll` (с `src/pll_init.v`) нужно только отладочному выходу
`clk_80MHz` на E3, тракт от него не зависит. Ядро сгенерировано ещё под
ревизию B; под A проект с ним собирается, но перегенерировать его стоит.
Занято на GW5A-25 вместе с ветвью ЦАП и кнопкой: 1179 LUT/ALU (6%),
804 регистра (4%), 5 BSRAM (9%), 3 DSP (11%).
Тайминг сходится с запасом, но полагаться на него как на признак исправности
нельзя — см. раздел про ограничения кристалла выше.
## Состав
| Файл | Назначение |
|:--|:--|
| `src/Top.vhd` | верхний уровень: только экземпляры и связи |
| `src/pdm_rx.vhd` | тактирование микрофона и выборка бита в нужной фазе |
| `src/cic_decim.vhd` | CIC sinc^3, децимация 50 |
| `src/fir_decim.vhd` | ФНЧ с децимацией 5, последовательная свёртка |
| `src/fir_coefs_pkg.vhd` | коэффициенты ФНЧ, генерируется |
| `src/fifo_top.vhd` | буфер отсчётов, заглушка под IP-ядро Gowin |
| `src/Packets_creator.vhd` | сборка кадра с CRC-16 |
| `src/UART.vhd` | передатчик |
| `src/dac_stream.v` | ветвь ЦАП: усиление, пересчёт в 48 828 Гц, I2S и SCK |
| `src/i2s_transmitter.v` | передатчик I2S, без изменений из прошивки проверки ЦАП |
| `src/led_status.vhd` | индикация частотой мигания |
| `src/gain_button.vhd` | кнопка усиления ЦАП: дребезг, ступени, показ светодиодом |
| `src/gowin_pll/`, `src/pll_init.v` | PLL для отладочного выхода 80 МГц |
| `src/DigMic.cst` | привязка выводов |
| `src/DigMic.sdc` | ограничение клока 50 МГц |
| `tb/pdm_mic_model.vhd` | поведенческая модель PDM-микрофона |
| `tb/tb_top.vhd` | сквозной тест: модель -> линия LVDS -> ПЛИС -> UART -> разбор кадра |
| `tb/tb_dac_stream.v` | тест ветви ЦАП: отсчёты -> I2S на выводах -> разбор слов |
| `tb/tb_gain_button.vhd` | тест кнопки: дребезг, ступени по кругу, вспышки |
| `tools/gen_fir_coefs.py` | расчёт коэффициентов ФНЧ |
| `tools/uart_capture.py` | приём кадров, запись `.raw` и `.bin` |
| `tools/uart_live.py` | живой просмотр: осциллограмма, спектр, счётчики |
| `tools/uart_audio.py` | прослушивание: поток на звуковую карту ПК |
| `tools/Bin_to_wav.py` | перевод записанного `.bin` в WAV |
| `tools/requirements.txt` | пакеты Python для скриптов |
## Симуляция
Сквозной тест, GHDL:
```bash
mkdir -p build && ghdl -a --std=08 --workdir=build src/fifo_top.vhd src/UART.vhd src/led_status.vhd src/gain_button.vhd src/pdm_rx.vhd src/cic_decim.vhd src/fir_coefs_pkg.vhd src/fir_decim.vhd src/Packets_creator.vhd src/Top.vhd tb/pdm_mic_model.vhd tb/tb_top.vhd && ghdl --elab-run --std=08 --workdir=build tb_top --stop-time=400ms
```
`--elab-run` работает с любой сборкой GHDL, в том числе с mcode, которая не
даёт исполняемого файла. Ожидаемый итог — `TEST PASSED`.
Микрофон в тестбенче подключён как на плате, через модель линии LVDS:
задержка в обе стороны и удержание уровня, пока микрофон отпускает вывод.
Задержка подобрана так, что смена данных попадает между тактами 1 и 2 — туда
же, где её видно на железе, — поэтому проверяется та же точка выборки.
Модель прогоняет четыре режима. Три из них дают точно вычислимый ответ:
постоянные `'1'` и `'0'` — плюс и минус полная шкала, чередование `1010` —
ровно ноль (эта частота попадает в нуль АЧХ CIC). Четвёртый — синус, у него
проверяется размах. Тракт настраивается около 10 мс, поэтому в каждом режиме
кадры читаются до первого сошедшегося.
Ветвь ЦАП и PLL написаны на Verilog, поэтому GHDL их не собирает и
предупреждает, что `dac_stream_inst` и `PLL_dbg` не привязаны; тракт UART от
этого не зависит. Ветвь ЦАП проверяется отдельно, в Icarus Verilog:
```bash
mkdir -p build && iverilog -g2012 -o build/tb_dac_stream tb/tb_dac_stream.v src/dac_stream.v src/i2s_transmitter.v && vvp build/tb_dac_stream
```
Тестбенч разбирает I2S на выводах так же, как ЦАП, и сверяет каждое слово с
эталонной интерполяцией бит в бит, с учётом момента, в который оно взято.
Параметры `-Ptb_dac_stream.GAIN=2` и `-Ptb_dac_stream.WAVE=1` (меандр
между крайними кодами) проверяют ограничение по шкале и разрядности,
`-Ptb_dac_stream.GAIN_STEP=3` — переключение усиления на ходу по всем 16
значениям, в том числе выше `GAIN_MAX`.
Кнопка усиления — отдельно, в GHDL:
```bash
mkdir -p build && ghdl -a --std=08 --workdir=build src/gain_button.vhd tb/tb_gain_button.vhd && ghdl --elab-run --std=08 --workdir=build tb_gain_button
```
Такт в нём снижен до 100 кГц, чтобы секунды показа считались быстро.
Проверяются дребезг и короткая помеха, ступени по кругу, долгое удержание,
число и длительность вспышек и перезапуск показа нажатием.
## ПО на ПК
Пакеты Python — в `tools/requirements.txt`:
```bash
python -m pip install -r tools/requirements.txt
```
COM-порт держит один процесс: запись, просмотр и прослушивание запускаются
по очереди. Разбор кадров у всех трёх общий (`FrameParser` в
`uart_capture.py`), так что они не могут разойтись.
### Запись
```bash
python tools/uart_capture.py --list
```
```bash
python tools/uart_capture.py --port COM14 --seconds 10 --out test1
```
Получаются `test1.raw` (весь поток как пришёл, для отладки канала),
`test1.bin` (только отсчёты, читается `read_24bit_bin()`) и `test1.log`.
Кадр с несошедшейся CRC отбрасывается, пропуск по FRAME_CNT добивается нулями,
чтобы не сдвинулась ось времени. Ключ `--dump` пишет только `.raw`, без
разбора, и показывает, теряет ли байты сам канал.
`.bin` переводится в WAV для прослушивания или отправки:
```bash
python tools/Bin_to_wav.py test1.bin
```
Без аргумента файл выбирается в окне. Рядом появляется `test1_gain5.wav`:
постоянная составляющая убрана, усиление `GAIN`, 12 500 Гц, PCM 16 бит, по
`WAV_BITS = 24` — полная разрядность. Настройки — константы в начале скрипта.
Берётся именно `.bin`: в `.raw` лежат кадры целиком, с заголовками и CRC.
### Живой просмотр
```bash
python tools/uart_live.py --port COM14
```
Осциллограмма в долях шкалы, спектр в dBFS и счётчики кадров в реальном
времени. Управление кнопками внизу окна, те же действия — клавишами `r`, `a`,
`d`, `s`, `f`, выход `q`. Запись встроена в просмотр по необходимости:
запустить `uart_capture.py` на том же порту параллельно нельзя.
### Прослушивание
```bash
python tools/uart_audio.py --port COM14
```
Поток уходит на звуковую карту ПК, то есть в джек на компьютере: ЦАП здесь
она сама, внешнего железа не нужно. Отсчёты 12 500 Гц пересчитываются в
частоту устройства вывода (обычно 48 000), на входе стоит ФВЧ 20 Гц — иначе
постоянная составляющая микрофона съедает запас по шкале и даёт щелчок на
старте. Полоса та же, что у тракта: 5 кГц, звук телефонного качества.
Выход выбирается тот, что назначен в Windows; если нужен другой —
`--list` показывает и порты, и устройства, `--device` выбирает. Слушать лучше
в наушниках: динамик рядом с микрофоном даёт самовозбуждение.
Постоянного отношения частот в программе нет нигде, и это главное: 12 500 Гц
отмеряет кварц платы, частоту вывода — кварц звуковой карты, а расхождение в
десятки ppm за минуту накапливает миллисекунды и рано или поздно опустошает
буфер. Поэтому шаг пересчёта подстраивает регулятор по заполнению буфера —
в строке состояния он виден как `шаг ... ppm`, там же `недобор` и `клип`.
Без порта проигрывается записанный `.bin`:
```bash
python tools/uart_audio.py --file test1.bin --gain 12
```
Проверка без порта и без звука — `--selftest` у `uart_audio.py` (пересчёт
по спектру, непрерывность на стыках блоков, реакция регулятора, ФВЧ) и у
`uart_live.py` (разбор и отрисовка).
## Документация
Даташиты и схемы в репозиторий не кладутся (`.gitignore`), их держат
локально в `docs/`:
| Файл | Что это |
|:--|:--|
| `docs/SPH9855LM4H-C.PDF` | даташит микрофона |
| `docs/Primer_25K/Tang_25K_60033.pdf` | схема платы Dock: гребёнка J3, кнопки, BL616 |
| `docs/Primer_25K/Tang_Primer_25K_52300_Schematic.pdf` | схема модуля Tang Primer 25K |
| `docs/Primer_25K/Tang_Primer_25K_Specification_V1.0_en.pdf` | спецификация модуля |
| `docs/DAC_sch.pdf`, `docs/datasheet_PCM5102APWR.pdf` | схема модуля ЦАП и даташит PCM5102A |