Compare commits
10
Commits
a4a12fbc0d
...
525a1dd90e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
525a1dd90e | ||
|
|
d6f3ee5ef8 | ||
|
|
2672f8b8a8 | ||
|
|
668c0564e9 | ||
|
|
f936d86f89 | ||
|
|
58e6a11a3f | ||
|
|
5fbb3c3eb9 | ||
|
|
df179829bf | ||
|
|
4aa221105a | ||
|
|
d3ec80b461 |
+16
-1
@@ -3,7 +3,12 @@ impl/*
|
|||||||
!impl/*_process_config.json
|
!impl/*_process_config.json
|
||||||
*.gprj.user
|
*.gprj.user
|
||||||
|
|
||||||
# Даташиты: большие, не трекаем
|
# Мусор генератора IP-ядер Gowin
|
||||||
|
**/temp/
|
||||||
|
*_tmp.v
|
||||||
|
*_tmp.vhd
|
||||||
|
|
||||||
|
# Даташиты и схемы плат: большие, не трекаем, список -- в README
|
||||||
docs/*.pdf
|
docs/*.pdf
|
||||||
docs/*.PDF
|
docs/*.PDF
|
||||||
docs/Primer_25K/
|
docs/Primer_25K/
|
||||||
@@ -12,10 +17,20 @@ docs/Primer_25K/
|
|||||||
*.bin
|
*.bin
|
||||||
*.raw
|
*.raw
|
||||||
*.log
|
*.log
|
||||||
|
*.wav
|
||||||
|
|
||||||
# Симуляция
|
# Симуляция
|
||||||
build/
|
build/
|
||||||
*.cf
|
*.cf
|
||||||
*.ghw
|
*.ghw
|
||||||
|
*.vcd
|
||||||
|
*.vvp
|
||||||
|
|
||||||
|
# Python
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
Thumbs.db
|
||||||
|
desktop.ini
|
||||||
|
|||||||
+14
-8
@@ -3,17 +3,23 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<Template>FPGA</Template>
|
<Template>FPGA</Template>
|
||||||
<Version>5</Version>
|
<Version>5</Version>
|
||||||
<Device name="" pn="">gw5a25b-003</Device>
|
<Device name="GW5A-25A" pn="GW5A-LV25MG121NC1/I0">gw5a25a-002</Device>
|
||||||
<FileList>
|
<FileList>
|
||||||
<File path="src/UART.vhd" type="file.vhdl" enable="1"/>
|
<File path="src/pll_init.v" type="file.verilog" enable="1"/>
|
||||||
<File path="src/led_status.vhd" type="file.vhdl" enable="1"/>
|
|
||||||
<File path="src/pdm_rx.vhd" type="file.vhdl" enable="1"/>
|
|
||||||
<File path="src/cic_decim.vhd" type="file.vhdl" enable="1"/>
|
|
||||||
<File path="src/fir_coefs_pkg.vhd" type="file.vhdl" enable="1"/>
|
|
||||||
<File path="src/fir_decim.vhd" type="file.vhdl" enable="1"/>
|
|
||||||
<File path="src/fifo_top.vhd" type="file.vhdl" enable="1"/>
|
|
||||||
<File path="src/Packets_creator.vhd" type="file.vhdl" enable="1"/>
|
<File path="src/Packets_creator.vhd" type="file.vhdl" enable="1"/>
|
||||||
<File path="src/Top.vhd" type="file.vhdl" enable="1"/>
|
<File path="src/Top.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/UART.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/dac_stream.v" type="file.verilog" enable="1"/>
|
||||||
|
<File path="src/i2s_transmitter.v" type="file.verilog" enable="1"/>
|
||||||
|
<File path="src/cic_decim.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/fifo_top.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/fir_coefs_pkg.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/fir_decim.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/gain_button.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/gowin_pll/gowin_pll.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/gowin_pll/gowin_pll_mod.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/led_status.vhd" type="file.vhdl" enable="1"/>
|
||||||
|
<File path="src/pdm_rx.vhd" type="file.vhdl" enable="1"/>
|
||||||
<File path="src/DigMic.cst" type="file.cst" enable="1"/>
|
<File path="src/DigMic.cst" type="file.cst" enable="1"/>
|
||||||
<File path="src/DigMic.sdc" type="file.sdc" enable="1"/>
|
<File path="src/DigMic.sdc" type="file.sdc" enable="1"/>
|
||||||
</FileList>
|
</FileList>
|
||||||
|
|||||||
@@ -1,10 +1,52 @@
|
|||||||
# Цифровой микрофон SPH9855 на Tang Primer 25K
|
# Цифровой микрофон SPH9855 на Tang Primer 25K
|
||||||
|
|
||||||
Прошивка принимает поток PDM с микрофона SPH9855LM4H-C, приводит его к
|
Прошивка ПЛИС GW5A-25 принимает поток PDM с микрофона SPH9855LM4H-C,
|
||||||
**24 бит / 12 500 Гц** и отдаёт отсчёты кадрами по UART.
|
подключённого по линии LVDS (SN65LVDS), приводит его к **24 бит / 12 500 Гц**
|
||||||
|
и отдаёт отсчёты сразу в две стороны: кадрами по UART через встроенный мост
|
||||||
|
BL616 на ПК и по I2S на ЦАП PCM5102A, чей аналоговый выход смотрят
|
||||||
|
осциллографом.
|
||||||
|
|
||||||
MIC_CLK 3.125 МГц -> CIC sinc^4 /50 -> 62.5 кГц -> ФНЧ /5 -> **12 500 Гц** ->
|
MIC_CLK 3.125 МГц -> CIC sinc^3 /50 -> 62.5 кГц -> ФНЧ /5 -> **12 500 Гц** ->
|
||||||
кадры по 50 отсчётов -> UART 921600.
|
- кадры по 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 описание стенда
|
||||||
|
```
|
||||||
|
|
||||||
## Тактирование и частоты
|
## Тактирование и частоты
|
||||||
|
|
||||||
@@ -12,10 +54,11 @@ MIC_CLK 3.125 МГц -> CIC sinc^4 /50 -> 62.5 кГц -> ФНЧ /5 -> **12 500
|
|||||||
|:--|:--|:--|
|
|:--|:--|:--|
|
||||||
| Системный клок | 50 МГц (E2) | генератор платы |
|
| Системный клок | 50 МГц (E2) | генератор платы |
|
||||||
| MIC_CLK | 3.125 МГц = 50/16 | Normal mode микрофона 1.1...4.8 МГц |
|
| MIC_CLK | 3.125 МГц = 50/16 | Normal mode микрофона 1.1...4.8 МГц |
|
||||||
| CIC | sinc^4, децимация 50 | до 62.5 кГц, без умножителей |
|
| CIC | sinc^3, децимация 50 | до 62.5 кГц, без умножителей, аккумуляторы 18 бит |
|
||||||
| ФНЧ | 221 отвод, децимация 5 | до 12 500 Гц |
|
| ФНЧ | 221 отвод, децимация 5 | до 12 500 Гц |
|
||||||
| Выходная ЧД | 12 500 Гц | |
|
| Выходная ЧД | 12 500 Гц | |
|
||||||
| UART | 921600, 8N1 | 157 байт на кадр, 250 кадров/с, 39.25 кБ/с (43% канала) |
|
| 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 должна
|
Делитель 16 выбран не произвольно: чтобы обойтись без PLL, частота PDM должна
|
||||||
быть 50 МГц / N, а чтобы децимация до 12 500 Гц была целой, N обязан делить
|
быть 50 МГц / N, а чтобы децимация до 12 500 Гц была целой, N обязан делить
|
||||||
@@ -31,22 +74,26 @@ CIC снимает поток до 62.5 кГц, а всю избирательн
|
|||||||
| Показатель | Значение |
|
| Показатель | Значение |
|
||||||
|:--|:--|
|
|:--|:--|
|
||||||
| неравномерность полосы 0...5000 Гц | 0.032 дБ |
|
| неравномерность полосы 0...5000 Гц | 0.032 дБ |
|
||||||
| подавление от 6250 Гц | 72 дБ |
|
| подавление от 6250 Гц | 76 дБ |
|
||||||
| подавление сворачиваемых полос | 77 дБ |
|
| подавление сворачиваемых полос | 92 дБ |
|
||||||
|
|
||||||
Спад CIC в полосе (до 0.36 дБ на краю) не компенсируется отдельным звеном:
|
Спад CIC в полосе не компенсируется отдельным звеном: он задан прямо в желаемой
|
||||||
он задан прямо в желаемой АЧХ при расчёте ФНЧ.
|
АЧХ при расчёте ФНЧ, поэтому смена порядка CIC требует только перезапуска
|
||||||
|
генератора коэффициентов.
|
||||||
|
|
||||||
|
Порядок 3, а не 4, выбран не из соображений АЧХ — характеристики с ним даже
|
||||||
|
лучше — а по ограничению кристалла, см. ниже.
|
||||||
|
|
||||||
Коэффициенты и разрядности лежат в `src/fir_coefs_pkg.vhd`, файл генерируется:
|
Коэффициенты и разрядности лежат в `src/fir_coefs_pkg.vhd`, файл генерируется:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
py -3 tools/gen_fir_coefs.py
|
python tools/gen_fir_coefs.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Шкала
|
## Шкала
|
||||||
|
|
||||||
Постоянная `'1'` на выводе данных микрофона даёт полную шкалу 24 бит
|
Постоянная `'1'` на выводе данных микрофона даёт полную шкалу 24 бит
|
||||||
(код 8 388 567). Отсюда шкала выхода — dBFS, ровно та, в которой микрофон
|
(код 8 388 596). Отсюда шкала выхода — dBFS, ровно та, в которой микрофон
|
||||||
специфицирован. Вольт в тракте нет, `V_REF` в приёмных скриптах не нужен.
|
специфицирован. Вольт в тракте нет, `V_REF` в приёмных скриптах не нужен.
|
||||||
|
|
||||||
## Формат кадра UART
|
## Формат кадра UART
|
||||||
@@ -62,34 +109,126 @@ py -3 tools/gen_fir_coefs.py
|
|||||||
SYNC в CRC не входит: он нужен приёмнику для ресинхронизации до того, как тот
|
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_50m | тактовый генератор платы | E2 |
|
Кратко: clk E2, MIC_CLK K8, MIC_DAT K9, ЦАП G1/G2/L4/L3 (SCK/BCK/LRCK/DIN),
|
||||||
| uart_tx | UART на внешний мост, ПЛИС -> ПК | G5 |
|
UART_TX C3, кнопка H11, светодиод E8, отладочный выход PLL E3.
|
||||||
| led0 | светодиод | E8 |
|
|
||||||
| MIC_CLK | тактирование микрофона, 3.125 МГц | при разводке |
|
|
||||||
| MIC_DAT | однобитный поток PDM | при разводке |
|
|
||||||
| MIC_SEL | вывод SELECT микрофона | при разводке |
|
|
||||||
|
|
||||||
Выводы микрофона в `src/DigMic.cst` закомментированы, номера подставляются при
|
### Точка выборки через линию LVDS
|
||||||
разводке. Банк 3.3 В, питание микрофона 1.65...3.6 В.
|
|
||||||
|
|
||||||
`MIC_SEL` прошивка держит в `'0'`: микрофон выставляет бит по спаду MIC_CLK,
|
Кабель, драйверы и приёмники SN65LVDS на обоих концах и задержка выдачи самого
|
||||||
выборка идёт в середине низкой фазы. Если вывод SELECT посажен на питание
|
микрофона сдвигают данные относительно MIC_CLK, и точка выборки для микрофона
|
||||||
платой, поменять `SEL_HIGH` в `Top.vhd` на `'1'` — точка выборки переедет сама.
|
на коротких проводах сюда не подходит.
|
||||||
|
Фаза измерена на плате: 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) на этом корпусе сидят на двойных функциях
|
Выводы `clk` (E2) и `LED` (E8) на этом корпусе сидят на двойных функциях
|
||||||
(SSPI/CPU и READY), поэтому в свойствах проекта их надо разрешить как обычные
|
(SSPI/CPU и READY), поэтому в свойствах проекта их надо разрешить как обычные
|
||||||
выводы, иначе размещение падает с PR2017. В IDE это флажки CPU, SSPI и READY,
|
выводы, иначе размещение падает с PR2017. Это уже сделано в
|
||||||
в `gw_sh` -- `set_option -use_cpu_as_gpio 1 -use_sspi_as_gpio 1
|
`impl/DigMic_process_config.json`, который лежит в репозитории; в IDE это
|
||||||
-use_ready_as_gpio 1`.
|
флажки CPU, SSPI и READY, в `gw_sh` — `set_option -use_cpu_as_gpio 1
|
||||||
|
-use_sspi_as_gpio 1 -use_ready_as_gpio 1`.
|
||||||
|
|
||||||
Занято на GW5A-25: 778 LUT/ALU (4%), 616 регистров (3%), 5 BSRAM (9%),
|
IP-ядро `src/gowin_pll` (с `src/pll_init.v`) нужно только отладочному выходу
|
||||||
1 DSP (4%). Fmax 164 МГц при требуемых 50, нарушений установки и удержания нет.
|
`clk_80MHz` на E3, тракт от него не зависит. Ядро сгенерировано ещё под
|
||||||
|
ревизию B; под A проект с ним собирается, но перегенерировать его стоит.
|
||||||
|
|
||||||
|
Занято на GW5A-25 вместе с ветвью ЦАП и кнопкой: 1179 LUT/ALU (6%),
|
||||||
|
804 регистра (4%), 5 BSRAM (9%), 3 DSP (11%).
|
||||||
|
Тайминг сходится с запасом, но полагаться на него как на признак исправности
|
||||||
|
нельзя — см. раздел про ограничения кристалла выше.
|
||||||
|
|
||||||
## Состав
|
## Состав
|
||||||
|
|
||||||
@@ -97,58 +236,168 @@ SYNC в CRC не входит: он нужен приёмнику для рес
|
|||||||
|:--|:--|
|
|:--|:--|
|
||||||
| `src/Top.vhd` | верхний уровень: только экземпляры и связи |
|
| `src/Top.vhd` | верхний уровень: только экземпляры и связи |
|
||||||
| `src/pdm_rx.vhd` | тактирование микрофона и выборка бита в нужной фазе |
|
| `src/pdm_rx.vhd` | тактирование микрофона и выборка бита в нужной фазе |
|
||||||
| `src/cic_decim.vhd` | CIC sinc^4, децимация 50 |
|
| `src/cic_decim.vhd` | CIC sinc^3, децимация 50 |
|
||||||
| `src/fir_decim.vhd` | ФНЧ с децимацией 5, последовательная свёртка |
|
| `src/fir_decim.vhd` | ФНЧ с децимацией 5, последовательная свёртка |
|
||||||
| `src/fir_coefs_pkg.vhd` | коэффициенты ФНЧ, генерируется |
|
| `src/fir_coefs_pkg.vhd` | коэффициенты ФНЧ, генерируется |
|
||||||
| `src/fifo_top.vhd` | буфер отсчётов, заглушка под IP-ядро Gowin |
|
| `src/fifo_top.vhd` | буфер отсчётов, заглушка под IP-ядро Gowin |
|
||||||
| `src/Packets_creator.vhd` | сборка кадра с CRC-16 |
|
| `src/Packets_creator.vhd` | сборка кадра с CRC-16 |
|
||||||
| `src/UART.vhd` | передатчик |
|
| `src/UART.vhd` | передатчик |
|
||||||
|
| `src/dac_stream.v` | ветвь ЦАП: усиление, пересчёт в 48 828 Гц, I2S и SCK |
|
||||||
|
| `src/i2s_transmitter.v` | передатчик I2S, без изменений из прошивки проверки ЦАП |
|
||||||
| `src/led_status.vhd` | индикация частотой мигания |
|
| `src/led_status.vhd` | индикация частотой мигания |
|
||||||
|
| `src/gain_button.vhd` | кнопка усиления ЦАП: дребезг, ступени, показ светодиодом |
|
||||||
|
| `src/gowin_pll/`, `src/pll_init.v` | PLL для отладочного выхода 80 МГц |
|
||||||
| `src/DigMic.cst` | привязка выводов |
|
| `src/DigMic.cst` | привязка выводов |
|
||||||
|
| `src/DigMic.sdc` | ограничение клока 50 МГц |
|
||||||
| `tb/pdm_mic_model.vhd` | поведенческая модель PDM-микрофона |
|
| `tb/pdm_mic_model.vhd` | поведенческая модель PDM-микрофона |
|
||||||
| `tb/tb_top.vhd` | сквозной тест: модель -> ПЛИС -> UART -> разбор кадра |
|
| `tb/tb_top.vhd` | сквозной тест: модель -> линия LVDS -> ПЛИС -> UART -> разбор кадра |
|
||||||
|
| `tb/tb_dac_stream.v` | тест ветви ЦАП: отсчёты -> I2S на выводах -> разбор слов |
|
||||||
|
| `tb/tb_gain_button.vhd` | тест кнопки: дребезг, ступени по кругу, вспышки |
|
||||||
| `tools/gen_fir_coefs.py` | расчёт коэффициентов ФНЧ |
|
| `tools/gen_fir_coefs.py` | расчёт коэффициентов ФНЧ |
|
||||||
| `tools/uart_capture.py` | приём кадров, запись `.raw` и `.bin` |
|
| `tools/uart_capture.py` | приём кадров, запись `.raw` и `.bin` |
|
||||||
| `tools/uart_live.py` | живой просмотр: осциллограмма, спектр, счётчики |
|
| `tools/uart_live.py` | живой просмотр: осциллограмма, спектр, счётчики |
|
||||||
|
| `tools/uart_audio.py` | прослушивание: поток на звуковую карту ПК |
|
||||||
|
| `tools/Bin_to_wav.py` | перевод записанного `.bin` в WAV |
|
||||||
|
| `tools/requirements.txt` | пакеты Python для скриптов |
|
||||||
|
|
||||||
## Симуляция (GHDL)
|
## Симуляция
|
||||||
|
|
||||||
|
Сквозной тест, GHDL:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p build && ghdl -a --std=08 --workdir=build src/fifo_top.vhd src/UART.vhd src/led_status.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 -e --std=08 --workdir=build -o build/tb_top tb_top && build/tb_top --stop-time=400ms
|
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` —
|
постоянные `'1'` и `'0'` — плюс и минус полная шкала, чередование `1010` —
|
||||||
ровно ноль (эта частота попадает в нуль АЧХ CIC). Четвёртый — синус, у него
|
ровно ноль (эта частота попадает в нуль АЧХ CIC). Четвёртый — синус, у него
|
||||||
проверяется размах. Тракт настраивается около 10 мс, поэтому в каждом режиме
|
проверяется размах. Тракт настраивается около 10 мс, поэтому в каждом режиме
|
||||||
кадры читаются до первого сошедшегося.
|
кадры читаются до первого сошедшегося.
|
||||||
|
|
||||||
## Запись с железа
|
Ветвь ЦАП и PLL написаны на Verilog, поэтому GHDL их не собирает и
|
||||||
|
предупреждает, что `dac_stream_inst` и `PLL_dbg` не привязаны; тракт UART от
|
||||||
|
этого не зависит. Ветвь ЦАП проверяется отдельно, в Icarus Verilog:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
py -3 tools/uart_capture.py --list
|
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
|
```bash
|
||||||
py -3 tools/uart_capture.py --port COM5 --seconds 10 --out test1
|
python tools/uart_capture.py --port COM14 --seconds 10 --out test1
|
||||||
```
|
```
|
||||||
|
|
||||||
Получаются `test1.raw` (весь поток как пришёл, для отладки канала),
|
Получаются `test1.raw` (весь поток как пришёл, для отладки канала),
|
||||||
`test1.bin` (только отсчёты, читается `read_24bit_bin()`) и `test1.log`.
|
`test1.bin` (только отсчёты, читается `read_24bit_bin()`) и `test1.log`.
|
||||||
Кадр с несошедшейся CRC отбрасывается, пропуск по FRAME_CNT добивается нулями,
|
Кадр с несошедшейся CRC отбрасывается, пропуск по FRAME_CNT добивается нулями,
|
||||||
чтобы не сдвинулась ось времени.
|
чтобы не сдвинулась ось времени. Ключ `--dump` пишет только `.raw`, без
|
||||||
|
разбора, и показывает, теряет ли байты сам канал.
|
||||||
|
|
||||||
## Живой просмотр
|
`.bin` переводится в WAV для прослушивания или отправки:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
py -3 tools/uart_live.py --port COM10
|
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 и счётчики кадров в реальном
|
Осциллограмма в долях шкалы, спектр в dBFS и счётчики кадров в реальном
|
||||||
времени. Управление кнопками внизу окна, те же действия — клавишами `r`, `a`,
|
времени. Управление кнопками внизу окна, те же действия — клавишами `r`, `a`,
|
||||||
`d`, `s`, `f`, выход `q`.
|
`d`, `s`, `f`, выход `q`. Запись встроена в просмотр по необходимости:
|
||||||
|
запустить `uart_capture.py` на том же порту параллельно нельзя.
|
||||||
|
|
||||||
Запись встроена в просмотр по необходимости: COM-порт держит один процесс, и
|
### Прослушивание
|
||||||
запустить `uart_capture.py` на том же порту параллельно нельзя. Разбор кадров у
|
|
||||||
обоих инструментов общий (`FrameParser` в `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 |
|
||||||
|
|||||||
+211
@@ -0,0 +1,211 @@
|
|||||||
|
# Стенд цифрового микрофона
|
||||||
|
|
||||||
|
Стенд проверяет цифровой микрофон SPH9855LM4H-C, вынесенный на кабель с
|
||||||
|
линией LVDS, вместе с трактом его обработки в ПЛИС. ПЛИС тактирует микрофон,
|
||||||
|
принимает поток PDM, приводит его к **24 бит / 12 500 Гц** и отдаёт отсчёты
|
||||||
|
сразу в две стороны:
|
||||||
|
|
||||||
|
- **на ПК по USB** — кадры с CRC: запись, живой просмотр, прослушивание;
|
||||||
|
- **на ЦАП PCM5102A** — аналоговый сигнал, который смотрят осциллографом.
|
||||||
|
|
||||||
|
Обе ветви получают одни и те же отсчёты. Кнопкой на плате поднимается
|
||||||
|
усиление только ветви ЦАП, на ПК всегда уходит исходная шкала.
|
||||||
|
|
||||||
|
## Состав
|
||||||
|
|
||||||
|
| Узел | Что это | Роль на стенде |
|
||||||
|
|:--|:--|:--|
|
||||||
|
| Плата микрофона | SPH9855LM4H-C и приёмопередатчики SN65LVDS | PDM-микрофон; вывод SELECT задан на плате |
|
||||||
|
| Кабель | две пары LVDS: CLK к микрофону, DATA от микрофона | SN65LVDS на обоих концах |
|
||||||
|
| Tang Primer 25K + Dock | модуль GW5A-LV25MG121NC1/I0, **ревизия A** | тактирование микрофона, дециматор, кадры UART, I2S |
|
||||||
|
| Мост BL616 на Dock | встроенный отладчик с заменённой прошивкой (отдельный проект BL616): DirtyJTAG + CDC-UART | загрузка битстрима и UART на ПК по одному USB-C |
|
||||||
|
| Модуль ЦАП | PCM5102A на своей плате, питание через LDO TPS7A2033 | I2S -> аналог, выход OUTL на BNC |
|
||||||
|
| Осциллограф | — | смотрит выход ЦАП |
|
||||||
|
| ПК | Windows, Python 3.10+, openFPGALoader | сборка, загрузка, запись, просмотр, прослушивание |
|
||||||
|
|
||||||
|
## Схема
|
||||||
|
|
||||||
|
```
|
||||||
|
Плата микрофона Tang Primer 25K + Dock (GW5A-25A)
|
||||||
|
+----------------------+ кабель LVDS +--------------------------------------+
|
||||||
|
| SPH9855LM4H-C | | |
|
||||||
|
| CLK <-- SN65LVDS <====== пара CLK ======< SN65LVDS <-- K8 MIC_CLK 3.125 МГц |
|
||||||
|
| DATA --> SN65LVDS >====== пара DATA =====> SN65LVDS --> K9 MIC_DAT PDM |
|
||||||
|
| SELECT: на плате | | |
|
||||||
|
+----------------------+ | E2 генератор 50 МГц |
|
||||||
|
| PDM -> CIC -> ФНЧ -> 12 500 Гц |
|
||||||
|
+----------------------+ I2S | |
|
||||||
|
| Модуль ЦАП PCM5102A |<--------------------+ G1 SCK G2 BCK L4 LRCK L3 DIN |
|
||||||
|
| OUTL -> BNC ------+--> осциллограф | |
|
||||||
|
+----------------------+ | C3 UART_TX --> BL616 --+ |
|
||||||
|
| JTAG <--------- BL616 <-+--> USB-C --> ПК
|
||||||
|
| | COM14 + DirtyJTAG
|
||||||
|
| H11 кнопка S1: усиление ЦАП |
|
||||||
|
| E8 светодиод: состояние, усиление |
|
||||||
|
| E3 отладка: выход PLL 80 МГц |
|
||||||
|
+--------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
Земля у ПЛИС, линии LVDS и модуля ЦАП общая.
|
||||||
|
|
||||||
|
## Соединения
|
||||||
|
|
||||||
|
| Цепь | Вывод ПЛИС | Банк | Где на плате | Куда идёт |
|
||||||
|
|:--|:--:|:--:|:--|:--|
|
||||||
|
| clk | E2 | 5 | генератор 50 МГц | системный клок; весь тракт и I2S делятся от него, без PLL |
|
||||||
|
| MIC_CLK | K8 | 0 | гребёнка J3 | вход драйвера SN65LVDS, пара CLK к микрофону, 3.125 МГц |
|
||||||
|
| MIC_DAT | K9 | 0 | гребёнка J3 | выход приёмника SN65LVDS, пара DATA от микрофона; подтяжка ПЛИС вниз |
|
||||||
|
| DAC_SCK | G1 | 2 | гребёнка J3 | SCK модуля ЦАП, 12.5 МГц = 256 fS |
|
||||||
|
| I2S_BCK | G2 | 2 | гребёнка J3 | BCK модуля ЦАП, 3.125 МГц = 64 fS |
|
||||||
|
| I2S_LRCK | L4 | 2 | гребёнка J3 | LRCK модуля ЦАП, fS = 48 828 Гц |
|
||||||
|
| I2S_DIN | L3 | 2 | гребёнка J3 | DIN модуля ЦАП |
|
||||||
|
| UART_TX | C3 | 4 | цепь `FPGA_UART_TX` Dock | RX моста BL616 -> COM-порт на ПК, 921600 8N1 |
|
||||||
|
| BTN_GAIN | H11 | 7 | кнопка S1 Dock | замыкает вывод на +3.3 В через 100 Ом; подтяжка ПЛИС вниз |
|
||||||
|
| LED | E8 | 4 | светодиод (READY) | индикация, см. ниже |
|
||||||
|
| clk_80MHz | E3 | 4 | гребёнка J3 | только отладка: выход PLL, к тракту не относится |
|
||||||
|
|
||||||
|
Привязка выводов — в [`src/DigMic.cst`](../src/DigMic.cst). Номера контактов
|
||||||
|
гребёнки J3 — по схеме Dock (`Tang_25K_60033.pdf`, цепи вида `K8_IOT56B_40P`).
|
||||||
|
|
||||||
|
Направления MIC_CLK и MIC_DAT проверены на плате до первой прошивки
|
||||||
|
диагностической прошивкой, которая ничего не выдаёт на спорные выводы: K8
|
||||||
|
висит на подтяжке ПЛИС (вход драйвера), K9 держит приёмник.
|
||||||
|
|
||||||
|
### Питание
|
||||||
|
|
||||||
|
- **Dock** — от USB-C; тот же разъём даёт JTAG и UART через BL616.
|
||||||
|
- **VCCIO банков 6/7** на этой плате подаётся снаружи, через контакты 29 и 31
|
||||||
|
гребёнки. Без 3.3 В на них не работает кнопка S1 (H11, банк 7).
|
||||||
|
- **Модуль ЦАП** — на разъём X1 (цепь `+5.5V`), дальше два LDO TPS7A2033
|
||||||
|
раздельно дают аналоговые и цифровые 3.3 В. На BNC выведен только OUTL;
|
||||||
|
ПЛИС шлёт один и тот же отсчёт в оба канала.
|
||||||
|
- **Сторона микрофона** — по схеме платы микрофона; сам микрофон допускает
|
||||||
|
1.65...3.6 В.
|
||||||
|
|
||||||
|
## ПО на ПК
|
||||||
|
|
||||||
|
| Что | Зачем |
|
||||||
|
|:--|:--|
|
||||||
|
| Gowin EDA V1.9.12 | сборка в IDE |
|
||||||
|
| openFPGALoader ≥ 1.1.0 или `TangProgrammer.exe` — оба лежат в проекте BL616 | загрузка битстрима через DirtyJTAG |
|
||||||
|
| Python 3.10+ и пакеты из [`tools/requirements.txt`](../tools/requirements.txt) | запись, просмотр, прослушивание |
|
||||||
|
| GHDL, Icarus Verilog | только симуляция |
|
||||||
|
|
||||||
|
Gowin Programmer с заменённой прошивкой BL616 **не работает**: он видит
|
||||||
|
только мост со штатной прошивкой Sipeed. Вернуть штатную —
|
||||||
|
`flash.ps1 -Restore` в проекте BL616, но со штатной UART BL616 работает хуже.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install -r tools/requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Порядок работы
|
||||||
|
|
||||||
|
### 1. Собрать
|
||||||
|
|
||||||
|
Gowin IDE: открыть `DigMic.gprj`, Run All.
|
||||||
|
|
||||||
|
Битстрим — `impl\pnr\DigMic.fs`. Проект собирается под Device Version **A**
|
||||||
|
(`gw5a25a-002`). Это важно: битстрим под B на этой плате не выдаёт ни
|
||||||
|
байта, а программатор расхождения не замечает, JTAG-код у ревизий одинаковый.
|
||||||
|
|
||||||
|
### 2. Загрузить
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
openFPGALoader -c dirtyJtag impl\pnr\DigMic.fs
|
||||||
|
```
|
||||||
|
|
||||||
|
Битстрим живёт в SRAM до выключения питания. Чтобы ПЛИС стартовала с ним
|
||||||
|
сама, его записывают во флеш: `openFPGALoader -c dirtyJtag -f impl\pnr\DigMic.fs`.
|
||||||
|
То же самое умеет `TangProgrammer.exe`, а с флажком «Загружать в SRAM
|
||||||
|
автоматически» он заливает каждую новую сборку из IDE.
|
||||||
|
|
||||||
|
### 3. Проверить по светодиоду
|
||||||
|
|
||||||
|
| Светодиод | Что значит |
|
||||||
|
|:--|:--|
|
||||||
|
| не мигает, горит или погашен | клока нет или битстрим не загружен |
|
||||||
|
| медленно, 2 Гц | клок есть, отсчёты с дециматора не приходят |
|
||||||
|
| быстро, 10 Гц | отсчёты идут: тракт PDM -> CIC -> ФНЧ работает |
|
||||||
|
| пауза, N коротких вспышек, пауза | только что нажали S1: выбрана ступень N |
|
||||||
|
| пауза, одна длинная вспышка 1 с, пауза | только что нажали S1: ступень 0 |
|
||||||
|
|
||||||
|
Рабочее состояние — быстрое мигание.
|
||||||
|
|
||||||
|
### 4. Посмотреть поток на ПК
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python tools/uart_live.py --port COM14
|
||||||
|
```
|
||||||
|
|
||||||
|
COM14 — порт моста BL616 на этом ПК; на другом номер другой, его
|
||||||
|
показывает `python tools/uart_capture.py --list`. Порт держит только одна
|
||||||
|
программа: `uart_live`, `uart_capture` и `uart_audio` по очереди.
|
||||||
|
|
||||||
|
В строке состояния исправный стенд показывает:
|
||||||
|
|
||||||
|
| Показатель | Норма |
|
||||||
|
|:--|:--|
|
||||||
|
| кадров/с | 250 |
|
||||||
|
| CRC, потеряно, ресинхр | 0; одна-две ошибки CRC и ресинхронизации в первый момент — норма: порт открылся посреди кадра |
|
||||||
|
| STATUS | чисто (`FIFO=1` — переполнение буфера, `SAT=1` — выход упёрся в шкалу) |
|
||||||
|
| тишина в комнате | СКО около −72 dBFS |
|
||||||
|
| смещение нуля | около −90 dBFS |
|
||||||
|
| речь рядом с микрофоном | пики около −50 dBFS |
|
||||||
|
|
||||||
|
### 5. Посмотреть выход ЦАП
|
||||||
|
|
||||||
|
Осциллограф — на BNC модуля ЦАП. Полная шкала 24 бит — 2.1 В скз на выходе
|
||||||
|
PCM5102A. При ступени 0 сигнал там в той же шкале, что на ПК, то есть
|
||||||
|
доли и единицы милливольт. Каждое нажатие S1 даёт ровно +6.02 дБ (сдвиг на
|
||||||
|
разряд), после ступени 8 снова 0:
|
||||||
|
|
||||||
|
| Ступень S1 | Усиление | Фон комнаты, −72 dBFS | Речь, −50 dBFS |
|
||||||
|
|:--:|:--:|:--:|:--:|
|
||||||
|
| 0 | 0 дБ | ≈ 0.5 мВ скз | ≈ 7 мВ |
|
||||||
|
| 4 | +24 дБ | ≈ 8 мВ | ≈ 0.1 В |
|
||||||
|
| 8 | +48 дБ | ≈ 0.13 В | ≈ 1.7 В, у предела шкалы |
|
||||||
|
|
||||||
|
На ступени 8 всё громче примерно 85 дБ SPL упирается в шкалу и ограничивается;
|
||||||
|
признака этого нигде нет, на ПК при этом шкала прежняя. Полоса выхода, как у
|
||||||
|
всего тракта, — до 5 кГц, задержка относительно UART — один отсчёт, 80 мкс.
|
||||||
|
|
||||||
|
### 6. Записать и послушать
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python tools/uart_capture.py --port COM14 --seconds 10 --out test1
|
||||||
|
python tools/Bin_to_wav.py test1.bin
|
||||||
|
python tools/uart_audio.py --port COM14
|
||||||
|
```
|
||||||
|
|
||||||
|
`uart_capture` пишет `test1.raw` (поток как есть), `test1.bin` (только
|
||||||
|
отсчёты, 3 байта BE) и `test1.log` со сводкой: те же показатели, что в
|
||||||
|
таблице п. 4. `Bin_to_wav` делает из `.bin` WAV. `uart_audio` отправляет
|
||||||
|
поток на звуковую карту ПК, слушать лучше в наушниках. Подробности — в
|
||||||
|
[README](../README.md#по-на-пк).
|
||||||
|
|
||||||
|
## Если не работает
|
||||||
|
|
||||||
|
| Признак | Вероятная причина | Что сделать |
|
||||||
|
|:--|:--|:--|
|
||||||
|
| светодиод не мигает | битстрим не загружен или нет клока | загрузить заново; с `LED_SRC = 3` в `Top.vhd` светодиод мигает ровно 1 Гц прямо от clk: другой период значит, что клок не 50 МГц |
|
||||||
|
| светодиод мигает медленно | отсчёты не идут: обрыв MIC_CLK или MIC_DAT, нет питания линии или микрофона | осциллографом: 3.125 МГц на K8 и PDM на K9; `LED_SRC = 2` — мигание от битов PDM, `1` — от CIC: быстрое мерцание значит, что этот участок живой |
|
||||||
|
| на ПК ни одного байта | битстрим собран под ревизию B; открыт не тот порт; UART не на C3 | проверить Device Version **A** в проекте; `uart_capture.py --list`; `UART_TX` в `DigMic.cst` |
|
||||||
|
| CRC или ресинхронизации растут постоянно | скорость порта не 921600 | `--baud 921600`, это значение по умолчанию |
|
||||||
|
| кадров/с меньше 250, `потеряно` растёт | байты теряются в мосте или драйвере, либо разбор не успевает | `uart_capture.py --port COM14 --dump`: только приём без разбора, сравнивает скорость с ожидаемой и говорит, где потери |
|
||||||
|
| сигнал пропал или стал шумом после замены кабеля или SN65LVDS | сдвинулась точка выборки бита | перемерить фазу смены MIC_DAT относительно MIC_CLK и поправить `MIC_SMP_CNT` в `Top.vhd`, см. README |
|
||||||
|
| кнопка S1 не меняет ступень | нет 3.3 В на VCCIO банков 6/7 | подать 3.3 В на контакты 29 и 31 гребёнки |
|
||||||
|
| на ПК сигнал есть, на осциллографе нет | ступень 0: на выходе доли мВ; нет питания модуля ЦАП | нажать S1 несколько раз; проверить питание X1 и общую землю |
|
||||||
|
| `could not open port` | порт держит другая программа | закрыть `uart_live` / `uart_audio` / терминал |
|
||||||
|
| Gowin Programmer не видит кабель | на BL616 заменённая прошивка | грузить через openFPGALoader или TangProgrammer |
|
||||||
|
|
||||||
|
### Чего не делать
|
||||||
|
|
||||||
|
Два ограничения кристалла найдены на железе ещё при сборке под ревизию B. Под
|
||||||
|
A их пока не перепроверяли, поэтому они остаются в силе:
|
||||||
|
|
||||||
|
- **не расширять аккумуляторы CIC сверх 18 бит**: при 20 битах и больше
|
||||||
|
тракт молча отдаёт нули, хотя синтез, тайминг и симуляция чистые;
|
||||||
|
- **не добавлять сброс по включению**: счётчик, держащий `rst_n`, убивает тракт.
|
||||||
|
|
||||||
|
Подробности — в README, раздел про ограничения кристалла.
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"BACKGROUND_PROGRAMMING" : "off",
|
||||||
|
"COMPRESS" : false,
|
||||||
|
"CPU" : true,
|
||||||
|
"CRC_CHECK" : true,
|
||||||
|
"Clock_Route_Order" : 0,
|
||||||
|
"Convert_SDP32_36_to_SDP16_18" : true,
|
||||||
|
"Correct_Hold_Violation" : true,
|
||||||
|
"DONE" : true,
|
||||||
|
"DOWNLOAD_SPEED" : "default",
|
||||||
|
"Disable_Insert_Pad" : false,
|
||||||
|
"ENABLE_CTP" : false,
|
||||||
|
"ENABLE_MERGE_MODE" : false,
|
||||||
|
"ENCRYPTION_KEY" : false,
|
||||||
|
"ENCRYPTION_KEY_TEXT" : "00000000000000000000000000000000",
|
||||||
|
"ERROR_DECTION_AND_CORRECTION" : false,
|
||||||
|
"ERROR_DECTION_ONLY" : false,
|
||||||
|
"ERROR_INJECTION" : false,
|
||||||
|
"EXTERNAL_MASTER_CONFIG_CLOCK" : false,
|
||||||
|
"Enable_DSRM" : false,
|
||||||
|
"FORMAT" : "binary",
|
||||||
|
"FREQUENCY_DIVIDER" : "1",
|
||||||
|
"Generate_Constraint_File_of_Ports" : false,
|
||||||
|
"Generate_IBIS_File" : false,
|
||||||
|
"Generate_Plain_Text_Timing_Report" : false,
|
||||||
|
"Generate_Post_PNR_Simulation_Model_File" : false,
|
||||||
|
"Generate_Post_Place_File" : false,
|
||||||
|
"Generate_SDF_File" : false,
|
||||||
|
"Generate_VHDL_Post_PNR_Simulation_Model_File" : false,
|
||||||
|
"Global_Freq" : "default",
|
||||||
|
"GwSyn_Loop_Limit" : 2000,
|
||||||
|
"HOTBOOT" : false,
|
||||||
|
"I2C" : false,
|
||||||
|
"I2C_SLAVE_ADDR" : "00",
|
||||||
|
"INCREMENTAL_PLACE_AND_ROUTING" : "0",
|
||||||
|
"INCREMENTAL_PLACE_ONLY" : "0",
|
||||||
|
"IncludePath" : [
|
||||||
|
|
||||||
|
],
|
||||||
|
"Incremental_Compile" : "",
|
||||||
|
"Initialize_Primitives" : false,
|
||||||
|
"JTAG" : false,
|
||||||
|
"MODE_IO" : false,
|
||||||
|
"MSPI" : false,
|
||||||
|
"MSPI_JUMP" : false,
|
||||||
|
"MULTIBOOT_ADDRESS_WIDTH" : "24",
|
||||||
|
"MULTIBOOT_MODE" : "Single",
|
||||||
|
"MULTIBOOT_SPI_FLASH_ADDRESS" : "000000",
|
||||||
|
"MULTIJUMP_ADDRESS_WIDTH" : "24",
|
||||||
|
"MULTIJUMP_MODE" : "Single",
|
||||||
|
"MULTIJUMP_SPI_FLASH_ADDRESS" : "000000",
|
||||||
|
"Multi_Boot" : false,
|
||||||
|
"OUTPUT_BASE_NAME" : "DigMic",
|
||||||
|
"POWER_ON_RESET_MONITOR" : true,
|
||||||
|
"PRINT_BSRAM_VALUE" : true,
|
||||||
|
"PROGRAM_DONE_BYPASS" : false,
|
||||||
|
"PlaceInRegToIob" : true,
|
||||||
|
"PlaceIoRegToIob" : true,
|
||||||
|
"PlaceOutRegToIob" : true,
|
||||||
|
"Place_Option" : "0",
|
||||||
|
"Process_Configuration_Verion" : "1.0",
|
||||||
|
"Promote_Physical_Constraint_Warning_to_Error" : true,
|
||||||
|
"READY" : true,
|
||||||
|
"RECONFIG_N" : false,
|
||||||
|
"Ram_RW_Check" : false,
|
||||||
|
"Replicate_Resources" : false,
|
||||||
|
"Report_Auto-Placed_Io_Information" : false,
|
||||||
|
"Route_Maxfan" : 23,
|
||||||
|
"Route_Option" : "0",
|
||||||
|
"Run_Timing_Driven" : true,
|
||||||
|
"SECURE_MODE" : false,
|
||||||
|
"SECURITY_BIT" : true,
|
||||||
|
"SEU_HANDLER" : false,
|
||||||
|
"SEU_HANDLER_CHECKSUM" : false,
|
||||||
|
"SEU_HANDLER_MODE" : "auto",
|
||||||
|
"SSPI" : true,
|
||||||
|
"STOP_SEU_HANDLER" : false,
|
||||||
|
"SerDes_retiming" : false,
|
||||||
|
"Show_All_Warnings" : false,
|
||||||
|
"Synthesize_tool" : "GowinSyn",
|
||||||
|
"TclPre" : "",
|
||||||
|
"TopModule" : "",
|
||||||
|
"USERCODE" : "default",
|
||||||
|
"Unused_Pin" : "As_input_tri_stated_with_pull_up",
|
||||||
|
"VCC" : "0.9",
|
||||||
|
"VCCAUX" : "3.3",
|
||||||
|
"VCCX" : "3.3",
|
||||||
|
"VHDL_Standard" : "VHDL_Std_1993",
|
||||||
|
"Verilog_Standard" : "Vlg_Std_2001",
|
||||||
|
"WAKE_UP" : "0",
|
||||||
|
"show_all_warnings" : false,
|
||||||
|
"turn_off_bg" : false
|
||||||
|
}
|
||||||
+24
-14
@@ -1,21 +1,31 @@
|
|||||||
//Copyright (C)2014-2025 Gowin Semiconductor Corporation.
|
//Copyright (C)2014-2025 Gowin Semiconductor Corporation.
|
||||||
|
//All rights reserved.
|
||||||
//File Title: Physical Constraints file
|
//File Title: Physical Constraints file
|
||||||
|
//Tool Version: V1.9.12 (64-bit)
|
||||||
//Part Number: GW5A-LV25MG121NC1/I0
|
//Part Number: GW5A-LV25MG121NC1/I0
|
||||||
//Device: GW5A-25
|
//Device: GW5A-25
|
||||||
|
//Device Version: A
|
||||||
|
//Created Time: Thu 10 01 17:59:10 2026
|
||||||
|
|
||||||
// Служебные цепи платы Tang Primer 25K
|
IO_LOC "clk_80MHz" E3;
|
||||||
IO_LOC "clk" E2;
|
IO_PORT "clk_80MHz" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=NONE BANK_VCCIO=3.3;
|
|
||||||
IO_LOC "UART_TX" G5;
|
|
||||||
IO_PORT "UART_TX" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
|
||||||
IO_LOC "LED" E8;
|
IO_LOC "LED" E8;
|
||||||
IO_PORT "LED" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
IO_PORT "LED" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "DAC_SCK" G1;
|
||||||
// Микрофон: выводы выбираются при разводке, банк 3.3 В (Vdd микрофона до 3.6 В).
|
IO_PORT "DAC_SCK" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
// Раскомментировать и подставить номера.
|
IO_LOC "I2S_DIN" L3;
|
||||||
//IO_LOC "MIC_CLK" ??;
|
IO_PORT "I2S_DIN" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
//IO_PORT "MIC_CLK" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
IO_LOC "I2S_LRCK" L4;
|
||||||
//IO_LOC "MIC_DAT" ??;
|
IO_PORT "I2S_LRCK" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
//IO_PORT "MIC_DAT" IO_TYPE=LVCMOS33 PULL_MODE=NONE BANK_VCCIO=3.3;
|
IO_LOC "I2S_BCK" G2;
|
||||||
//IO_LOC "MIC_SEL" ??;
|
IO_PORT "I2S_BCK" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
//IO_PORT "MIC_SEL" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
IO_LOC "UART_TX" C3;
|
||||||
|
IO_PORT "UART_TX" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "MIC_CLK" K8;
|
||||||
|
IO_PORT "MIC_CLK" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "BTN_GAIN" H11;
|
||||||
|
IO_PORT "BTN_GAIN" IO_TYPE=LVCMOS33 PULL_MODE=DOWN BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "MIC_DAT" K9;
|
||||||
|
IO_PORT "MIC_DAT" IO_TYPE=LVCMOS33 PULL_MODE=DOWN BANK_VCCIO=3.3;
|
||||||
|
IO_LOC "clk" E2;
|
||||||
|
IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=NONE BANK_VCCIO=3.3;
|
||||||
|
|||||||
+170
-15
@@ -7,9 +7,13 @@
|
|||||||
-- Project Name: DigMic_prj
|
-- Project Name: DigMic_prj
|
||||||
-- Target Devices: Gowin GW5A-25
|
-- Target Devices: Gowin GW5A-25
|
||||||
-- Description:
|
-- Description:
|
||||||
-- Цифровой микрофон SPH9855 -> UART.
|
-- Цифровой микрофон SPH9855 -> UART и ЦАП PCM5102A.
|
||||||
-- MIC_CLK 3.125 МГц (50/16, Normal mode) -> CIC sinc^4 /50 -> 62.5 кГц ->
|
-- Микрофон подключён по линии LVDS (SN65LVDS): MIC_CLK уходит на вход
|
||||||
-- ФНЧ /5 -> 12 500 Гц, 24 бита -> кадры по 50 отсчётов -> UART 921600.
|
-- драйвера, MIC_DAT приходит с выхода приёмника.
|
||||||
|
-- MIC_CLK 3.125 МГц (50/16, Normal mode) -> CIC sinc^3 /50 -> 62.5 кГц ->
|
||||||
|
-- ФНЧ /5 -> 12 500 Гц, 24 бита, и дальше двумя ветвями:
|
||||||
|
-- кадры по 50 отсчётов -> UART 921600 на ПК;
|
||||||
|
-- пересчёт в 48 828 Гц -> I2S на PCM5102A, аналоговый выход на осциллограф.
|
||||||
--
|
--
|
||||||
-- Постоянная '1' на выводе данных микрофона даёт полную шкалу 24 бит,
|
-- Постоянная '1' на выводе данных микрофона даёт полную шкалу 24 бит,
|
||||||
-- так что шкала выхода -- dBFS, как её задаёт даташит микрофона.
|
-- так что шкала выхода -- dBFS, как её задаёт даташит микрофона.
|
||||||
@@ -21,20 +25,55 @@ use ieee.numeric_std.all;
|
|||||||
entity Top is
|
entity Top is
|
||||||
generic (
|
generic (
|
||||||
SYS_CLK_HZ : integer := 50_000_000;
|
SYS_CLK_HZ : integer := 50_000_000;
|
||||||
BAUD_RATE : integer := 921600
|
BAUD_RATE : integer := 921600;
|
||||||
|
-- Что показывает светодиод:
|
||||||
|
-- 0 -- отсчёты ФНЧ, рабочий режим
|
||||||
|
-- 1 -- отсчёты CIC
|
||||||
|
-- 2 -- биты PDM
|
||||||
|
-- 3 -- маяк ровно 1 Гц прямо от clk, мимо всего тракта
|
||||||
|
-- Режимы 0..2 ищут обрыв тракта: быстрое мерцание значит, что выбранный
|
||||||
|
-- строб живой. Режим 3 меряет сам clk: период не 1 с означает, что
|
||||||
|
-- системная частота не SYS_CLK_HZ, и тогда врут все производные от неё.
|
||||||
|
LED_SRC : integer := 0;
|
||||||
|
-- Усиление ветви ЦАП задаёт кнопка BTN_GAIN: каждое нажатие +6 дБ, после
|
||||||
|
-- DAC_GAIN_MAX (не больше 15) снова 0, после включения 0 -- та же шкала,
|
||||||
|
-- что на UART; на UART всегда идут исходные отсчёты. Полная шкала -- 2.1 В
|
||||||
|
-- скз на выходе ЦАП, фон в комнате около -72 dBFS: при +48 дБ он уже на
|
||||||
|
-- -24 dBFS, а всё громче примерно 85 дБ SPL упирается в шкалу.
|
||||||
|
DAC_GAIN_MAX : integer := 8;
|
||||||
|
-- Такт периода MIC_CLK (0..15), на котором берётся бит PDM, см. pdm_rx.
|
||||||
|
-- Задержка линии LVDS туда и обратно вместе с задержкой выдачи микрофона
|
||||||
|
-- сдвигает смену данных, поэтому точка выборки измерена на железе:
|
||||||
|
-- MIC_DAT меняется только между тактами 1 и 2 (40-60 нс после спада
|
||||||
|
-- MIC_CLK с точностью до целых периодов) и держится весь остальной
|
||||||
|
-- период. Бит берём на такте 12, то есть уровень вывода на
|
||||||
|
-- такте 10 -- около 150 нс запаса в обе стороны. Если поменяется кабель
|
||||||
|
-- или микросхемы линии, фазу надо перемерить. Для микрофона на коротких
|
||||||
|
-- проводах: -1.
|
||||||
|
MIC_SMP_CNT : integer := 12
|
||||||
);
|
);
|
||||||
port (
|
port (
|
||||||
clk : in std_logic;
|
clk : in std_logic;
|
||||||
|
|
||||||
-- Микрофон
|
-- Микрофон по линии LVDS; SELECT задан на плате микрофона
|
||||||
MIC_DAT : in std_logic;
|
MIC_DAT : in std_logic;
|
||||||
MIC_CLK : out std_logic;
|
MIC_CLK : out std_logic;
|
||||||
MIC_SEL : out std_logic;
|
|
||||||
|
|
||||||
-- UART на внешний USB-мост
|
-- UART на внешний USB-мост FT232
|
||||||
UART_TX : out std_logic;
|
UART_TX : out std_logic;
|
||||||
|
|
||||||
LED : out std_logic
|
-- ЦАП PCM5102A
|
||||||
|
I2S_BCK : out std_logic;
|
||||||
|
I2S_LRCK : out std_logic;
|
||||||
|
I2S_DIN : out std_logic;
|
||||||
|
DAC_SCK : out std_logic;
|
||||||
|
|
||||||
|
-- Кнопка усиления ЦАП, нажатие -- '1'
|
||||||
|
BTN_GAIN : in std_logic;
|
||||||
|
|
||||||
|
LED : out std_logic;
|
||||||
|
|
||||||
|
clk_80MHz : out std_logic
|
||||||
);
|
);
|
||||||
end entity Top;
|
end entity Top;
|
||||||
|
|
||||||
@@ -42,10 +81,14 @@ architecture Behavioral of Top is
|
|||||||
|
|
||||||
constant PDM_DIV : integer := 16; -- 50 МГц / 16 = 3.125 МГц
|
constant PDM_DIV : integer := 16; -- 50 МГц / 16 = 3.125 МГц
|
||||||
constant CIC_R : integer := 50; -- 3.125 МГц / 50 = 62.5 кГц
|
constant CIC_R : integer := 50; -- 3.125 МГц / 50 = 62.5 кГц
|
||||||
constant CIC_N : integer := 4;
|
constant CIC_N : integer := 3;
|
||||||
constant CIC_W : integer := 26;
|
constant CIC_W : integer := 18;
|
||||||
constant FIR_DEC : integer := 5; -- 62.5 кГц / 5 = 12.5 кГц
|
constant FIR_DEC : integer := 5; -- 62.5 кГц / 5 = 12.5 кГц
|
||||||
|
constant FS_OUT : integer := SYS_CLK_HZ / (PDM_DIV * CIC_R * FIR_DEC);
|
||||||
|
|
||||||
|
-- Сброса нет намеренно: состояние задают начальные значения регистров.
|
||||||
|
-- Пробовал сброс по включению от счётчика -- на железе тракт от него молча
|
||||||
|
-- умирает, кадры перестают идти. Проверено прошивкой в обе стороны.
|
||||||
signal rst_n : std_logic := '1';
|
signal rst_n : std_logic := '1';
|
||||||
|
|
||||||
signal pdm_bit : std_logic;
|
signal pdm_bit : std_logic;
|
||||||
@@ -67,14 +110,105 @@ architecture Behavioral of Top is
|
|||||||
signal uart_data : std_logic_vector(7 downto 0);
|
signal uart_data : std_logic_vector(7 downto 0);
|
||||||
signal uart_busy : std_logic;
|
signal uart_busy : std_logic;
|
||||||
|
|
||||||
|
signal led_event : std_logic;
|
||||||
|
signal led_chain : std_logic;
|
||||||
|
signal led_norm : std_logic;
|
||||||
|
|
||||||
|
signal dac_gain : unsigned(3 downto 0);
|
||||||
|
signal gain_show : std_logic;
|
||||||
|
signal gain_led : std_logic;
|
||||||
|
|
||||||
|
signal beat_cnt : integer range 0 to SYS_CLK_HZ / 2 - 1 := 0;
|
||||||
|
signal beacon : std_logic := '0';
|
||||||
|
|
||||||
|
component Gowin_PLL
|
||||||
|
port (
|
||||||
|
clkin: in std_logic;
|
||||||
|
clkout0: out std_logic;
|
||||||
|
mdclk: in std_logic
|
||||||
|
);
|
||||||
|
end component;
|
||||||
|
|
||||||
|
-- Verilog, взят из проекта DAC_PCM5102A: I2S и SCK оттуда без изменений
|
||||||
|
component dac_stream
|
||||||
|
generic (
|
||||||
|
CLK_FREQ_HZ : integer;
|
||||||
|
IN_RATE_HZ : integer;
|
||||||
|
SAMPLE_RATE_HZ : integer;
|
||||||
|
GAIN_MAX : integer
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
clk : in std_logic;
|
||||||
|
din : in std_logic_vector(23 downto 0);
|
||||||
|
din_valid : in std_logic;
|
||||||
|
gain : in std_logic_vector(3 downto 0);
|
||||||
|
i2s_bck : out std_logic;
|
||||||
|
i2s_lrck : out std_logic;
|
||||||
|
i2s_din : out std_logic;
|
||||||
|
dac_sck : out std_logic
|
||||||
|
);
|
||||||
|
end component;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
|
||||||
rst_n <= '1';
|
rst_n <= '1';
|
||||||
|
|
||||||
|
led_event <= pdm_valid when LED_SRC = 2 else
|
||||||
|
cic_valid when LED_SRC = 1 else
|
||||||
|
fir_valid;
|
||||||
|
|
||||||
|
-- Маяк собирается только когда выбран: его счётчик широкий и тикает каждый
|
||||||
|
-- такт, а такие в этом кристалле надёжно работать не обязаны
|
||||||
|
beacon_gen : if LED_SRC = 3 generate
|
||||||
|
led_norm <= beacon;
|
||||||
|
|
||||||
|
beacon_proc : process (clk)
|
||||||
|
begin
|
||||||
|
if rising_edge(clk) then
|
||||||
|
if beat_cnt = SYS_CLK_HZ / 2 - 1 then
|
||||||
|
beat_cnt <= 0;
|
||||||
|
beacon <= not beacon;
|
||||||
|
else
|
||||||
|
beat_cnt <= beat_cnt + 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process beacon_proc;
|
||||||
|
end generate beacon_gen;
|
||||||
|
|
||||||
|
chain_gen : if LED_SRC /= 3 generate
|
||||||
|
led_norm <= led_chain;
|
||||||
|
end generate chain_gen;
|
||||||
|
|
||||||
|
-- После нажатия кнопки светодиод на несколько секунд показывает усиление
|
||||||
|
LED <= gain_led when gain_show = '1' else led_norm;
|
||||||
|
|
||||||
|
gain_button_inst : entity work.gain_button(rtl)
|
||||||
|
generic map(
|
||||||
|
SYS_CLK_HZ => SYS_CLK_HZ,
|
||||||
|
GAIN_MAX => DAC_GAIN_MAX,
|
||||||
|
BTN_ACTIVE => '1' -- кнопка S1 платы замыкает вывод на +3.3 В
|
||||||
|
)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
clk => clk,
|
||||||
|
btn => BTN_GAIN,
|
||||||
|
gain => dac_gain,
|
||||||
|
show => gain_show,
|
||||||
|
led => gain_led
|
||||||
|
);
|
||||||
|
|
||||||
|
PLL_dbg: Gowin_PLL
|
||||||
|
port map (
|
||||||
|
clkin => clk,
|
||||||
|
clkout0 => clk_80MHz,
|
||||||
|
mdclk => clk
|
||||||
|
);
|
||||||
|
|
||||||
pdm_rx_inst : entity work.pdm_rx(rtl)
|
pdm_rx_inst : entity work.pdm_rx(rtl)
|
||||||
generic map(
|
generic map(
|
||||||
CLK_DIV => PDM_DIV,
|
CLK_DIV => PDM_DIV,
|
||||||
SEL_HIGH => '0' -- микрофон отдаёт бит по спаду MIC_CLK
|
SEL_HIGH => '0', -- микрофон отдаёт бит по спаду MIC_CLK
|
||||||
|
SMP_CNT => MIC_SMP_CNT
|
||||||
)
|
)
|
||||||
port map
|
port map
|
||||||
(
|
(
|
||||||
@@ -82,7 +216,7 @@ begin
|
|||||||
rst_n => rst_n,
|
rst_n => rst_n,
|
||||||
mic_dat => MIC_DAT,
|
mic_dat => MIC_DAT,
|
||||||
mic_clk => MIC_CLK,
|
mic_clk => MIC_CLK,
|
||||||
mic_sel => MIC_SEL,
|
mic_sel => open,
|
||||||
bit_out => pdm_bit,
|
bit_out => pdm_bit,
|
||||||
bit_valid => pdm_valid
|
bit_valid => pdm_valid
|
||||||
);
|
);
|
||||||
@@ -109,7 +243,7 @@ begin
|
|||||||
DOUT_W => 24,
|
DOUT_W => 24,
|
||||||
DECIM => FIR_DEC,
|
DECIM => FIR_DEC,
|
||||||
BUF_AW => 8,
|
BUF_AW => 8,
|
||||||
ACC_W => 48
|
ACC_W => 40
|
||||||
)
|
)
|
||||||
port map
|
port map
|
||||||
(
|
(
|
||||||
@@ -184,6 +318,27 @@ begin
|
|||||||
tx => UART_TX
|
tx => UART_TX
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Вторая ветвь: те же отсчёты ФНЧ, мимо FIFO и кадров, на ЦАП
|
||||||
|
dac_stream_inst : dac_stream
|
||||||
|
generic map(
|
||||||
|
CLK_FREQ_HZ => SYS_CLK_HZ,
|
||||||
|
IN_RATE_HZ => FS_OUT,
|
||||||
|
SAMPLE_RATE_HZ => 48000, -- номинал делителя I2S, выйдет 48 828.125 Гц
|
||||||
|
GAIN_MAX => DAC_GAIN_MAX
|
||||||
|
)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
clk => clk,
|
||||||
|
din => std_logic_vector(fir_data),
|
||||||
|
din_valid => fir_valid,
|
||||||
|
gain => std_logic_vector(dac_gain),
|
||||||
|
i2s_bck => I2S_BCK,
|
||||||
|
i2s_lrck => I2S_LRCK,
|
||||||
|
i2s_din => I2S_DIN,
|
||||||
|
dac_sck => DAC_SCK
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
led_status_inst : entity work.led_status(rtl)
|
led_status_inst : entity work.led_status(rtl)
|
||||||
generic map(
|
generic map(
|
||||||
SYS_CLK_HZ => SYS_CLK_HZ,
|
SYS_CLK_HZ => SYS_CLK_HZ,
|
||||||
@@ -194,8 +349,8 @@ begin
|
|||||||
port map
|
port map
|
||||||
(
|
(
|
||||||
clk => clk,
|
clk => clk,
|
||||||
data_event => fir_valid,
|
data_event => led_event,
|
||||||
led => LED
|
led => led_chain
|
||||||
);
|
);
|
||||||
|
|
||||||
end architecture Behavioral;
|
end architecture Behavioral;
|
||||||
|
|||||||
+6
-4
@@ -11,10 +11,12 @@
|
|||||||
-- частоте PDM, гребёнки -- на пониженной. Умножителей не требует, нули АЧХ
|
-- частоте PDM, гребёнки -- на пониженной. Умножителей не требует, нули АЧХ
|
||||||
-- стоят ровно на частотах, которые складываются в полосу при децимации.
|
-- стоят ровно на частотах, которые складываются в полосу при децимации.
|
||||||
--
|
--
|
||||||
-- Разрядность: ORDER*log2(DECIM) + 1 бит на знак. Для 4 и 50 это 24 бита,
|
-- Разрядность: ORDER*log2(DECIM) + 1 бит на знак. Для 3 и 50 это 18 бит.
|
||||||
-- ACC_W взят с запасом. Переполнение внутри тракта штатно: результат верен
|
-- Шире 18 бит на этом кристалле нельзя: аккумуляторы от 20 бит и выше
|
||||||
-- за счёт дополнительного кода, пока ACC_W не меньше требуемого.
|
-- Gowin укладывает неверно, тракт молча отдаёт нули (проверено на железе).
|
||||||
-- Постоянному '1' на входе отвечает DECIM^ORDER = 6 250 000.
|
-- Переполнение внутри тракта штатно: результат верен за счёт дополнительного
|
||||||
|
-- кода, пока ACC_W не меньше требуемого.
|
||||||
|
-- Постоянному '1' на входе отвечает DECIM^ORDER = 125 000.
|
||||||
--
|
--
|
||||||
-- Каждая ступень защёлкнута отдельно -- это только задержка на ORDER
|
-- Каждая ступень защёлкнута отдельно -- это только задержка на ORDER
|
||||||
-- отсчётов, зато нет длинной цепи сумматоров в одном такте.
|
-- отсчётов, зато нет длинной цепи сумматоров в одном такте.
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
`timescale 1ns / 1ps
|
||||||
|
// =============================================================================
|
||||||
|
// Module: dac_stream
|
||||||
|
// Project Name: DigMic_prj
|
||||||
|
// Description:
|
||||||
|
// Отсчёты микрофона -> ЦАП PCM5102A по I2S.
|
||||||
|
//
|
||||||
|
// I2S и SCK -- ровно как в проверенном на железе dac_top проекта
|
||||||
|
// DAC_PCM5102A: fS = 50 МГц / 1024 = 48 828.125 Гц, BCK = 64 fS,
|
||||||
|
// SCK = 12.5 МГц = 256 fS, тот же i2s_transmitter без изменений.
|
||||||
|
//
|
||||||
|
// Частота отсчётов микрофона (12 500 Гц) и частота ЦАП делятся из одного
|
||||||
|
// clk, поэтому их отношение постоянно и точно: 4000 / 1024 тактов. Между
|
||||||
|
// ними нужен только пересчёт, без FIFO и подстройки по заполнению.
|
||||||
|
//
|
||||||
|
// Пересчёт -- линейная интерполяция между двумя последними входными
|
||||||
|
// отсчётами. Доля интервала берётся из числа тактов с прихода последнего
|
||||||
|
// отсчёта, так что каждый выходной отсчёт взят в своей точке времени.
|
||||||
|
// Простое удержание дало бы ступени по 3-4 повтора с дрожанием на период
|
||||||
|
// ЦАП и зеркала 12.5 кГц +- f в полосе ЦАП: для тона 1 кГц это -21 дБ,
|
||||||
|
// интерполяция опускает их до -42 дБ. Цена -- задержка на один входной
|
||||||
|
// отсчёт, 80 мкс.
|
||||||
|
//
|
||||||
|
// gain поднимает уровень только для ЦАП, на UART уходят исходные отсчёты.
|
||||||
|
// Полная шкала 24 бит -- это 2.1 В скз на выходе PCM5102A, а обычный
|
||||||
|
// уровень с микрофона -60...-70 dBFS, то есть единицы милливольт. Сдвиг на
|
||||||
|
// gain даёт ровно +6.02 * gain дБ с ограничением по шкале. Новое значение
|
||||||
|
// действует с очередного входного отсчёта; больше GAIN_MAX не бывает.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
module dac_stream #(
|
||||||
|
parameter integer CLK_FREQ_HZ = 50000000,
|
||||||
|
parameter integer IN_RATE_HZ = 12500, // частота входных отсчётов
|
||||||
|
parameter integer SAMPLE_RATE_HZ = 48000, // номинал для делителя I2S, выйдет 48 828.125
|
||||||
|
parameter integer GAIN_MAX = 8 // наибольший сдвиг, не больше 15
|
||||||
|
)(
|
||||||
|
input wire clk,
|
||||||
|
|
||||||
|
input wire [23:0] din, // отсчёт микрофона, дополнительный код
|
||||||
|
input wire din_valid, // строб отсчёта, раз в IN_PERIOD тактов
|
||||||
|
input wire [3:0] gain, // усиление для ЦАП: +6 дБ на единицу
|
||||||
|
|
||||||
|
output wire i2s_bck,
|
||||||
|
output wire i2s_lrck,
|
||||||
|
output wire i2s_din,
|
||||||
|
output wire dac_sck
|
||||||
|
);
|
||||||
|
|
||||||
|
localparam integer IN_PERIOD = CLK_FREQ_HZ / IN_RATE_HZ; // 4000 тактов
|
||||||
|
localparam integer CNT_W = $clog2(IN_PERIOD);
|
||||||
|
|
||||||
|
// Доля интервала cnt / IN_PERIOD в Q16: умножение на обратную величину
|
||||||
|
// вместо деления. Ошибка доли меньше 3e-5, это -100 dBFS на крутом склоне.
|
||||||
|
localparam integer FRAC_W = 16;
|
||||||
|
localparam integer RECIP_SH = 10;
|
||||||
|
localparam integer RECIP = (1 << (FRAC_W + RECIP_SH)) / IN_PERIOD;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. Усиление с ограничением по шкале
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
localparam integer GW = 24 + GAIN_MAX;
|
||||||
|
localparam [3:0] GMAX4 = GAIN_MAX;
|
||||||
|
|
||||||
|
wire [3:0] g = (gain > GMAX4) ? GMAX4 : gain;
|
||||||
|
wire signed [GW-1:0] din_wide = $signed(din) <<< g;
|
||||||
|
wire [GAIN_MAX:0] din_top = din_wide[GW-1:23];
|
||||||
|
wire din_ovf = ~(&din_top) & (|din_top);
|
||||||
|
|
||||||
|
wire signed [23:0] din_gain = !din_ovf ? din_wide[23:0] :
|
||||||
|
din_wide[GW-1] ? 24'sh800000 :
|
||||||
|
24'sh7FFFFF;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. Два последних отсчёта и время с прихода последнего
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Если отсчёты перестанут приходить, счётчик встанет на конце интервала
|
||||||
|
// и выход останется на последнем отсчёте.
|
||||||
|
reg signed [23:0] x_cur = 24'sd0;
|
||||||
|
reg signed [23:0] x_prev = 24'sd0;
|
||||||
|
reg [CNT_W-1:0] cnt = {CNT_W{1'b0}};
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (din_valid) begin
|
||||||
|
x_prev <= x_cur;
|
||||||
|
x_cur <= din_gain;
|
||||||
|
cnt <= {CNT_W{1'b0}};
|
||||||
|
end else if (cnt != IN_PERIOD - 1) begin
|
||||||
|
cnt <= cnt + 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 3. Интерполяция: x_prev + (x_cur - x_prev) * доля
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Конвейер считает каждый такт, все ступени берут состояние одного такта,
|
||||||
|
// так что смена отсчёта посреди конвейера ничего не смешивает. Результат
|
||||||
|
// всегда лежит между x_prev и x_cur, переполнения нет.
|
||||||
|
reg signed [24:0] d1 = 25'sd0;
|
||||||
|
reg [FRAC_W-1:0] f1 = {FRAC_W{1'b0}};
|
||||||
|
reg signed [23:0] b1 = 24'sd0;
|
||||||
|
|
||||||
|
reg signed [FRAC_W+25:0] p2 = {(FRAC_W+26){1'b0}};
|
||||||
|
reg signed [23:0] b2 = 24'sd0;
|
||||||
|
|
||||||
|
reg signed [23:0] y3 = 24'sd0;
|
||||||
|
|
||||||
|
wire [CNT_W+RECIP_SH+FRAC_W-1:0] frac_full = cnt * RECIP;
|
||||||
|
|
||||||
|
wire signed [FRAC_W+25:0] p2_rnd = p2 + (1 << (FRAC_W - 1));
|
||||||
|
wire signed [25:0] p2_sh = p2_rnd[FRAC_W+25:FRAC_W];
|
||||||
|
wire signed [25:0] y_sum = b2 + p2_sh;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
d1 <= x_cur - x_prev;
|
||||||
|
f1 <= frac_full[RECIP_SH +: FRAC_W];
|
||||||
|
b1 <= x_prev;
|
||||||
|
|
||||||
|
p2 <= d1 * $signed({1'b0, f1});
|
||||||
|
b2 <= b1;
|
||||||
|
|
||||||
|
y3 <= y_sum[23:0];
|
||||||
|
end
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 4. I2S: один и тот же отсчёт в оба канала
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// На BNC платы ЦАП выведен только OUTL, но отсчёт идёт в оба канала, как в
|
||||||
|
// dac_top. Защёлка по sample_tick (за 16 тактов до начала кадра) нужна,
|
||||||
|
// чтобы правый канал получил тот же отсчёт, а не взятый на полкадра позже.
|
||||||
|
wire sample_tick;
|
||||||
|
reg signed [23:0] dac_sample = 24'sd0;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (sample_tick)
|
||||||
|
dac_sample <= y3;
|
||||||
|
end
|
||||||
|
|
||||||
|
i2s_transmitter #(
|
||||||
|
.CLK_FREQ_HZ (CLK_FREQ_HZ),
|
||||||
|
.SAMPLE_RATE_HZ (SAMPLE_RATE_HZ),
|
||||||
|
.DATA_WIDTH (24)
|
||||||
|
) u_i2s_transmitter (
|
||||||
|
.clk (clk),
|
||||||
|
.rst_n (1'b1),
|
||||||
|
.data_left (dac_sample),
|
||||||
|
.data_right (dac_sample),
|
||||||
|
.bck (i2s_bck),
|
||||||
|
.lrck (i2s_lrck),
|
||||||
|
.din (i2s_din),
|
||||||
|
.sample_tick (sample_tick),
|
||||||
|
.dbg_bit_cnt (),
|
||||||
|
.dbg_bck_fall ()
|
||||||
|
);
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 5. Системный клок ЦАП, как в dac_top
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// SCK = 256 fS из того же clk, поэтому SCK, BCK и LRCK синхронны по
|
||||||
|
// построению. Без SCK ЦАП перешёл бы на внутренний PLL от BCK, но пока
|
||||||
|
// вывод ПЛИС не сконфигурирован, это состояние неоднозначно.
|
||||||
|
localparam integer BCK_CLK_DIV = CLK_FREQ_HZ / (SAMPLE_RATE_HZ * 64 * 2);
|
||||||
|
localparam integer SCK_HALF = (BCK_CLK_DIV / 4) > 0 ? (BCK_CLK_DIV / 4) : 1;
|
||||||
|
localparam integer SCK_CNT_W = (SCK_HALF > 1) ? $clog2(SCK_HALF) : 1;
|
||||||
|
|
||||||
|
reg [SCK_CNT_W-1:0] sck_cnt = {SCK_CNT_W{1'b0}};
|
||||||
|
reg sck_reg = 1'b0;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (sck_cnt >= SCK_HALF - 1) begin
|
||||||
|
sck_cnt <= {SCK_CNT_W{1'b0}};
|
||||||
|
sck_reg <= ~sck_reg;
|
||||||
|
end else begin
|
||||||
|
sck_cnt <= sck_cnt + 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assign dac_sck = sck_reg;
|
||||||
|
|
||||||
|
endmodule
|
||||||
+61
-61
@@ -9,8 +9,8 @@
|
|||||||
-- вход 62500 Гц -> выход 12500 Гц, отводов 221, симметричный
|
-- вход 62500 Гц -> выход 12500 Гц, отводов 221, симметричный
|
||||||
-- полоса 0..5000 Гц, задерживание от 6250 Гц
|
-- полоса 0..5000 Гц, задерживание от 6250 Гц
|
||||||
-- неравномерность полосы с учётом CIC: 0.032 дБ
|
-- неравномерность полосы с учётом CIC: 0.032 дБ
|
||||||
-- подавление в полосе задерживания: -72.1 дБ
|
-- подавление в полосе задерживания: -76.3 дБ
|
||||||
-- худшее подавление сворачиваемых полос: -77.5 дБ
|
-- худшее подавление сворачиваемых полос: -92.1 дБ
|
||||||
----------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------
|
||||||
library ieee;
|
library ieee;
|
||||||
use ieee.std_logic_1164.all;
|
use ieee.std_logic_1164.all;
|
||||||
@@ -20,72 +20,72 @@ package fir_coefs_pkg is
|
|||||||
|
|
||||||
constant FIR_NTAPS : integer := 221;
|
constant FIR_NTAPS : integer := 221;
|
||||||
constant FIR_COEF_W : integer := 18;
|
constant FIR_COEF_W : integer := 18;
|
||||||
constant FIR_SHIFT : integer := 17;
|
constant FIR_SHIFT : integer := 13;
|
||||||
|
|
||||||
-- Отклик тракта на постоянную '1' на выводе данных микрофона:
|
-- Отклик тракта на постоянную '1' на выводе данных микрофона:
|
||||||
-- сумма коэффициентов x усиление CIC, округлённая как в fir_decim.
|
-- сумма коэффициентов x усиление CIC, округлённая как в fir_decim.
|
||||||
constant FIR_SUM : integer := 175921;
|
constant FIR_SUM : integer := 549755;
|
||||||
constant FIR_DC_CODE : integer := 8388567;
|
constant FIR_DC_CODE : integer := 8388596;
|
||||||
|
|
||||||
type fir_coefs_t is array (0 to FIR_NTAPS - 1) of signed(FIR_COEF_W - 1 downto 0);
|
type fir_coefs_t is array (0 to FIR_NTAPS - 1) of signed(FIR_COEF_W - 1 downto 0);
|
||||||
|
|
||||||
constant FIR_COEFS : fir_coefs_t := (
|
constant FIR_COEFS : fir_coefs_t := (
|
||||||
"000000000000000001", "000000000000000010", "000000000000000100", "000000000000000111", -- 0
|
"000000000000000011", "000000000000000111", "000000000000001110", "000000000000010100", -- 0
|
||||||
"000000000000001000", "000000000000001000", "000000000000000110", "000000000000000001", -- 4
|
"000000000000011001", "000000000000011001", "000000000000010010", "000000000000000011", -- 4
|
||||||
"111111111111111010", "111111111111110100", "111111111111101110", "111111111111101101", -- 8
|
"111111111111101111", "111111111111011010", "111111111111001011", "111111111111000111", -- 8
|
||||||
"111111111111110010", "111111111111111011", "000000000000001000", "000000000000010110", -- 12
|
"111111111111010100", "111111111111110010", "000000000000011011", "000000000001000011", -- 12
|
||||||
"000000000000011111", "000000000000100000", "000000000000010111", "000000000000000101", -- 16
|
"000000000001100000", "000000000001100011", "000000000001000111", "000000000000001110", -- 16
|
||||||
"111111111111101101", "111111111111010111", "111111111111001010", "111111111111001100", -- 20
|
"111111111111000110", "111111111110000011", "111111111101011011", "111111111101100001", -- 20
|
||||||
"111111111111011111", "000000000000000000", "000000000000100110", "000000000001000111", -- 24
|
"111111111110011011", "000000000000000000", "000000000001110111", "000000000011011011", -- 24
|
||||||
"000000000001010110", "000000000001001100", "000000000000101000", "111111111111110001", -- 28
|
"000000000100001001", "000000000011101001", "000000000001111010", "111111111111010010", -- 28
|
||||||
"111111111110110111", "111111111110001100", "111111111101111111", "111111111110011010", -- 32
|
"111111111100011111", "111111111010011001", "111111111001110011", "111111111011000111", -- 32
|
||||||
"111111111111011010", "000000000000101111", "000000000010000000", "000000000010110011", -- 36
|
"111111111110001100", "000000000010010001", "000000000110001011", "000000001000101001", -- 36
|
||||||
"000000000010110100", "000000000001111011", "000000000000010100", "111111111110011001", -- 40
|
"000000001000101011", "000000000101111011", "000000000000111100", "111111111011000000", -- 40
|
||||||
"111111111100101111", "111111111011111011", "111111111100010100", "111111111101111100", -- 44
|
"111111110101111011", "111111110011011011", "111111110100101001", "111111111001101001", -- 44
|
||||||
"000000000000011010", "000000000011000010", "000000000101000001", "000000000101100111", -- 48
|
"000000000001010001", "000000001001011001", "000000001111011110", "000000010001010011", -- 48
|
||||||
"000000000100011111", "000000000001110100", "111111111110010000", "111111111010110111", -- 52
|
"000000001101110101", "000000000101100100", "111111111010100110", "111111110000001000", -- 52
|
||||||
"111111111000101111", "111111111000101100", "111111111010111110", "111111111111000110", -- 56
|
"111111101001100110", "111111101001011110", "111111110000100001", "111111111101010000", -- 56
|
||||||
"000000000011111100", "000000001000000011", "000000001010000001", "000000001001000000", -- 60
|
"000000001100001101", "000000011000110110", "000000011110111001", "000000011011110000", -- 60
|
||||||
"000000000101000001", "111111111111000010", "111111111000101111", "111111110100001000", -- 64
|
"000000001111011100", "111111111100111101", "111111101001100100", "111111011011010101", -- 64
|
||||||
"111111110010110100", "111111110101100100", "111111111011111011", "000000000100001101", -- 68
|
"111111010111010100", "111111011111110110", "111111110011011110", "000000001101000100", -- 68
|
||||||
"000000001100000000", "000000010000110000", "000000010000101010", "000000001011010000", -- 72
|
"000000100101000100", "000000110011101101", "000000110011010111", "000000100010101000", -- 72
|
||||||
"000000000001101101", "111111110110101001", "111111101101011100", "111111101001001100", -- 76
|
"000000000101001011", "111111100011000011", "111111000110101010", "111110111001100111", -- 76
|
||||||
"111111101011110001", "111111110101000101", "000000000010111000", "000000010001001111", -- 80
|
"111111000001101010", "111111011110011101", "000000001001000010", "000000110101010100", -- 80
|
||||||
"000000011011101011", "000000011110011001", "000000010111101101", "000000001000101000", -- 84
|
"000001010101011011", "000001011101101111", "000001001000111110", "000000011010011000", -- 84
|
||||||
"111111110100111111", "111111100010100000", "111111010111001101", "111111010111100110", -- 88
|
"111111011101110010", "111110100100110011", "111110000010000100", "111110000011011110", -- 88
|
||||||
"111111100101001000", "111111111101001111", "000000011001110001", "000000110010010000", -- 92
|
"111110101101011000", "111111110111111011", "000001001111111101", "000010011011011000", -- 92
|
||||||
"000000111110001111", "000000110111110101", "000000011101110100", "111111110100110100", -- 96
|
"000011000000000100", "000010101011111000", "000001011011001110", "111111011100011010", -- 96
|
||||||
"111111000110110101", "111110100001011001", "111110010010011110", "111110100100111011", -- 100
|
"111101001110011111", "111011011011010101", "111010101110000110", "111011101000011010", -- 100
|
||||||
"111111011101011111", "000000111000110011", "000010101011011110", "000100100011111001", -- 104
|
"111110011000101100", "000010110100100011", "001000011000100101", "001110001110010000", -- 104
|
||||||
"000110001101101111", "000111010110001001", "000111110000000101", "000111010110001001", -- 108
|
"010011010110100000", "010110110111000011", "011000000110110111", "010110110111000011", -- 108
|
||||||
"000110001101101111", "000100100011111001", "000010101011011110", "000000111000110011", -- 112
|
"010011010110100000", "001110001110010000", "001000011000100101", "000010110100100011", -- 112
|
||||||
"111111011101011111", "111110100100111011", "111110010010011110", "111110100001011001", -- 116
|
"111110011000101100", "111011101000011010", "111010101110000110", "111011011011010101", -- 116
|
||||||
"111111000110110101", "111111110100110100", "000000011101110100", "000000110111110101", -- 120
|
"111101001110011111", "111111011100011010", "000001011011001110", "000010101011111000", -- 120
|
||||||
"000000111110001111", "000000110010010000", "000000011001110001", "111111111101001111", -- 124
|
"000011000000000100", "000010011011011000", "000001001111111101", "111111110111111011", -- 124
|
||||||
"111111100101001000", "111111010111100110", "111111010111001101", "111111100010100000", -- 128
|
"111110101101011000", "111110000011011110", "111110000010000100", "111110100100110011", -- 128
|
||||||
"111111110100111111", "000000001000101000", "000000010111101101", "000000011110011001", -- 132
|
"111111011101110010", "000000011010011000", "000001001000111110", "000001011101101111", -- 132
|
||||||
"000000011011101011", "000000010001001111", "000000000010111000", "111111110101000101", -- 136
|
"000001010101011011", "000000110101010100", "000000001001000010", "111111011110011101", -- 136
|
||||||
"111111101011110001", "111111101001001100", "111111101101011100", "111111110110101001", -- 140
|
"111111000001101010", "111110111001100111", "111111000110101010", "111111100011000011", -- 140
|
||||||
"000000000001101101", "000000001011010000", "000000010000101010", "000000010000110000", -- 144
|
"000000000101001011", "000000100010101000", "000000110011010111", "000000110011101101", -- 144
|
||||||
"000000001100000000", "000000000100001101", "111111111011111011", "111111110101100100", -- 148
|
"000000100101000100", "000000001101000100", "111111110011011110", "111111011111110110", -- 148
|
||||||
"111111110010110100", "111111110100001000", "111111111000101111", "111111111111000010", -- 152
|
"111111010111010100", "111111011011010101", "111111101001100100", "111111111100111101", -- 152
|
||||||
"000000000101000001", "000000001001000000", "000000001010000001", "000000001000000011", -- 156
|
"000000001111011100", "000000011011110000", "000000011110111001", "000000011000110110", -- 156
|
||||||
"000000000011111100", "111111111111000110", "111111111010111110", "111111111000101100", -- 160
|
"000000001100001101", "111111111101010000", "111111110000100001", "111111101001011110", -- 160
|
||||||
"111111111000101111", "111111111010110111", "111111111110010000", "000000000001110100", -- 164
|
"111111101001100110", "111111110000001000", "111111111010100110", "000000000101100100", -- 164
|
||||||
"000000000100011111", "000000000101100111", "000000000101000001", "000000000011000010", -- 168
|
"000000001101110101", "000000010001010011", "000000001111011110", "000000001001011001", -- 168
|
||||||
"000000000000011010", "111111111101111100", "111111111100010100", "111111111011111011", -- 172
|
"000000000001010001", "111111111001101001", "111111110100101001", "111111110011011011", -- 172
|
||||||
"111111111100101111", "111111111110011001", "000000000000010100", "000000000001111011", -- 176
|
"111111110101111011", "111111111011000000", "000000000000111100", "000000000101111011", -- 176
|
||||||
"000000000010110100", "000000000010110011", "000000000010000000", "000000000000101111", -- 180
|
"000000001000101011", "000000001000101001", "000000000110001011", "000000000010010001", -- 180
|
||||||
"111111111111011010", "111111111110011010", "111111111101111111", "111111111110001100", -- 184
|
"111111111110001100", "111111111011000111", "111111111001110011", "111111111010011001", -- 184
|
||||||
"111111111110110111", "111111111111110001", "000000000000101000", "000000000001001100", -- 188
|
"111111111100011111", "111111111111010010", "000000000001111010", "000000000011101001", -- 188
|
||||||
"000000000001010110", "000000000001000111", "000000000000100110", "000000000000000000", -- 192
|
"000000000100001001", "000000000011011011", "000000000001110111", "000000000000000000", -- 192
|
||||||
"111111111111011111", "111111111111001100", "111111111111001010", "111111111111010111", -- 196
|
"111111111110011011", "111111111101100001", "111111111101011011", "111111111110000011", -- 196
|
||||||
"111111111111101101", "000000000000000101", "000000000000010111", "000000000000100000", -- 200
|
"111111111111000110", "000000000000001110", "000000000001000111", "000000000001100011", -- 200
|
||||||
"000000000000011111", "000000000000010110", "000000000000001000", "111111111111111011", -- 204
|
"000000000001100000", "000000000001000011", "000000000000011011", "111111111111110010", -- 204
|
||||||
"111111111111110010", "111111111111101101", "111111111111101110", "111111111111110100", -- 208
|
"111111111111010100", "111111111111000111", "111111111111001011", "111111111111011010", -- 208
|
||||||
"111111111111111010", "000000000000000001", "000000000000000110", "000000000000001000", -- 212
|
"111111111111101111", "000000000000000011", "000000000000010010", "000000000000011001", -- 212
|
||||||
"000000000000001000", "000000000000000111", "000000000000000100", "000000000000000010", -- 216
|
"000000000000011001", "000000000000010100", "000000000000001110", "000000000000000111", -- 216
|
||||||
"000000000000000001" -- 220
|
"000000000000000011" -- 220
|
||||||
);
|
);
|
||||||
|
|
||||||
end package fir_coefs_pkg;
|
end package fir_coefs_pkg;
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
----------------------------------------------------------------------------------
|
||||||
|
-- Company:
|
||||||
|
-- Engineer:
|
||||||
|
-- Create Date:
|
||||||
|
-- Design Name:
|
||||||
|
-- Module Name: gain_button - rtl
|
||||||
|
-- Project Name: DigMic_prj
|
||||||
|
-- Target Devices: Gowin GW5A-25
|
||||||
|
-- Description:
|
||||||
|
-- Усиление ветви ЦАП с кнопки. Каждое нажатие -- следующая ступень, +6 дБ,
|
||||||
|
-- после GAIN_MAX снова 0. После включения -- 0, то есть та же шкала, что на
|
||||||
|
-- UART.
|
||||||
|
--
|
||||||
|
-- Выбранную ступень показывает светодиод: пауза, N коротких вспышек (для 0
|
||||||
|
-- -- одна длинная), пауза, и светодиод возвращается к обычной индикации.
|
||||||
|
-- Нажатие во время показа начинает показ заново, уже с новым значением.
|
||||||
|
--
|
||||||
|
-- Дребезг: новый уровень кнопки принимается, только если продержался
|
||||||
|
-- DEBOUNCE_MS подряд.
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
use ieee.numeric_std.all;
|
||||||
|
|
||||||
|
entity gain_button is
|
||||||
|
generic (
|
||||||
|
SYS_CLK_HZ : integer := 50_000_000;
|
||||||
|
GAIN_MAX : integer := 8; -- не больше 15
|
||||||
|
BTN_ACTIVE : std_logic := '1'; -- уровень на выводе при нажатой кнопке
|
||||||
|
DEBOUNCE_MS : integer := 20
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
clk : in std_logic;
|
||||||
|
btn : in std_logic;
|
||||||
|
|
||||||
|
gain : out unsigned(3 downto 0);
|
||||||
|
show : out std_logic; -- светодиод занят показом усиления
|
||||||
|
led : out std_logic
|
||||||
|
);
|
||||||
|
end entity gain_button;
|
||||||
|
|
||||||
|
architecture rtl of gain_button is
|
||||||
|
|
||||||
|
constant MS_DIV : integer := SYS_CLK_HZ / 1000;
|
||||||
|
|
||||||
|
-- Длительности показа, мс
|
||||||
|
constant T_GAP : integer := 600; -- пауза до и после вспышек
|
||||||
|
constant T_ON : integer := 150; -- короткая вспышка
|
||||||
|
constant T_OFF : integer := 350; -- между вспышками
|
||||||
|
constant T_ZERO : integer := 1000; -- длинная вспышка для 0
|
||||||
|
|
||||||
|
type state_t is (S_IDLE, S_GAP1, S_ON, S_OFF, S_GAP2);
|
||||||
|
|
||||||
|
signal state : state_t := S_IDLE;
|
||||||
|
|
||||||
|
signal div : integer range 0 to MS_DIV - 1 := 0;
|
||||||
|
signal tick : std_logic := '0'; -- раз в миллисекунду
|
||||||
|
|
||||||
|
signal sync : std_logic_vector(1 downto 0) := (others => not BTN_ACTIVE);
|
||||||
|
signal stable : std_logic := not BTN_ACTIVE;
|
||||||
|
signal db_cnt : integer range 0 to DEBOUNCE_MS := 0;
|
||||||
|
|
||||||
|
signal g : integer range 0 to GAIN_MAX := 0;
|
||||||
|
signal cnt : integer range 0 to GAIN_MAX := 0; -- сколько вспышек показать
|
||||||
|
signal left : integer range 0 to GAIN_MAX := 0; -- сколько ещё после текущей
|
||||||
|
signal t : integer range 0 to T_ZERO := 0; -- мс до конца состояния
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
gain <= to_unsigned(g, 4);
|
||||||
|
show <= '0' when state = S_IDLE else '1';
|
||||||
|
led <= '1' when state = S_ON else '0';
|
||||||
|
|
||||||
|
main_proc : process (clk)
|
||||||
|
variable press : boolean;
|
||||||
|
begin
|
||||||
|
if rising_edge(clk) then
|
||||||
|
sync <= sync(0) & btn;
|
||||||
|
tick <= '0';
|
||||||
|
press := false;
|
||||||
|
|
||||||
|
if div = MS_DIV - 1 then
|
||||||
|
div <= 0;
|
||||||
|
tick <= '1';
|
||||||
|
else
|
||||||
|
div <= div + 1;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Дребезг
|
||||||
|
if sync(1) = stable then
|
||||||
|
db_cnt <= 0;
|
||||||
|
elsif tick = '1' then
|
||||||
|
if db_cnt = DEBOUNCE_MS - 1 then
|
||||||
|
db_cnt <= 0;
|
||||||
|
stable <= sync(1);
|
||||||
|
press := sync(1) = BTN_ACTIVE;
|
||||||
|
else
|
||||||
|
db_cnt <= db_cnt + 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Показ
|
||||||
|
if tick = '1' and state /= S_IDLE then
|
||||||
|
if t /= 0 then
|
||||||
|
t <= t - 1;
|
||||||
|
else
|
||||||
|
case state is
|
||||||
|
when S_GAP1 =>
|
||||||
|
state <= S_ON;
|
||||||
|
if cnt = 0 then
|
||||||
|
t <= T_ZERO - 1;
|
||||||
|
left <= 0;
|
||||||
|
else
|
||||||
|
t <= T_ON - 1;
|
||||||
|
left <= cnt - 1;
|
||||||
|
end if;
|
||||||
|
when S_ON =>
|
||||||
|
if left = 0 then
|
||||||
|
state <= S_GAP2;
|
||||||
|
t <= T_GAP - 1;
|
||||||
|
else
|
||||||
|
state <= S_OFF;
|
||||||
|
t <= T_OFF - 1;
|
||||||
|
end if;
|
||||||
|
when S_OFF =>
|
||||||
|
state <= S_ON;
|
||||||
|
t <= T_ON - 1;
|
||||||
|
left <= left - 1;
|
||||||
|
when others =>
|
||||||
|
state <= S_IDLE;
|
||||||
|
end case;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Нажатие: следующая ступень и показ с начала
|
||||||
|
if press then
|
||||||
|
if g = GAIN_MAX then
|
||||||
|
g <= 0;
|
||||||
|
cnt <= 0;
|
||||||
|
else
|
||||||
|
g <= g + 1;
|
||||||
|
cnt <= g + 1;
|
||||||
|
end if;
|
||||||
|
state <= S_GAP1;
|
||||||
|
t <= T_GAP - 1;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end process main_proc;
|
||||||
|
|
||||||
|
end architecture rtl;
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
[General]
|
||||||
|
ipc_version=4
|
||||||
|
file=gowin_pll
|
||||||
|
module=Gowin_PLL
|
||||||
|
target_device=gw5a25b-003
|
||||||
|
type=clock_plladv
|
||||||
|
version=1.0
|
||||||
|
|
||||||
|
[Config]
|
||||||
|
AdvancedMode=false
|
||||||
|
ClkfbDivideFactorStatic=true
|
||||||
|
ClkfbDivideFactorStaticValue=1
|
||||||
|
ClkfbInternal=true
|
||||||
|
ClkfboutExpectedFrequency=400
|
||||||
|
ClkfboutTolerance=0.0
|
||||||
|
ClkfboutVCODivideFactorStatic=true
|
||||||
|
ClkfboutVCODivideFactorStaticValue=16
|
||||||
|
ClkfboutVCOFractionalDivideFactorStaticValue=0
|
||||||
|
ClkinClockFrequency=50
|
||||||
|
ClkinDividerFactorStatic=true
|
||||||
|
ClkinDividerFactorStaticValue=1
|
||||||
|
ClkinDividerReset=false
|
||||||
|
Clkou1BPhaseStatic=true
|
||||||
|
Clkou2BPhaseStatic=true
|
||||||
|
Clkou3BPhaseStatic=true
|
||||||
|
Clkout0Bypass=false
|
||||||
|
Clkout0DutyCycleDynamic=false
|
||||||
|
Clkout0DutyCycleDynamicValue=0
|
||||||
|
Clkout0DutyCycleStatic=true
|
||||||
|
Clkout0DutyTrimStatic=true
|
||||||
|
Clkout0DutyTrimStaticFalling=false
|
||||||
|
Clkout0DutyTrimStaticRising=true
|
||||||
|
Clkout0DutyTrimStaticStep=0
|
||||||
|
Clkout0ExpectedFrequency=80
|
||||||
|
Clkout0PhaseDynamic=false
|
||||||
|
Clkout0PhaseStatic=true
|
||||||
|
Clkout0PhaseStaticValue=0
|
||||||
|
Clkout0Tolerance=0.0
|
||||||
|
Clkout0VCODivideFactorStatic=true
|
||||||
|
Clkout0VCODivideFactorStaticValue=10
|
||||||
|
Clkout0VCOFractionalDivideFactorStaticValue=0
|
||||||
|
Clkout1Bypass=false
|
||||||
|
Clkout1DutyCycleDynamic=false
|
||||||
|
Clkout1DutyCycleDynamicValue=0
|
||||||
|
Clkout1DutyCycleStatic=true
|
||||||
|
Clkout1DutyTrimStatic=true
|
||||||
|
Clkout1DutyTrimStaticFalling=false
|
||||||
|
Clkout1DutyTrimStaticRising=true
|
||||||
|
Clkout1DutyTrimStaticStep=0
|
||||||
|
Clkout1ExpectedFrequency=400
|
||||||
|
Clkout1PhaseDynamic=false
|
||||||
|
Clkout1PhaseStaticValue=0
|
||||||
|
Clkout1Tolerance=0.0
|
||||||
|
Clkout1VCODivideFactorStatic=true
|
||||||
|
Clkout1VCODivideFactorStaticValue=8
|
||||||
|
Clkout2Bypass=false
|
||||||
|
Clkout2DutyCycleDynamic=false
|
||||||
|
Clkout2DutyCycleDynamicValue=0
|
||||||
|
Clkout2DutyCycleStatic=true
|
||||||
|
Clkout2DutyTrimStatic=true
|
||||||
|
Clkout2DutyTrimStaticFalling=false
|
||||||
|
Clkout2DutyTrimStaticRising=true
|
||||||
|
Clkout2DutyTrimStaticStep=0
|
||||||
|
Clkout2ExpectedFrequency=400
|
||||||
|
Clkout2PhaseDynamic=false
|
||||||
|
Clkout2PhaseStaticValue=0
|
||||||
|
Clkout2Tolerance=0.0
|
||||||
|
Clkout2VCODivideFactorStatic=true
|
||||||
|
Clkout2VCODivideFactorStaticValue=8
|
||||||
|
Clkout3Bypass=false
|
||||||
|
Clkout3DutyCycleDynamic=false
|
||||||
|
Clkout3DutyCycleDynamicValue=0
|
||||||
|
Clkout3DutyCycleStatic=true
|
||||||
|
Clkout3DutyTrimStatic=true
|
||||||
|
Clkout3DutyTrimStaticFalling=false
|
||||||
|
Clkout3DutyTrimStaticRising=true
|
||||||
|
Clkout3DutyTrimStaticStep=0
|
||||||
|
Clkout3ExpectedFrequency=400
|
||||||
|
Clkout3PhaseDynamic=false
|
||||||
|
Clkout3PhaseStaticValue=0
|
||||||
|
Clkout3Tolerance=0.0
|
||||||
|
Clkout3VCODivideFactorStatic=true
|
||||||
|
Clkout3VCODivideFactorStaticValue=8
|
||||||
|
Clkout4Bypass=false
|
||||||
|
Clkout4DutyCycleDynamic=false
|
||||||
|
Clkout4DutyCycleDynamicValue=0
|
||||||
|
Clkout4DutyCycleStatic=true
|
||||||
|
Clkout4ExpectedFrequency=400
|
||||||
|
Clkout4PhaseDynamic=false
|
||||||
|
Clkout4PhaseStatic=true
|
||||||
|
Clkout4PhaseStaticValue=0
|
||||||
|
Clkout4Tolerance=0.0
|
||||||
|
Clkout4VCODivideFactorStatic=true
|
||||||
|
Clkout4VCODivideFactorStaticValue=8
|
||||||
|
Clkout5Bypass=false
|
||||||
|
Clkout5DutyCycleDynamic=false
|
||||||
|
Clkout5DutyCycleDynamicValue=0
|
||||||
|
Clkout5DutyCycleStatic=true
|
||||||
|
Clkout5ExpectedFrequency=400
|
||||||
|
Clkout5PhaseDynamic=false
|
||||||
|
Clkout5PhaseStatic=true
|
||||||
|
Clkout5PhaseStaticValue=0
|
||||||
|
Clkout5Tolerance=0.0
|
||||||
|
Clkout5VCODivideFactorStatic=true
|
||||||
|
Clkout5VCODivideFactorStaticValue=8
|
||||||
|
Clkout6Bypass=false
|
||||||
|
Clkout6DutyCycleDynamic=false
|
||||||
|
Clkout6DutyCycleDynamicValue=0
|
||||||
|
Clkout6DutyCycleStatic=true
|
||||||
|
Clkout6ExpectedFrequency=400
|
||||||
|
Clkout6PhaseDynamic=false
|
||||||
|
Clkout6PhaseStatic=true
|
||||||
|
Clkout6PhaseStaticValue=0
|
||||||
|
Clkout6Tolerance=0.0
|
||||||
|
Clkout6VCODivideFactorStatic=true
|
||||||
|
Clkout6VCODivideFactorStaticValue=8
|
||||||
|
ClkoutDividerReset=false
|
||||||
|
EnableCascade=false
|
||||||
|
EnableClkfbout=false
|
||||||
|
EnableClkout0Divider=false
|
||||||
|
EnableClkout1=false
|
||||||
|
EnableClkout1Divider=false
|
||||||
|
EnableClkout2=false
|
||||||
|
EnableClkout2Divider=false
|
||||||
|
EnableClkout3=false
|
||||||
|
EnableClkout3Divider=false
|
||||||
|
EnableClkout4=false
|
||||||
|
EnableClkout4Divider=false
|
||||||
|
EnableClkout5=false
|
||||||
|
EnableClkout5Divider=false
|
||||||
|
EnableClkout6=false
|
||||||
|
EnableClkout6Divider=false
|
||||||
|
EnableLock=false
|
||||||
|
EnableMDRP=false
|
||||||
|
EnableSsc=false
|
||||||
|
GeneralMode=true
|
||||||
|
ICPSELStatic=true
|
||||||
|
ICPSELStaticValue=X
|
||||||
|
LANG=1
|
||||||
|
LPFCAPStaticValue=C0
|
||||||
|
LPFRESStaticValue=X
|
||||||
|
LPFSELStatic=true
|
||||||
|
PLLPowerDown=false
|
||||||
|
PLLReset=false
|
||||||
|
clkfbExternal=false
|
||||||
|
clkfbExternalValue=
|
||||||
|
mDRP_Clock_Frequency=50
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
-series GW5A
|
||||||
|
-device GW5A-25
|
||||||
|
-device_version B
|
||||||
|
-package MBGA121N
|
||||||
|
-part_number GW5A-LV25MG121NC1/I0
|
||||||
|
|
||||||
|
|
||||||
|
-mod_name Gowin_PLL_MOD
|
||||||
|
-file_name gowin_pll_mod
|
||||||
|
-path F:/gitea/DigMic_prj/src/gowin_pll/
|
||||||
|
-type PLL_ADV
|
||||||
|
-file_type vhdl
|
||||||
|
-ssc false
|
||||||
|
-rst true
|
||||||
|
-rst_pwd false
|
||||||
|
-rst_i false
|
||||||
|
-mdrp_en true
|
||||||
|
-rst_o false
|
||||||
|
-fclkin 50
|
||||||
|
-idiv_sel 1
|
||||||
|
-clkfb_sel 0
|
||||||
|
-fbdiv_sel 1
|
||||||
|
-en_lock true
|
||||||
|
-dyn_dpa_en false
|
||||||
|
-clkout0_bypass false
|
||||||
|
-odiv0_sel 10
|
||||||
|
-odiv0_frac_sel 0
|
||||||
|
-clkout0_dt_dir 1
|
||||||
|
-clkout0_dt_step 0
|
||||||
|
-dyn_pe0_sel false
|
||||||
|
-clkout0_pe_coarse 0
|
||||||
|
-clkout0_pe_fine 0
|
||||||
|
-de0_en false
|
||||||
|
-clkout0_dt_dir 1
|
||||||
|
-clkout0_dt_step 0
|
||||||
|
-en_clkout1 false
|
||||||
|
-en_clkout2 false
|
||||||
|
-en_clkout3 false
|
||||||
|
-en_clkout4 false
|
||||||
|
-en_clkout5 false
|
||||||
|
-en_clkout6 false
|
||||||
|
-en_clkfbout false
|
||||||
|
-mdiv_sel 16
|
||||||
|
-mdiv_frac_sel 0
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
library IEEE;
|
||||||
|
use IEEE.std_logic_1164.all;
|
||||||
|
|
||||||
|
|
||||||
|
entity Gowin_PLL is
|
||||||
|
port (
|
||||||
|
clkin: in std_logic;
|
||||||
|
clkout0: out std_logic;
|
||||||
|
mdclk: in std_logic
|
||||||
|
);
|
||||||
|
end Gowin_PLL;
|
||||||
|
|
||||||
|
|
||||||
|
architecture Behavioral of Gowin_PLL is
|
||||||
|
signal mdrdo: std_logic_vector(7 downto 0);
|
||||||
|
signal lock: std_logic;
|
||||||
|
signal wMdOpc: std_logic_vector(1 downto 0);
|
||||||
|
signal wMdAInc: std_logic;
|
||||||
|
signal wMdDIn: std_logic_vector(7 downto 0);
|
||||||
|
signal wMdQOut: std_logic_vector(7 downto 0);
|
||||||
|
signal pll_lock: std_logic;
|
||||||
|
signal pll_rst: std_logic;
|
||||||
|
|
||||||
|
|
||||||
|
component Gowin_PLL_MOD
|
||||||
|
port (
|
||||||
|
clkout0: out std_logic;
|
||||||
|
lock: out std_logic;
|
||||||
|
reset: in std_logic;
|
||||||
|
mdrdo: out std_logic_vector(7 downto 0);
|
||||||
|
clkin: in std_logic;
|
||||||
|
mdclk: in std_logic;
|
||||||
|
mdopc: in std_logic_vector(1 downto 0);
|
||||||
|
mdainc: in std_logic;
|
||||||
|
mdwdi: in std_logic_vector(7 downto 0)
|
||||||
|
);
|
||||||
|
end component;
|
||||||
|
|
||||||
|
|
||||||
|
component PLL_INIT
|
||||||
|
generic (
|
||||||
|
CLK_PERIOD: INTEGER:= 20;
|
||||||
|
MULTI_FAC: INTEGER:= 24
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
I_RST: in std_logic;
|
||||||
|
O_RST: out std_logic;
|
||||||
|
I_LOCK: in std_logic;
|
||||||
|
O_LOCK: out std_logic;
|
||||||
|
I_MD_CLK: in std_logic;
|
||||||
|
O_MD_INC: out std_logic;
|
||||||
|
O_MD_OPC: out std_logic_vector(1 downto 0);
|
||||||
|
O_MD_WR_DATA: out std_logic_vector(7 downto 0);
|
||||||
|
I_MD_RD_DATA: in std_logic_vector(7 downto 0);
|
||||||
|
PLL_INIT_BYPASS: in std_logic;
|
||||||
|
MDRDO: out std_logic_vector(7 downto 0);
|
||||||
|
MDOPC: in std_logic_vector(1 downto 0);
|
||||||
|
MDAINC: in std_logic;
|
||||||
|
MDWDI: in std_logic_vector(7 downto 0)
|
||||||
|
);
|
||||||
|
end component;
|
||||||
|
|
||||||
|
|
||||||
|
begin
|
||||||
|
u_pll: Gowin_PLL_MOD
|
||||||
|
port map (
|
||||||
|
clkout0 => clkout0,
|
||||||
|
lock => pll_lock,
|
||||||
|
mdrdo => wMdQOut,
|
||||||
|
clkin => clkin,
|
||||||
|
reset => pll_rst,
|
||||||
|
mdclk => mdclk,
|
||||||
|
mdopc => wMdOpc,
|
||||||
|
mdainc => wMdAInc,
|
||||||
|
mdwdi => wMdDIn
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
u_pll_init: PLL_INIT
|
||||||
|
generic map (
|
||||||
|
CLK_PERIOD => 20,
|
||||||
|
MULTI_FAC => 16
|
||||||
|
)
|
||||||
|
port map (
|
||||||
|
I_RST => '0',
|
||||||
|
O_RST => pll_rst,
|
||||||
|
I_LOCK => pll_lock,
|
||||||
|
O_LOCK => lock,
|
||||||
|
I_MD_CLK => mdclk,
|
||||||
|
O_MD_INC => wMdAInc,
|
||||||
|
O_MD_OPC => wMdOpc,
|
||||||
|
O_MD_WR_DATA => wMdDIn,
|
||||||
|
I_MD_RD_DATA => wMdQOut,
|
||||||
|
PLL_INIT_BYPASS => '0',
|
||||||
|
MDRDO => mdrdo,
|
||||||
|
MDOPC => "00",
|
||||||
|
MDAINC => '0',
|
||||||
|
MDWDI => "00000000"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
end Behavioral; --Gowin_PLL
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
--Copyright (C)2014-2025 Gowin Semiconductor Corporation.
|
||||||
|
--All rights reserved.
|
||||||
|
--File Title: IP file
|
||||||
|
--Tool Version: V1.9.12 (64-bit)
|
||||||
|
--Part Number: GW5A-LV25MG121NC1/I0
|
||||||
|
--Device: GW5A-25
|
||||||
|
--Device Version: B
|
||||||
|
--Created Time: Sat Sep 19 15:31:17 2026
|
||||||
|
|
||||||
|
library IEEE;
|
||||||
|
use IEEE.std_logic_1164.all;
|
||||||
|
|
||||||
|
entity Gowin_PLL_MOD is
|
||||||
|
port (
|
||||||
|
lock: out std_logic;
|
||||||
|
clkout0: out std_logic;
|
||||||
|
mdrdo: out std_logic_vector(7 downto 0);
|
||||||
|
clkin: in std_logic;
|
||||||
|
reset: in std_logic;
|
||||||
|
mdclk: in std_logic;
|
||||||
|
mdopc: in std_logic_vector(1 downto 0);
|
||||||
|
mdainc: in std_logic;
|
||||||
|
mdwdi: in std_logic_vector(7 downto 0)
|
||||||
|
);
|
||||||
|
end Gowin_PLL_MOD;
|
||||||
|
|
||||||
|
architecture Behavioral of Gowin_PLL_MOD is
|
||||||
|
|
||||||
|
signal clkout1: std_logic;
|
||||||
|
signal clkout2: std_logic;
|
||||||
|
signal clkout3: std_logic;
|
||||||
|
signal clkout4: std_logic;
|
||||||
|
signal clkout5: std_logic;
|
||||||
|
signal clkout6: std_logic;
|
||||||
|
signal clkfbout: std_logic;
|
||||||
|
signal gw_gnd: std_logic;
|
||||||
|
signal PSSEL_i: std_logic_vector(2 downto 0);
|
||||||
|
signal SSCMDSEL_i: std_logic_vector(6 downto 0);
|
||||||
|
signal SSCMDSEL_FRAC_i: std_logic_vector(2 downto 0);
|
||||||
|
|
||||||
|
--component declaration
|
||||||
|
component PLLA
|
||||||
|
generic (
|
||||||
|
FCLKIN: string := "100.0";
|
||||||
|
IDIV_SEL: integer := 1;
|
||||||
|
FBDIV_SEL: integer := 1;
|
||||||
|
ODIV0_SEL: integer := 8;
|
||||||
|
ODIV0_FRAC_SEL: integer := 0;
|
||||||
|
ODIV1_SEL: integer := 8;
|
||||||
|
ODIV2_SEL: integer := 8;
|
||||||
|
ODIV3_SEL: integer := 8;
|
||||||
|
ODIV4_SEL: integer := 8;
|
||||||
|
ODIV5_SEL: integer := 8;
|
||||||
|
ODIV6_SEL: integer := 8;
|
||||||
|
MDIV_SEL: integer := 8;
|
||||||
|
MDIV_FRAC_SEL: integer := 0;
|
||||||
|
CLKOUT0_EN: string := "FALSE";
|
||||||
|
CLKOUT1_EN: string := "FALSE";
|
||||||
|
CLKOUT2_EN: string := "FALSE";
|
||||||
|
CLKOUT3_EN: string := "FALSE";
|
||||||
|
CLKOUT4_EN: string := "FALSE";
|
||||||
|
CLKOUT5_EN: string := "FALSE";
|
||||||
|
CLKOUT6_EN: string := "FALSE";
|
||||||
|
CLKFB_SEL: string := "internal";
|
||||||
|
CLKOUT0_DT_DIR: bit := '1';
|
||||||
|
CLKOUT1_DT_DIR: bit := '1';
|
||||||
|
CLKOUT2_DT_DIR: bit := '1';
|
||||||
|
CLKOUT3_DT_DIR: bit := '1';
|
||||||
|
CLKOUT0_DT_STEP: integer := 0;
|
||||||
|
CLKOUT1_DT_STEP: integer := 0;
|
||||||
|
CLKOUT2_DT_STEP: integer := 0;
|
||||||
|
CLKOUT3_DT_STEP: integer := 0;
|
||||||
|
CLK0_IN_SEL: bit := '0';
|
||||||
|
CLK0_OUT_SEL: bit := '0';
|
||||||
|
CLK1_IN_SEL: bit := '0';
|
||||||
|
CLK1_OUT_SEL: bit := '0';
|
||||||
|
CLK2_IN_SEL: bit := '0';
|
||||||
|
CLK2_OUT_SEL: bit := '0';
|
||||||
|
CLK3_IN_SEL: bit := '0';
|
||||||
|
CLK3_OUT_SEL: bit := '0';
|
||||||
|
CLK4_IN_SEL: bit_vector := "00";
|
||||||
|
CLK4_OUT_SEL: bit := '0';
|
||||||
|
CLK5_IN_SEL: bit := '0';
|
||||||
|
CLK5_OUT_SEL: bit := '0';
|
||||||
|
CLK6_IN_SEL: bit := '0';
|
||||||
|
CLK6_OUT_SEL: bit := '0';
|
||||||
|
CLKOUT0_PE_COARSE: integer := 0;
|
||||||
|
CLKOUT0_PE_FINE: integer := 0;
|
||||||
|
CLKOUT1_PE_COARSE: integer := 0;
|
||||||
|
CLKOUT1_PE_FINE: integer := 0;
|
||||||
|
CLKOUT2_PE_COARSE: integer := 0;
|
||||||
|
CLKOUT2_PE_FINE: integer := 0;
|
||||||
|
CLKOUT3_PE_COARSE: integer := 0;
|
||||||
|
CLKOUT3_PE_FINE: integer := 0;
|
||||||
|
CLKOUT4_PE_COARSE: integer := 0;
|
||||||
|
CLKOUT4_PE_FINE: integer := 0;
|
||||||
|
CLKOUT5_PE_COARSE: integer := 0;
|
||||||
|
CLKOUT5_PE_FINE: integer := 0;
|
||||||
|
CLKOUT6_PE_COARSE: integer := 0;
|
||||||
|
CLKOUT6_PE_FINE: integer := 0;
|
||||||
|
DE0_EN: string := "FALSE";
|
||||||
|
DE1_EN: string := "FALSE";
|
||||||
|
DE2_EN: string := "FALSE";
|
||||||
|
DE3_EN: string := "FALSE";
|
||||||
|
DE4_EN: string := "FALSE";
|
||||||
|
DE5_EN: string := "FALSE";
|
||||||
|
DE6_EN: string := "FALSE";
|
||||||
|
DYN_DPA_EN: string := "FALSE";
|
||||||
|
DYN_PE0_SEL: string := "FALSE";
|
||||||
|
DYN_PE1_SEL: string := "FALSE";
|
||||||
|
DYN_PE2_SEL: string := "FALSE";
|
||||||
|
DYN_PE3_SEL: string := "FALSE";
|
||||||
|
DYN_PE4_SEL: string := "FALSE";
|
||||||
|
DYN_PE5_SEL: string := "FALSE";
|
||||||
|
DYN_PE6_SEL: string := "FALSE";
|
||||||
|
ICP_SEL : std_logic_vector(5 downto 0) := "XXXXXX";
|
||||||
|
LPF_RES : std_logic_vector(2 downto 0) := "XXX";
|
||||||
|
LPF_CAP: bit_vector := "00";
|
||||||
|
RESET_I_EN: string := "FALSE";
|
||||||
|
RESET_O_EN: string := "FALSE";
|
||||||
|
SSC_EN: string := "FALSE"
|
||||||
|
);
|
||||||
|
port (
|
||||||
|
LOCK: out std_logic;
|
||||||
|
CLKOUT0: out std_logic;
|
||||||
|
CLKOUT1: out std_logic;
|
||||||
|
CLKOUT2: out std_logic;
|
||||||
|
CLKOUT3: out std_logic;
|
||||||
|
CLKOUT4: out std_logic;
|
||||||
|
CLKOUT5: out std_logic;
|
||||||
|
CLKOUT6: out std_logic;
|
||||||
|
CLKFBOUT: out std_logic;
|
||||||
|
MDRDO: out std_logic_vector(7 downto 0);
|
||||||
|
CLKIN: in std_logic;
|
||||||
|
CLKFB: in std_logic;
|
||||||
|
RESET: in std_logic;
|
||||||
|
PLLPWD: in std_logic;
|
||||||
|
RESET_I: in std_logic;
|
||||||
|
RESET_O: in std_logic;
|
||||||
|
PSSEL: in std_logic_vector(2 downto 0);
|
||||||
|
PSDIR: in std_logic;
|
||||||
|
PSPULSE: in std_logic;
|
||||||
|
SSCPOL: in std_logic;
|
||||||
|
SSCON: in std_logic;
|
||||||
|
SSCMDSEL: in std_logic_vector(6 downto 0);
|
||||||
|
SSCMDSEL_FRAC: in std_logic_vector(2 downto 0);
|
||||||
|
MDCLK: in std_logic;
|
||||||
|
MDOPC: in std_logic_vector(1 downto 0);
|
||||||
|
MDAINC: in std_logic;
|
||||||
|
MDWDI: in std_logic_vector(7 downto 0)
|
||||||
|
);
|
||||||
|
end component;
|
||||||
|
begin
|
||||||
|
gw_gnd <= '0';
|
||||||
|
|
||||||
|
PSSEL_i <= gw_gnd & gw_gnd & gw_gnd;
|
||||||
|
SSCMDSEL_i <= gw_gnd & gw_gnd & gw_gnd & gw_gnd & gw_gnd & gw_gnd & gw_gnd;
|
||||||
|
SSCMDSEL_FRAC_i <= gw_gnd & gw_gnd & gw_gnd;
|
||||||
|
|
||||||
|
PLLA_inst: PLLA
|
||||||
|
generic map (
|
||||||
|
FCLKIN => "50",
|
||||||
|
IDIV_SEL => 1,
|
||||||
|
FBDIV_SEL => 1,
|
||||||
|
ODIV0_SEL => 10,
|
||||||
|
ODIV1_SEL => 8,
|
||||||
|
ODIV2_SEL => 8,
|
||||||
|
ODIV3_SEL => 8,
|
||||||
|
ODIV4_SEL => 8,
|
||||||
|
ODIV5_SEL => 8,
|
||||||
|
ODIV6_SEL => 8,
|
||||||
|
MDIV_SEL => 16,
|
||||||
|
MDIV_FRAC_SEL => 0,
|
||||||
|
ODIV0_FRAC_SEL => 0,
|
||||||
|
CLKOUT0_EN => "TRUE",
|
||||||
|
CLKOUT1_EN => "FALSE",
|
||||||
|
CLKOUT2_EN => "FALSE",
|
||||||
|
CLKOUT3_EN => "FALSE",
|
||||||
|
CLKOUT4_EN => "FALSE",
|
||||||
|
CLKOUT5_EN => "FALSE",
|
||||||
|
CLKOUT6_EN => "FALSE",
|
||||||
|
CLKFB_SEL => "INTERNAL",
|
||||||
|
CLKOUT0_DT_DIR => '1',
|
||||||
|
CLKOUT1_DT_DIR => '1',
|
||||||
|
CLKOUT2_DT_DIR => '1',
|
||||||
|
CLKOUT3_DT_DIR => '1',
|
||||||
|
CLKOUT0_DT_STEP => 0,
|
||||||
|
CLKOUT1_DT_STEP => 0,
|
||||||
|
CLKOUT2_DT_STEP => 0,
|
||||||
|
CLKOUT3_DT_STEP => 0,
|
||||||
|
CLK0_IN_SEL => '0',
|
||||||
|
CLK0_OUT_SEL => '0',
|
||||||
|
CLK1_IN_SEL => '0',
|
||||||
|
CLK1_OUT_SEL => '0',
|
||||||
|
CLK2_IN_SEL => '0',
|
||||||
|
CLK2_OUT_SEL => '0',
|
||||||
|
CLK3_IN_SEL => '0',
|
||||||
|
CLK3_OUT_SEL => '0',
|
||||||
|
CLK4_IN_SEL => "00",
|
||||||
|
CLK4_OUT_SEL => '0',
|
||||||
|
CLK5_IN_SEL => '0',
|
||||||
|
CLK5_OUT_SEL => '0',
|
||||||
|
CLK6_IN_SEL => '0',
|
||||||
|
CLK6_OUT_SEL => '0',
|
||||||
|
DYN_DPA_EN => "FALSE",
|
||||||
|
CLKOUT0_PE_COARSE => 0,
|
||||||
|
CLKOUT0_PE_FINE => 0,
|
||||||
|
CLKOUT1_PE_COARSE => 0,
|
||||||
|
CLKOUT1_PE_FINE => 0,
|
||||||
|
CLKOUT2_PE_COARSE => 0,
|
||||||
|
CLKOUT2_PE_FINE => 0,
|
||||||
|
CLKOUT3_PE_COARSE => 0,
|
||||||
|
CLKOUT3_PE_FINE => 0,
|
||||||
|
CLKOUT4_PE_COARSE => 0,
|
||||||
|
CLKOUT4_PE_FINE => 0,
|
||||||
|
CLKOUT5_PE_COARSE => 0,
|
||||||
|
CLKOUT5_PE_FINE => 0,
|
||||||
|
CLKOUT6_PE_COARSE => 0,
|
||||||
|
CLKOUT6_PE_FINE => 0,
|
||||||
|
DYN_PE0_SEL => "FALSE",
|
||||||
|
DYN_PE1_SEL => "FALSE",
|
||||||
|
DYN_PE2_SEL => "FALSE",
|
||||||
|
DYN_PE3_SEL => "FALSE",
|
||||||
|
DYN_PE4_SEL => "FALSE",
|
||||||
|
DYN_PE5_SEL => "FALSE",
|
||||||
|
DYN_PE6_SEL => "FALSE",
|
||||||
|
DE0_EN => "FALSE",
|
||||||
|
DE1_EN => "FALSE",
|
||||||
|
DE2_EN => "FALSE",
|
||||||
|
DE3_EN => "FALSE",
|
||||||
|
DE4_EN => "FALSE",
|
||||||
|
DE5_EN => "FALSE",
|
||||||
|
DE6_EN => "FALSE",
|
||||||
|
RESET_I_EN => "FALSE",
|
||||||
|
RESET_O_EN => "FALSE",
|
||||||
|
ICP_SEL => "XXXXXX",
|
||||||
|
LPF_RES => "XXX",
|
||||||
|
LPF_CAP => "00",
|
||||||
|
SSC_EN => "FALSE"
|
||||||
|
)
|
||||||
|
port map (
|
||||||
|
LOCK => lock,
|
||||||
|
CLKOUT0 => clkout0,
|
||||||
|
CLKOUT1 => clkout1,
|
||||||
|
CLKOUT2 => clkout2,
|
||||||
|
CLKOUT3 => clkout3,
|
||||||
|
CLKOUT4 => clkout4,
|
||||||
|
CLKOUT5 => clkout5,
|
||||||
|
CLKOUT6 => clkout6,
|
||||||
|
CLKFBOUT => clkfbout,
|
||||||
|
MDRDO => mdrdo,
|
||||||
|
CLKIN => clkin,
|
||||||
|
CLKFB => gw_gnd,
|
||||||
|
RESET => reset,
|
||||||
|
PLLPWD => gw_gnd,
|
||||||
|
RESET_I => gw_gnd,
|
||||||
|
RESET_O => gw_gnd,
|
||||||
|
PSSEL => PSSEL_i,
|
||||||
|
PSDIR => gw_gnd,
|
||||||
|
PSPULSE => gw_gnd,
|
||||||
|
SSCPOL => gw_gnd,
|
||||||
|
SSCON => gw_gnd,
|
||||||
|
SSCMDSEL => SSCMDSEL_i,
|
||||||
|
SSCMDSEL_FRAC => SSCMDSEL_FRAC_i,
|
||||||
|
MDCLK => mdclk,
|
||||||
|
MDOPC => mdopc,
|
||||||
|
MDAINC => mdainc,
|
||||||
|
MDWDI => mdwdi
|
||||||
|
);
|
||||||
|
|
||||||
|
end Behavioral; --Gowin_PLL_MOD
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
`timescale 1ns / 1ps
|
||||||
|
// =============================================================================
|
||||||
|
// Module: i2s_transmitter
|
||||||
|
// Description: Standard I2S Audio Transmitter for PCM5102A DAC.
|
||||||
|
// - Fully free-running clock generation (guaranteed immediate BCK/LRCK).
|
||||||
|
// - 64 BCK cycles per stereo frame (32 bits per channel).
|
||||||
|
// - 24-bit audio sample depth with zero padding.
|
||||||
|
// - Transmits MSB first with 1-cycle I2S delay after LRCK edges.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
module i2s_transmitter #(
|
||||||
|
parameter integer CLK_FREQ_HZ = 50000000,
|
||||||
|
parameter integer SAMPLE_RATE_HZ = 48000,
|
||||||
|
parameter integer DATA_WIDTH = 24
|
||||||
|
)(
|
||||||
|
input wire clk,
|
||||||
|
input wire rst_n,
|
||||||
|
input wire signed [23:0] data_left, // Left channel audio sample
|
||||||
|
input wire signed [23:0] data_right, // Right channel audio sample
|
||||||
|
output wire bck, // Bit Clock to DAC
|
||||||
|
output wire lrck, // Word Select (Left/Right) to DAC
|
||||||
|
output wire din, // Serial Data to DAC
|
||||||
|
output reg sample_tick, // Sample rate strobe to update DDS
|
||||||
|
output wire [5:0] dbg_bit_cnt, // debug only - unconnected in dac_top
|
||||||
|
output wire dbg_bck_fall // debug only - unconnected in dac_top
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate clock divider for BCK half-period
|
||||||
|
// f_bck = SAMPLE_RATE_HZ * 64
|
||||||
|
// BCK_HALF_PERIOD = CLK_FREQ_HZ / (f_bck * 2)
|
||||||
|
localparam integer CLK_DIV = CLK_FREQ_HZ / (SAMPLE_RATE_HZ * 64 * 2);
|
||||||
|
localparam integer CNT_WIDTH = (CLK_DIV > 1) ? $clog2(CLK_DIV + 1) : 1;
|
||||||
|
|
||||||
|
reg [CNT_WIDTH-1:0] clk_cnt = {CNT_WIDTH{1'b0}};
|
||||||
|
reg bck_reg = 1'b0;
|
||||||
|
reg lrck_reg = 1'b0;
|
||||||
|
reg din_reg = 1'b0;
|
||||||
|
|
||||||
|
reg bck_fall_pulse = 1'b0;
|
||||||
|
reg [5:0] bit_cnt = 6'd0; // 0 to 63 (64 bits per stereo frame)
|
||||||
|
reg [31:0] shift_reg = 32'd0;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1. Clock divider generating BCK (Unconditional free-running)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
always @(posedge clk) begin
|
||||||
|
bck_fall_pulse <= 1'b0;
|
||||||
|
if (clk_cnt >= CLK_DIV - 1) begin
|
||||||
|
clk_cnt <= {CNT_WIDTH{1'b0}};
|
||||||
|
bck_reg <= ~bck_reg;
|
||||||
|
// Falling edge strobe when bck goes 1 -> 0
|
||||||
|
if (bck_reg == 1'b1) begin
|
||||||
|
bck_fall_pulse <= 1'b1;
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
clk_cnt <= clk_cnt + 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 2. I2S Serial Frame State Machine (updates on BCK falling edge)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
always @(posedge clk) begin
|
||||||
|
sample_tick <= 1'b0; // Default: single-cycle pulse
|
||||||
|
|
||||||
|
if (!rst_n) begin
|
||||||
|
bit_cnt <= 6'd0;
|
||||||
|
lrck_reg <= 1'b0;
|
||||||
|
din_reg <= 1'b0;
|
||||||
|
shift_reg <= 32'd0;
|
||||||
|
end else if (bck_fall_pulse) begin
|
||||||
|
if (bit_cnt == 6'd63) begin
|
||||||
|
bit_cnt <= 6'd0;
|
||||||
|
end else begin
|
||||||
|
bit_cnt <= bit_cnt + 1'b1;
|
||||||
|
end
|
||||||
|
|
||||||
|
case (bit_cnt)
|
||||||
|
// --- Left Channel (LRCK = 0) ---
|
||||||
|
6'd0: begin
|
||||||
|
lrck_reg <= 1'b0; // Start Left channel
|
||||||
|
din_reg <= 1'b0; // 1-bit I2S delay
|
||||||
|
shift_reg <= {data_left, 8'd0}; // Latch 24-bit left sample
|
||||||
|
end
|
||||||
|
|
||||||
|
6'd1, 6'd2, 6'd3, 6'd4, 6'd5, 6'd6, 6'd7, 6'd8,
|
||||||
|
6'd9, 6'd10, 6'd11, 6'd12, 6'd13, 6'd14, 6'd15, 6'd16,
|
||||||
|
6'd17, 6'd18, 6'd19, 6'd20, 6'd21, 6'd22, 6'd23, 6'd24: begin
|
||||||
|
din_reg <= shift_reg[31]; // Transmit MSB first
|
||||||
|
shift_reg <= {shift_reg[30:0], 1'b0};
|
||||||
|
end
|
||||||
|
|
||||||
|
6'd25, 6'd26, 6'd27, 6'd28, 6'd29, 6'd30, 6'd31: begin
|
||||||
|
din_reg <= 1'b0; // Zero padding for remaining bits
|
||||||
|
shift_reg <= {shift_reg[30:0], 1'b0};
|
||||||
|
end
|
||||||
|
|
||||||
|
// --- Right Channel (LRCK = 1) ---
|
||||||
|
6'd32: begin
|
||||||
|
lrck_reg <= 1'b1; // Start Right channel
|
||||||
|
din_reg <= 1'b0; // 1-bit I2S delay
|
||||||
|
shift_reg <= {data_right, 8'd0}; // Latch 24-bit right sample
|
||||||
|
end
|
||||||
|
|
||||||
|
6'd33, 6'd34, 6'd35, 6'd36, 6'd37, 6'd38, 6'd39, 6'd40,
|
||||||
|
6'd41, 6'd42, 6'd43, 6'd44, 6'd45, 6'd46, 6'd47, 6'd48,
|
||||||
|
6'd49, 6'd50, 6'd51, 6'd52, 6'd53, 6'd54, 6'd55, 6'd56: begin
|
||||||
|
din_reg <= shift_reg[31]; // Transmit MSB first
|
||||||
|
shift_reg <= {shift_reg[30:0], 1'b0};
|
||||||
|
end
|
||||||
|
|
||||||
|
6'd57, 6'd58, 6'd59, 6'd60, 6'd61, 6'd62: begin
|
||||||
|
din_reg <= 1'b0; // Zero padding
|
||||||
|
shift_reg <= {shift_reg[30:0], 1'b0};
|
||||||
|
end
|
||||||
|
|
||||||
|
6'd63: begin
|
||||||
|
din_reg <= 1'b0;
|
||||||
|
sample_tick <= 1'b1; // Signal DDS to prepare next sample
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Output assignments
|
||||||
|
assign bck = bck_reg;
|
||||||
|
assign lrck = lrck_reg;
|
||||||
|
assign din = din_reg;
|
||||||
|
|
||||||
|
// Debug taps for the diagnostic build; dac_top leaves these unconnected.
|
||||||
|
assign dbg_bit_cnt = bit_cnt;
|
||||||
|
assign dbg_bck_fall = bck_fall_pulse;
|
||||||
|
|
||||||
|
endmodule
|
||||||
+13
-4
@@ -12,6 +12,12 @@
|
|||||||
-- MIC_CLK и держит его всю низкую фазу, при SELECT = '1' -- по фронту и всю
|
-- MIC_CLK и держит его всю низкую фазу, при SELECT = '1' -- по фронту и всю
|
||||||
-- высокую. Защёлкиваем в середине нужной фазы: там есть запас и на задержку
|
-- высокую. Защёлкиваем в середине нужной фазы: там есть запас и на задержку
|
||||||
-- выдачи, и на два такта синхронизатора.
|
-- выдачи, и на два такта синхронизатора.
|
||||||
|
--
|
||||||
|
-- Если микрофон подключён через линию (LVDS и т.п.), её задержка туда и
|
||||||
|
-- обратно сдвигает момент смены данных, и точку выборки задаёт SMP_CNT:
|
||||||
|
-- такт периода MIC_CLK (0..CLK_DIV-1), на котором берётся выход
|
||||||
|
-- синхронизатора. Смена данных, пришедшая между тактами k и k+1, видна на
|
||||||
|
-- выходе синхронизатора с такта k+3; брать бит надо подальше от этого места.
|
||||||
----------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------
|
||||||
library ieee;
|
library ieee;
|
||||||
use ieee.std_logic_1164.all;
|
use ieee.std_logic_1164.all;
|
||||||
@@ -20,7 +26,8 @@ use ieee.numeric_std.all;
|
|||||||
entity pdm_rx is
|
entity pdm_rx is
|
||||||
generic (
|
generic (
|
||||||
CLK_DIV : integer := 16; -- 50 МГц / 16 = 3.125 МГц, Normal mode микрофона
|
CLK_DIV : integer := 16; -- 50 МГц / 16 = 3.125 МГц, Normal mode микрофона
|
||||||
SEL_HIGH : std_logic := '0' -- уровень на выводе SELECT
|
SEL_HIGH : std_logic := '0'; -- уровень на выводе SELECT
|
||||||
|
SMP_CNT : integer := -1 -- точка выборки; -1 -- по SEL_HIGH, микрофон рядом
|
||||||
);
|
);
|
||||||
port (
|
port (
|
||||||
clk : in std_logic;
|
clk : in std_logic;
|
||||||
@@ -40,14 +47,16 @@ architecture rtl of pdm_rx is
|
|||||||
-- Середина фазы, в которой микрофон держит данные, плюс два такта синхронизатора
|
-- Середина фазы, в которой микрофон держит данные, плюс два такта синхронизатора
|
||||||
function sample_point return integer is
|
function sample_point return integer is
|
||||||
begin
|
begin
|
||||||
if SEL_HIGH = '1' then
|
if SMP_CNT >= 0 then
|
||||||
|
return SMP_CNT;
|
||||||
|
elsif SEL_HIGH = '1' then
|
||||||
return 3 * CLK_DIV / 4 + 2;
|
return 3 * CLK_DIV / 4 + 2;
|
||||||
else
|
else
|
||||||
return CLK_DIV / 4 + 2;
|
return CLK_DIV / 4 + 2;
|
||||||
end if;
|
end if;
|
||||||
end function;
|
end function;
|
||||||
|
|
||||||
constant SMP_CNT : integer := sample_point;
|
constant SMP_AT : integer := sample_point;
|
||||||
|
|
||||||
signal cnt : integer range 0 to CLK_DIV - 1 := 0;
|
signal cnt : integer range 0 to CLK_DIV - 1 := 0;
|
||||||
signal clk_r : std_logic := '0';
|
signal clk_r : std_logic := '0';
|
||||||
@@ -83,7 +92,7 @@ begin
|
|||||||
clk_r <= '1';
|
clk_r <= '1';
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
if cnt = SMP_CNT then
|
if cnt = SMP_AT then
|
||||||
bit_out <= sync(1);
|
bit_out <= sync(1);
|
||||||
bit_valid <= '1';
|
bit_valid <= '1';
|
||||||
end if;
|
end if;
|
||||||
|
|||||||
+397
@@ -0,0 +1,397 @@
|
|||||||
|
`timescale 1ns/1ns
|
||||||
|
|
||||||
|
|
||||||
|
module PLL_INIT #
|
||||||
|
( parameter CLK_PERIOD = 20
|
||||||
|
, parameter MULTI_FAC = 24
|
||||||
|
)
|
||||||
|
( input I_RST
|
||||||
|
, input I_MD_CLK
|
||||||
|
|
||||||
|
, output O_RST
|
||||||
|
|
||||||
|
, output O_MD_INC
|
||||||
|
, output [1:0] O_MD_OPC
|
||||||
|
, output [7:0] O_MD_WR_DATA
|
||||||
|
, input [7:0] I_MD_RD_DATA
|
||||||
|
|
||||||
|
, input I_LOCK
|
||||||
|
, output O_LOCK
|
||||||
|
|
||||||
|
, input PLL_INIT_BYPASS
|
||||||
|
, output [7:0] MDRDO
|
||||||
|
, input [1:0] MDOPC
|
||||||
|
, input MDAINC
|
||||||
|
, input [7:0] MDWDI
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
`ifdef SIM
|
||||||
|
`define DL #1
|
||||||
|
`else
|
||||||
|
`define DL
|
||||||
|
`endif
|
||||||
|
|
||||||
|
`ifdef SIM
|
||||||
|
localparam WAIT_TIME = 'd8_000;
|
||||||
|
localparam LOCK_TIME = 'd2_000;
|
||||||
|
`else
|
||||||
|
localparam WAIT_TIME = 'd800_000;
|
||||||
|
localparam LOCK_TIME = 'd200_000;
|
||||||
|
`endif
|
||||||
|
localparam WAIT_CNT = (WAIT_TIME + CLK_PERIOD - 1) / CLK_PERIOD; //! 1ms
|
||||||
|
localparam LOCK_CNT = (LOCK_TIME + CLK_PERIOD - 1) / CLK_PERIOD;
|
||||||
|
localparam WW = $clog2(WAIT_CNT + 1);
|
||||||
|
localparam LW = $clog2(LOCK_CNT + 1);
|
||||||
|
|
||||||
|
localparam INIT_STEP_0 = 4'd0
|
||||||
|
, INIT_STEP_1 = 4'd1
|
||||||
|
, INIT_STEP_2 = 4'd2
|
||||||
|
, INIT_STEP_3 = 4'd3
|
||||||
|
, INIT_STEP_4 = 4'd4
|
||||||
|
, INIT_STEP_5 = 4'd5
|
||||||
|
, INIT_STEP_6 = 4'd6
|
||||||
|
, INIT_STEP_7 = 4'd7
|
||||||
|
, INIT_STEP_8 = 4'd8
|
||||||
|
, INIT_STEP_9 = 4'd9
|
||||||
|
;
|
||||||
|
|
||||||
|
reg [ 1:0] rEnable = 'b0;
|
||||||
|
|
||||||
|
reg rRomRd = 'b0;
|
||||||
|
reg [ 7:0] rRomAddr = 'b0;
|
||||||
|
reg [ 24:0] rRomData = 'b0;
|
||||||
|
|
||||||
|
reg [ 2:0] rReqCnt = 'b0;
|
||||||
|
reg [ 4:0] rLoopCnt = 'b0;
|
||||||
|
|
||||||
|
reg [ 3:0] rChkOkCnt = 'b0;
|
||||||
|
reg [ 5:0] rChkStart = 'b0;
|
||||||
|
|
||||||
|
reg [ 3:0] rInitFsmC = INIT_STEP_0 ;
|
||||||
|
reg [ 3:0] rInitFsmN = INIT_STEP_1 ;
|
||||||
|
|
||||||
|
reg [ 7:0] rMdAddr = 'b0;
|
||||||
|
|
||||||
|
reg rMdInc = 'b0;
|
||||||
|
reg [ 1:0] rMdOpc = 'b0;
|
||||||
|
reg [ 7:0] rMdDOut = 'b0;
|
||||||
|
|
||||||
|
reg [ 7:0] rChkFlag = 'b0;
|
||||||
|
|
||||||
|
reg rLockReg = 'b0;
|
||||||
|
reg [LW-1:0] rLockCnt = 'b0;
|
||||||
|
reg rLocked = 'b0;
|
||||||
|
reg [WW-1:0] rWaitCnt = 'b0;
|
||||||
|
|
||||||
|
reg rLastStep = 'b0;
|
||||||
|
|
||||||
|
reg rLockOut = 'b0;
|
||||||
|
|
||||||
|
reg rChkVld = 'b0;
|
||||||
|
reg [ 7:0] rRomAddrVld = 'b0;
|
||||||
|
|
||||||
|
reg rRstOut = 1'b1;
|
||||||
|
|
||||||
|
wire wEnable = rEnable[1];
|
||||||
|
wire [ 7:0] wMdAddrNext = rMdAddr + 2'd1;
|
||||||
|
wire wLocked = (rLockCnt >= LOCK_CNT);
|
||||||
|
|
||||||
|
wire wIsStep0 = (rInitFsmC == INIT_STEP_0);
|
||||||
|
wire wIsStep1 = (rInitFsmC == INIT_STEP_1);
|
||||||
|
wire wIsStep2 = (rInitFsmC == INIT_STEP_2);
|
||||||
|
wire wIsStep3 = (rInitFsmC == INIT_STEP_3);
|
||||||
|
wire wIsStep4 = (rInitFsmC == INIT_STEP_4);
|
||||||
|
wire wIsStep5 = (rInitFsmC == INIT_STEP_5);
|
||||||
|
wire wIsStep6 = (rInitFsmC == INIT_STEP_6);
|
||||||
|
wire wIsStep7 = (rInitFsmC == INIT_STEP_7);
|
||||||
|
wire wIsStep8 = (rInitFsmC == INIT_STEP_8);
|
||||||
|
wire wIsStep9 = (rInitFsmC == INIT_STEP_9);
|
||||||
|
|
||||||
|
wire wReqOver = ~rRomData[24];
|
||||||
|
wire wMdrpReq = rRomData[24];
|
||||||
|
wire wWaitLock = rReqCnt[2];//(rReqCnt == 4);
|
||||||
|
|
||||||
|
|
||||||
|
wire [ 7:0] wReqAddr = rRomData[16+:8];
|
||||||
|
wire [ 7:0] wReqMask = rRomData[ 8+:8];
|
||||||
|
wire [ 7:0] wReqData = rRomData[ 0+:8];
|
||||||
|
|
||||||
|
|
||||||
|
localparam ROM_FILE = "";
|
||||||
|
|
||||||
|
reg [ 27:0] rRomDReg = 'b0;
|
||||||
|
|
||||||
|
|
||||||
|
reg [27:0] rRom [63:0];
|
||||||
|
|
||||||
|
|
||||||
|
always @ (posedge PLL_INIT_BYPASS or posedge I_MD_CLK or posedge I_RST) begin
|
||||||
|
if (PLL_INIT_BYPASS || I_RST) rEnable <=`DL 2'b00;
|
||||||
|
else rEnable <=`DL {rEnable[0], 1'b1};
|
||||||
|
end
|
||||||
|
|
||||||
|
generate
|
||||||
|
if (ROM_FILE != "") begin: ram_init_file
|
||||||
|
initial begin
|
||||||
|
$readmemh (ROM_FILE, rRom);
|
||||||
|
end
|
||||||
|
|
||||||
|
always @ (posedge I_MD_CLK) begin
|
||||||
|
rRomDReg <=`DL rRom[rRomAddr[5:0]];
|
||||||
|
end
|
||||||
|
end else begin : ram_init
|
||||||
|
always @ (posedge I_MD_CLK) begin
|
||||||
|
if (~rEnable[1]) begin
|
||||||
|
rRomDReg <=`DL 28'h000_0000;
|
||||||
|
//1
|
||||||
|
rRom[00] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F03
|
||||||
|
: (MULTI_FAC > 16) ? 28'h10B_3F01
|
||||||
|
: 28'h10B_3F00;
|
||||||
|
rRom[01] <=`DL 28'h10C_E080;
|
||||||
|
rRom[02] <=`DL 28'h111_0701;
|
||||||
|
rRom[03] <=`DL 28'h112_0C08;
|
||||||
|
//2
|
||||||
|
rRom[04] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F03
|
||||||
|
: (MULTI_FAC > 16) ? 28'h10B_3F01
|
||||||
|
: 28'h10B_3F00;
|
||||||
|
rRom[05] <=`DL 28'h10C_E080;
|
||||||
|
rRom[06] <=`DL 28'h111_0703;
|
||||||
|
rRom[07] <=`DL 28'h112_0C08;
|
||||||
|
//3
|
||||||
|
rRom[08] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F03
|
||||||
|
: (MULTI_FAC > 16) ? 28'h10B_3F01
|
||||||
|
: 28'h10B_3F00;
|
||||||
|
rRom[09] <=`DL 28'h10C_E0A0;
|
||||||
|
rRom[10] <=`DL 28'h111_0703;
|
||||||
|
rRom[11] <=`DL 28'h112_0C08;
|
||||||
|
//4
|
||||||
|
rRom[12] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F03
|
||||||
|
: (MULTI_FAC > 16) ? 28'h10B_3F01
|
||||||
|
: 28'h10B_3F00;
|
||||||
|
rRom[13] <=`DL 28'h10C_E0A0;
|
||||||
|
rRom[14] <=`DL 28'h111_0707;
|
||||||
|
rRom[15] <=`DL 28'h112_0C08;
|
||||||
|
//5
|
||||||
|
rRom[16] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F07
|
||||||
|
: (MULTI_FAC > 16) ? 28'h10B_3F03
|
||||||
|
: 28'h10B_3F01;
|
||||||
|
rRom[17] <=`DL 28'h10C_E0A0;
|
||||||
|
rRom[18] <=`DL 28'h111_0707;
|
||||||
|
rRom[19] <=`DL 28'h112_0C08;
|
||||||
|
//6
|
||||||
|
rRom[20] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F05
|
||||||
|
: (MULTI_FAC > 16) ? 28'h10B_3F02
|
||||||
|
: 28'h10B_3F01;
|
||||||
|
rRom[21] <=`DL 28'h10C_E0C0;
|
||||||
|
rRom[22] <=`DL 28'h111_0707;
|
||||||
|
rRom[23] <=`DL 28'h112_0C08;
|
||||||
|
//over
|
||||||
|
rRom[24] <=`DL 28'h000_0000;
|
||||||
|
rRom[25] <=`DL 28'h000_0000;
|
||||||
|
rRom[26] <=`DL 28'h000_0000;
|
||||||
|
rRom[27] <=`DL 28'h000_0000;
|
||||||
|
//1.5
|
||||||
|
rRom[28] <=`DL (MULTI_FAC > 34) ? 28'h10B3F03
|
||||||
|
: (MULTI_FAC > 16) ? 28'h10B3F01
|
||||||
|
: 28'h10B3F00;
|
||||||
|
rRom[29] <=`DL 28'h10CE080;
|
||||||
|
rRom[30] <=`DL 28'h1110702;
|
||||||
|
rRom[31] <=`DL 28'h1120C08;
|
||||||
|
//2.5
|
||||||
|
rRom[32] <=`DL (MULTI_FAC > 34) ? 28'h10B3F05
|
||||||
|
: (MULTI_FAC > 16) ? 28'h10B3F02
|
||||||
|
: 28'h10B3F00;
|
||||||
|
rRom[33] <=`DL 28'h10CE080;
|
||||||
|
rRom[34] <=`DL 28'h1110703;
|
||||||
|
rRom[35] <=`DL 28'h1120C08;
|
||||||
|
//3.5
|
||||||
|
rRom[36] <=`DL (MULTI_FAC > 34) ? 28'h10B3F03
|
||||||
|
: (MULTI_FAC > 16) ? 28'h10B3F01
|
||||||
|
: 28'h10B3F00;
|
||||||
|
rRom[37] <=`DL 28'h10CE0A0;
|
||||||
|
rRom[38] <=`DL 28'h1110704;
|
||||||
|
rRom[39] <=`DL 28'h112_0C08;
|
||||||
|
|
||||||
|
//4.5
|
||||||
|
rRom[40] <=`DL (MULTI_FAC > 34) ? 28'h10B3F04
|
||||||
|
: (MULTI_FAC > 16) ? 28'h10B3F02
|
||||||
|
: 28'h10B3F00;
|
||||||
|
rRom[41] <=`DL 28'h10CE0A0;
|
||||||
|
rRom[42] <=`DL 28'h1110707;
|
||||||
|
rRom[43] <=`DL 28'h1120C08;
|
||||||
|
|
||||||
|
//5.5
|
||||||
|
rRom[44] <=`DL (MULTI_FAC > 34) ? 28'h10B3F03
|
||||||
|
: (MULTI_FAC > 16) ? 28'h10B3F01
|
||||||
|
: 28'h10B3F00;
|
||||||
|
rRom[45] <=`DL 28'h10CE0C0;
|
||||||
|
rRom[46] <=`DL 28'h1110707;
|
||||||
|
rRom[47] <=`DL 28'h1120C08;
|
||||||
|
|
||||||
|
rRom[48] <=`DL 28'h000_0000;
|
||||||
|
rRom[49] <=`DL 28'h000_0000;
|
||||||
|
rRom[50] <=`DL 28'h000_0000;
|
||||||
|
rRom[51] <=`DL 28'h000_0000;
|
||||||
|
rRom[52] <=`DL 28'h000_0000;
|
||||||
|
rRom[53] <=`DL 28'h000_0000;
|
||||||
|
rRom[54] <=`DL 28'h000_0000;
|
||||||
|
rRom[55] <=`DL 28'h000_0000;
|
||||||
|
rRom[56] <=`DL 28'h000_0000;
|
||||||
|
rRom[57] <=`DL 28'h000_0000;
|
||||||
|
rRom[58] <=`DL 28'h000_0000;
|
||||||
|
rRom[59] <=`DL 28'h000_0000;
|
||||||
|
rRom[60] <=`DL 28'h000_0000;
|
||||||
|
rRom[61] <=`DL 28'h000_0000;
|
||||||
|
rRom[62] <=`DL 28'h000_0000;
|
||||||
|
rRom[63] <=`DL 28'h000_0000;
|
||||||
|
|
||||||
|
|
||||||
|
end else
|
||||||
|
rRomDReg <=`DL rRom[rRomAddr[5:0]];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endgenerate
|
||||||
|
|
||||||
|
|
||||||
|
always @ (*) begin
|
||||||
|
casex (rChkFlag[5:0])
|
||||||
|
6'b111_111 : {rChkVld, rRomAddrVld} = 9'b1_0000_1000;
|
||||||
|
6'b011_111 : {rChkVld, rRomAddrVld} = 9'b1_0000_1000;
|
||||||
|
6'b111_110 : {rChkVld, rRomAddrVld} = 9'b1_0000_1100;
|
||||||
|
6'b111_10x : {rChkVld, rRomAddrVld} = 9'b1_0000_1100;
|
||||||
|
6'bx01_111 : {rChkVld, rRomAddrVld} = 9'b1_0000_0100;
|
||||||
|
6'b011_110 : {rChkVld, rRomAddrVld} = 9'b1_0000_1000;
|
||||||
|
6'bxx0_111 : {rChkVld, rRomAddrVld} = 9'b1_0000_0100;
|
||||||
|
6'bx01_110 : {rChkVld, rRomAddrVld} = 9'b1_0000_1000;
|
||||||
|
6'b011_10x : {rChkVld, rRomAddrVld} = 9'b1_0000_1100;
|
||||||
|
6'b111_0xx : {rChkVld, rRomAddrVld} = 9'b1_0001_0000;
|
||||||
|
|
||||||
|
6'bxxx_011 : {rChkVld, rRomAddrVld} = 9'b1_0001_1100;
|
||||||
|
6'bxx0_110 : {rChkVld, rRomAddrVld} = 9'b1_0010_0000;
|
||||||
|
6'bx01_100 : {rChkVld, rRomAddrVld} = 9'b1_0010_0100;
|
||||||
|
6'b011_000 : {rChkVld, rRomAddrVld} = 9'b1_0010_1000;
|
||||||
|
6'b110_000 : {rChkVld, rRomAddrVld} = 9'b1_0010_1100;
|
||||||
|
6'bxxx_x01 : {rChkVld, rRomAddrVld} = 9'b1_0000_0000;
|
||||||
|
6'bxxx_010 : {rChkVld, rRomAddrVld} = 9'b1_0000_0100;
|
||||||
|
6'bxx0_100 : {rChkVld, rRomAddrVld} = 9'b1_0000_1000;
|
||||||
|
6'bx01_000 : {rChkVld, rRomAddrVld} = 9'b1_0000_1100;
|
||||||
|
6'b010_000 : {rChkVld, rRomAddrVld} = 9'b1_0001_0000;
|
||||||
|
6'b100_000 : {rChkVld, rRomAddrVld} = 9'b1_0001_0100;
|
||||||
|
|
||||||
|
6'b000_000 : {rChkVld, rRomAddrVld} = 9'b0_0000_0000;
|
||||||
|
|
||||||
|
default : {rChkVld, rRomAddrVld} = 9'b0_0000_0000;
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
|
||||||
|
always @ (posedge I_MD_CLK) begin
|
||||||
|
rInitFsmC <=`DL wEnable ? rInitFsmN : INIT_STEP_0;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rRomRd <=`DL wEnable & (wIsStep1 | wIsStep6 & ~wWaitLock);
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rRomData <=`DL rRomRd ? rRomDReg[24:0] : rRomData;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rRomAddr <=`DL ~wEnable ? 8'd0
|
||||||
|
: (wIsStep3 & wReqOver) ? rRomAddrVld
|
||||||
|
: rRomRd ? rRomAddr + 1'd1
|
||||||
|
: rRomAddr;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rLastStep <=`DL wEnable & (wIsStep3 & wReqOver | rLastStep);
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rReqCnt <=`DL ~wEnable ? 3'd0
|
||||||
|
: wIsStep7 ? 3'd0
|
||||||
|
: wIsStep5 ? rReqCnt + 1'd1
|
||||||
|
: rReqCnt;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rLoopCnt <=`DL ~wEnable ? 3'd0
|
||||||
|
: wIsStep8 ? rLoopCnt + 1'd1
|
||||||
|
: rLoopCnt;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rLockReg <=`DL wEnable & I_LOCK;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rLockCnt <=`DL (~wEnable) ? {LW{1'b0}}
|
||||||
|
: (~wIsStep7&~wIsStep9) ? {LW{1'b0}}
|
||||||
|
: (~rLockReg) ? {LW{1'b0}}
|
||||||
|
: (rLockCnt<LOCK_CNT) ? rLockCnt + 1'd1
|
||||||
|
: rLockCnt;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rLocked <=`DL wLocked;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rWaitCnt <=`DL wIsStep3 ? WAIT_CNT[0+:WW]
|
||||||
|
: ~wIsStep7 ? rWaitCnt
|
||||||
|
: (|rWaitCnt) ? rWaitCnt - 1'd1
|
||||||
|
: {WW{1'b0}};
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
if (~wEnable) rChkFlag <=`DL 8'h00;
|
||||||
|
else if (wIsStep7 & rLocked) rChkFlag[rLoopCnt[2:0]] <=`DL 1'b1;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rChkOkCnt <=`DL ~wEnable ? 4'd0
|
||||||
|
: ~wIsStep7 ? rChkOkCnt
|
||||||
|
: (rWaitCnt==0) ? 4'd0
|
||||||
|
: rLocked ? rChkOkCnt + 1'd1
|
||||||
|
: rChkOkCnt;
|
||||||
|
end
|
||||||
|
|
||||||
|
always @(*) begin
|
||||||
|
if (~wEnable) rInitFsmN =`DL INIT_STEP_1;
|
||||||
|
else case (rInitFsmC) /* synthesis full_case */
|
||||||
|
INIT_STEP_0 : rInitFsmN =`DL INIT_STEP_1;
|
||||||
|
INIT_STEP_1 : rInitFsmN =`DL INIT_STEP_2;
|
||||||
|
INIT_STEP_2 : rInitFsmN =`DL INIT_STEP_3;
|
||||||
|
INIT_STEP_3 : rInitFsmN =`DL wWaitLock ? INIT_STEP_7
|
||||||
|
: wReqOver & rLastStep ? INIT_STEP_9
|
||||||
|
: wReqOver &~rLastStep ? INIT_STEP_1
|
||||||
|
: (rMdAddr >= wReqAddr) ? INIT_STEP_4
|
||||||
|
: INIT_STEP_3;
|
||||||
|
INIT_STEP_4 : rInitFsmN =`DL INIT_STEP_5;
|
||||||
|
INIT_STEP_5 : rInitFsmN =`DL INIT_STEP_6;
|
||||||
|
INIT_STEP_6 : rInitFsmN =`DL INIT_STEP_2;
|
||||||
|
INIT_STEP_7 : rInitFsmN =`DL (rWaitCnt==0) ? INIT_STEP_8
|
||||||
|
: ~rLocked ? INIT_STEP_7
|
||||||
|
: rLastStep ? INIT_STEP_9
|
||||||
|
: INIT_STEP_8;
|
||||||
|
INIT_STEP_8 : rInitFsmN =`DL ~rLastStep ? INIT_STEP_1
|
||||||
|
: rChkVld ? INIT_STEP_1
|
||||||
|
: INIT_STEP_9;
|
||||||
|
INIT_STEP_9 : rInitFsmN =`DL INIT_STEP_9;
|
||||||
|
default : rInitFsmN =`DL INIT_STEP_9;
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
|
||||||
|
always @ (posedge I_MD_CLK) begin
|
||||||
|
rMdOpc <=`DL ~wEnable ? 2'b00
|
||||||
|
: wIsStep7 ? 2'b00
|
||||||
|
: wIsStep5 ? 2'b01
|
||||||
|
: 2'b10;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rMdInc <=`DL wIsStep3 & (~rMdInc ? (rMdAddr<wReqAddr)
|
||||||
|
: (wMdAddrNext < wReqAddr));
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rMdAddr <=`DL ~wEnable ? 8'd0
|
||||||
|
: wIsStep7 ? 8'd0
|
||||||
|
: rMdInc ? wMdAddrNext
|
||||||
|
: rMdAddr;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rMdDOut <=`DL wIsStep5 ? (I_MD_RD_DATA&~wReqMask|wReqData) : rMdDOut;
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rRstOut <=`DL ~wEnable | (~wIsStep7 & ~wIsStep9);
|
||||||
|
//!_____________________________________________________________________________
|
||||||
|
rLockOut <=`DL rLocked & wIsStep9 & rChkVld;
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
/////////////mdrp switch//////////////////////////
|
||||||
|
assign O_LOCK = (PLL_INIT_BYPASS == 1'b1) ? I_LOCK : rLockOut;
|
||||||
|
assign O_RST = (PLL_INIT_BYPASS == 1'b1) ? I_RST : rRstOut;
|
||||||
|
assign O_MD_INC = (PLL_INIT_BYPASS == 1'b1) ? MDAINC : rMdInc;
|
||||||
|
assign O_MD_OPC = (PLL_INIT_BYPASS == 1'b1) ? MDOPC : rMdOpc;
|
||||||
|
assign O_MD_WR_DATA= (PLL_INIT_BYPASS == 1'b1) ? MDWDI : rMdDOut;
|
||||||
|
assign MDRDO = (PLL_INIT_BYPASS == 1'b1) ? I_MD_RD_DATA : 8'b0000_0000;
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
|
||||||
|
endmodule//: PLL_INIT
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
`timescale 1ns / 1ps
|
||||||
|
// =============================================================================
|
||||||
|
// Module: tb_dac_stream
|
||||||
|
// Project Name: DigMic_prj
|
||||||
|
// Description:
|
||||||
|
// Тест тракта на ЦАП: отсчёты 12 500 Гц -> dac_stream -> I2S на выводах ->
|
||||||
|
// разбор слов в тестбенче, как это делает PCM5102A (по фронту BCK, MSB
|
||||||
|
// через один BCK после смены LRCK).
|
||||||
|
//
|
||||||
|
// Каждое слово левого канала сверяется с эталоном двумя способами:
|
||||||
|
// точно -- та же целочисленная интерполяция, что должна быть в ПЛИС,
|
||||||
|
// с тем же моментом отсчёта; ловит ошибки разрядности, знака и сдвига;
|
||||||
|
// приближённо -- интерполяция в вещественных числах; ловит ошибку в
|
||||||
|
// самой формуле доли интервала.
|
||||||
|
// Правый канал должен совпасть с левым. Отдельно меряются периоды SCK,
|
||||||
|
// BCK и LRCK.
|
||||||
|
//
|
||||||
|
// WAVE = 0 -- синус AMP на FREQ; WAVE = 1 -- меандр между крайними кодами
|
||||||
|
// шкалы, самые большие разности соседних отсчётов.
|
||||||
|
//
|
||||||
|
// Усиление: GAIN -- постоянное значение на входе gain. GAIN_STEP > 0 --
|
||||||
|
// каждые GAIN_STEP входных отсчётов gain растёт на 1 по кругу 0..15, то есть
|
||||||
|
// заходит и за GAIN_MAX: там dac_stream обязан ограничить сдвиг. Эталон берёт
|
||||||
|
// то усиление, что действовало в такт взятия каждого отсчёта.
|
||||||
|
// Разобранные отсчёты пишутся в OUT_FILE для анализа спектра.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
module tb_dac_stream;
|
||||||
|
|
||||||
|
parameter integer GAIN = 0;
|
||||||
|
parameter integer GAIN_MAX = 8;
|
||||||
|
parameter integer GAIN_STEP = 0;
|
||||||
|
parameter integer WAVE = 0;
|
||||||
|
parameter real AMP = 0.5; // доля шкалы
|
||||||
|
parameter real FREQ = 1000.0;
|
||||||
|
parameter integer N_WORDS = 2500; // сколько слов ЦАП проверить
|
||||||
|
parameter OUT_FILE = "build/dac_stream_out.txt";
|
||||||
|
|
||||||
|
localparam integer CLK_HZ = 50000000;
|
||||||
|
localparam integer IN_HZ = 12500;
|
||||||
|
localparam integer IN_PERIOD = CLK_HZ / IN_HZ;
|
||||||
|
localparam integer RECIP = (1 << 26) / IN_PERIOD;
|
||||||
|
localparam integer FS_MAX = 8388607;
|
||||||
|
localparam integer FS_MIN = -8388608;
|
||||||
|
|
||||||
|
// От фронта clk, на котором сменился LRCK, назад до такта, чьё состояние
|
||||||
|
// попало в слово: 16 тактов от sample_tick до начала кадра и 3 ступени
|
||||||
|
// конвейера интерполяции
|
||||||
|
localparam integer LAT = 19;
|
||||||
|
|
||||||
|
reg clk = 1'b0;
|
||||||
|
always #10 clk = ~clk;
|
||||||
|
|
||||||
|
integer cyc = 0; // номер текущего фронта clk
|
||||||
|
always @(posedge clk) cyc <= cyc + 1;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Источник отсчётов: строб раз в IN_PERIOD тактов, выставляется по спаду
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
reg [23:0] din = 24'd0;
|
||||||
|
reg din_valid = 1'b0;
|
||||||
|
integer ph = IN_PERIOD - 1234; // произвольная фаза относительно I2S
|
||||||
|
integer k_in = 0;
|
||||||
|
|
||||||
|
function integer gen(input integer k);
|
||||||
|
real v;
|
||||||
|
begin
|
||||||
|
if (WAVE == 0) begin
|
||||||
|
v = AMP * FS_MAX * $sin(2.0 * 3.14159265358979 * FREQ * k / IN_HZ);
|
||||||
|
gen = (v >= 0.0) ? $rtoi(v + 0.5) : -$rtoi(-v + 0.5);
|
||||||
|
end else begin
|
||||||
|
gen = ((k / 7) % 2) ? FS_MIN : FS_MAX;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function integer sat_gain(input integer x, input integer g);
|
||||||
|
reg signed [63:0] w;
|
||||||
|
begin
|
||||||
|
w = x;
|
||||||
|
w = w <<< ((g > GAIN_MAX) ? GAIN_MAX : g);
|
||||||
|
if (w > FS_MAX) sat_gain = FS_MAX;
|
||||||
|
else if (w < FS_MIN) sat_gain = FS_MIN;
|
||||||
|
else sat_gain = w;
|
||||||
|
end
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
reg [3:0] gain = GAIN;
|
||||||
|
|
||||||
|
always @(negedge clk) begin
|
||||||
|
din_valid <= 1'b0;
|
||||||
|
if (ph == IN_PERIOD - 1) begin
|
||||||
|
ph <= 0;
|
||||||
|
din <= gen(k_in);
|
||||||
|
din_valid <= 1'b1;
|
||||||
|
k_in <= k_in + 1;
|
||||||
|
end else begin
|
||||||
|
ph <= ph + 1;
|
||||||
|
// Смена усиления в середине интервала между отсчётами
|
||||||
|
if (GAIN_STEP > 0 && ph == IN_PERIOD / 2 && k_in % GAIN_STEP == 0)
|
||||||
|
gain <= gain + 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// История входа: на каком фронте ПЛИС взяла отсчёт и что получила после усиления
|
||||||
|
integer hist_t [0:8191];
|
||||||
|
integer hist_x [0:8191];
|
||||||
|
integer n_hist = 0;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (din_valid) begin
|
||||||
|
hist_t[n_hist] = cyc;
|
||||||
|
hist_x[n_hist] = sat_gain($signed(din), gain);
|
||||||
|
n_hist = n_hist + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Эталон: что должно быть на выходе по состоянию после фронта s
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Состояние кладётся в e_cur, e_prev, e_cnt: они же нужны для
|
||||||
|
// вещественного эталона и сообщений об ошибке
|
||||||
|
integer e_cur, e_prev, e_cnt;
|
||||||
|
|
||||||
|
function integer ref_exact(input integer s);
|
||||||
|
integer i;
|
||||||
|
reg signed [63:0] d, f, p;
|
||||||
|
begin
|
||||||
|
i = n_hist - 1;
|
||||||
|
while (i >= 0 && hist_t[i] > s)
|
||||||
|
i = i - 1;
|
||||||
|
e_cur = (i >= 0) ? hist_x[i] : 0;
|
||||||
|
e_prev = (i >= 1) ? hist_x[i - 1] : 0;
|
||||||
|
e_cnt = (i >= 0) ? s - hist_t[i] : s;
|
||||||
|
if (e_cnt > IN_PERIOD - 1)
|
||||||
|
e_cnt = IN_PERIOD - 1;
|
||||||
|
|
||||||
|
d = e_cur - e_prev;
|
||||||
|
f = (e_cnt * RECIP) >>> 10;
|
||||||
|
p = (d * f + 32768) >>> 16;
|
||||||
|
ref_exact = e_prev + p;
|
||||||
|
end
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Устройство
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
wire i2s_bck, i2s_lrck, i2s_din, dac_sck;
|
||||||
|
|
||||||
|
dac_stream #(
|
||||||
|
.CLK_FREQ_HZ (CLK_HZ),
|
||||||
|
.IN_RATE_HZ (IN_HZ),
|
||||||
|
.SAMPLE_RATE_HZ (48000),
|
||||||
|
.GAIN_MAX (GAIN_MAX)
|
||||||
|
) dut (
|
||||||
|
.clk (clk),
|
||||||
|
.din (din),
|
||||||
|
.din_valid (din_valid),
|
||||||
|
.gain (gain),
|
||||||
|
.i2s_bck (i2s_bck),
|
||||||
|
.i2s_lrck (i2s_lrck),
|
||||||
|
.i2s_din (i2s_din),
|
||||||
|
.dac_sck (dac_sck)
|
||||||
|
);
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Начало кадра: спад LRCK, в тактах clk
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
reg lrck_d = 1'b0;
|
||||||
|
integer p0 = -1; // фронт clk, на котором LRCK ушёл в 0
|
||||||
|
integer p0_prev = -1;
|
||||||
|
integer errors = 0;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
lrck_d <= i2s_lrck;
|
||||||
|
if (lrck_d && !i2s_lrck) begin
|
||||||
|
p0_prev = p0;
|
||||||
|
p0 = cyc - 1;
|
||||||
|
if (p0_prev >= 0 && p0 - p0_prev != 1024) begin
|
||||||
|
$display("ERROR: LRCK period %0d clk, want 1024", p0 - p0_prev);
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Периоды SCK и BCK
|
||||||
|
integer t_sck = -1, t_bck = -1;
|
||||||
|
always @(posedge dac_sck) begin
|
||||||
|
if (t_sck >= 0 && cyc - t_sck != 4) begin
|
||||||
|
$display("ERROR: SCK period %0d clk, want 4", cyc - t_sck);
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
t_sck = cyc;
|
||||||
|
end
|
||||||
|
always @(posedge i2s_bck) begin
|
||||||
|
if (t_bck >= 0 && cyc - t_bck != 16) begin
|
||||||
|
$display("ERROR: BCK period %0d clk, want 16", cyc - t_bck);
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
t_bck = cyc;
|
||||||
|
end
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Приём I2S как в ЦАП
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
reg lr_b = 1'b0;
|
||||||
|
integer bitn = 99;
|
||||||
|
reg [23:0] sh = 24'd0;
|
||||||
|
integer left = 0;
|
||||||
|
integer n_w = 0;
|
||||||
|
integer n_chk = 0;
|
||||||
|
integer max_dev = 0;
|
||||||
|
integer fd;
|
||||||
|
|
||||||
|
integer got, want, dev;
|
||||||
|
real ideal, tol;
|
||||||
|
|
||||||
|
always @(posedge i2s_bck) begin
|
||||||
|
if (i2s_lrck !== lr_b) begin
|
||||||
|
lr_b = i2s_lrck;
|
||||||
|
bitn = 0; // бит задержки I2S
|
||||||
|
end else if (bitn < 24) begin
|
||||||
|
sh = {sh[22:0], i2s_din};
|
||||||
|
bitn = bitn + 1;
|
||||||
|
if (bitn == 24) begin
|
||||||
|
got = $signed(sh);
|
||||||
|
if (!lr_b) begin
|
||||||
|
left = got;
|
||||||
|
n_w = n_w + 1;
|
||||||
|
// Первые кадры пропускаем: там ещё нет двух отсчётов в истории
|
||||||
|
if (n_hist >= 3) begin
|
||||||
|
want = ref_exact(p0 - LAT);
|
||||||
|
if (got !== want) begin
|
||||||
|
if (errors < 10)
|
||||||
|
$display("ERROR: word %0d at clk %0d: got %0d, want %0d (cnt %0d)",
|
||||||
|
n_w, p0, got, want, e_cnt);
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
ideal = e_prev + (e_cur - e_prev) * (e_cnt * 1.0 / IN_PERIOD);
|
||||||
|
tol = ((e_cur > e_prev) ? e_cur - e_prev : e_prev - e_cur) * 3.0e-5 + 1.0;
|
||||||
|
dev = (got > ideal) ? $rtoi(got - ideal + 0.5) : $rtoi(ideal - got + 0.5);
|
||||||
|
if (dev > max_dev)
|
||||||
|
max_dev = dev;
|
||||||
|
if (dev > tol) begin
|
||||||
|
if (errors < 10)
|
||||||
|
$display("ERROR: word %0d: got %0d, ideal %0f, tol %0f",
|
||||||
|
n_w, got, ideal, tol);
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
n_chk = n_chk + 1;
|
||||||
|
$fdisplay(fd, "%0d %0d", p0, got);
|
||||||
|
end
|
||||||
|
end else if (got !== left) begin
|
||||||
|
$display("ERROR: right %0d != left %0d", got, left);
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
fd = $fopen(OUT_FILE, "w");
|
||||||
|
$display("tb_dac_stream: GAIN=%0d GAIN_MAX=%0d GAIN_STEP=%0d WAVE=%0d AMP=%0f",
|
||||||
|
GAIN, GAIN_MAX, GAIN_STEP, WAVE, AMP);
|
||||||
|
wait (n_chk >= N_WORDS);
|
||||||
|
$fclose(fd);
|
||||||
|
$display("checked %0d words, inputs %0d, max deviation from ideal %0d LSB",
|
||||||
|
n_chk, n_hist, max_dev);
|
||||||
|
if (errors == 0)
|
||||||
|
$display("TEST PASSED");
|
||||||
|
else
|
||||||
|
$display("TEST FAILED: errors = %0d", errors);
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
----------------------------------------------------------------------------------
|
||||||
|
-- Module Name: tb_gain_button
|
||||||
|
-- Project Name: DigMic_prj
|
||||||
|
-- Description:
|
||||||
|
-- Тест кнопки усиления ЦАП. Такт уменьшен до 100 кГц: показ длится секунды,
|
||||||
|
-- а в миллисекундах модуль считает сам, так что поведение то же.
|
||||||
|
--
|
||||||
|
-- Проверяется:
|
||||||
|
-- импульс короче времени дребезга -- не нажатие;
|
||||||
|
-- дребезг на обоих фронтах даёт ровно один шаг, отпускание -- ни одного;
|
||||||
|
-- ступени идут по кругу 1..GAIN_MAX -> 0;
|
||||||
|
-- долгое удержание -- один шаг, без автоповтора;
|
||||||
|
-- показ: N коротких вспышек, для 0 одна длинная, потом светодиод свободен;
|
||||||
|
-- нажатие во время показа начинает показ заново с новым значением.
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
library ieee;
|
||||||
|
use ieee.std_logic_1164.all;
|
||||||
|
use ieee.numeric_std.all;
|
||||||
|
use std.env.all;
|
||||||
|
|
||||||
|
entity tb_gain_button is
|
||||||
|
end entity tb_gain_button;
|
||||||
|
|
||||||
|
architecture sim of tb_gain_button is
|
||||||
|
|
||||||
|
constant CLK_HZ : integer := 100_000;
|
||||||
|
constant CLK_T : time := 1 sec / CLK_HZ;
|
||||||
|
constant GMAX : integer := 3;
|
||||||
|
|
||||||
|
signal clk : std_logic := '0';
|
||||||
|
signal run : boolean := true;
|
||||||
|
signal btn : std_logic := '0';
|
||||||
|
signal gain : unsigned(3 downto 0);
|
||||||
|
signal show : std_logic;
|
||||||
|
signal led : std_logic;
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
clk <= not clk after CLK_T / 2 when run else '0';
|
||||||
|
|
||||||
|
dut : entity work.gain_button(rtl)
|
||||||
|
generic map(
|
||||||
|
SYS_CLK_HZ => CLK_HZ,
|
||||||
|
GAIN_MAX => GMAX,
|
||||||
|
BTN_ACTIVE => '1',
|
||||||
|
DEBOUNCE_MS => 20
|
||||||
|
)
|
||||||
|
port map
|
||||||
|
(
|
||||||
|
clk => clk,
|
||||||
|
btn => btn,
|
||||||
|
gain => gain,
|
||||||
|
show => show,
|
||||||
|
led => led
|
||||||
|
);
|
||||||
|
|
||||||
|
stim_proc : process
|
||||||
|
variable errors : integer := 0;
|
||||||
|
variable want : integer := 0;
|
||||||
|
|
||||||
|
-- Фронт кнопки с дребезгом: четыре отскока по 1-2 мс
|
||||||
|
procedure bounce(level : std_logic) is
|
||||||
|
begin
|
||||||
|
for i in 1 to 4 loop
|
||||||
|
btn <= level;
|
||||||
|
wait for 2 ms;
|
||||||
|
btn <= not level;
|
||||||
|
wait for 1 ms;
|
||||||
|
end loop;
|
||||||
|
btn <= level;
|
||||||
|
end procedure;
|
||||||
|
|
||||||
|
procedure press is
|
||||||
|
begin
|
||||||
|
bounce('1');
|
||||||
|
wait for 100 ms;
|
||||||
|
bounce('0');
|
||||||
|
wait for 50 ms;
|
||||||
|
end procedure;
|
||||||
|
|
||||||
|
procedure check_gain(expect : integer; what : string) is
|
||||||
|
begin
|
||||||
|
if to_integer(gain) /= expect then
|
||||||
|
report what & ": gain " & integer'image(to_integer(gain)) &
|
||||||
|
", want " & integer'image(expect) severity error;
|
||||||
|
errors := errors + 1;
|
||||||
|
end if;
|
||||||
|
end procedure;
|
||||||
|
|
||||||
|
-- Вспышки до конца показа: для 0 одна длинная, иначе expect коротких
|
||||||
|
procedure check_flashes(expect : integer) is
|
||||||
|
variable n : integer := 0;
|
||||||
|
variable t0 : time;
|
||||||
|
variable w : time;
|
||||||
|
variable wn : time;
|
||||||
|
begin
|
||||||
|
if expect = 0 then
|
||||||
|
wn := 1000 ms;
|
||||||
|
else
|
||||||
|
wn := 150 ms;
|
||||||
|
end if;
|
||||||
|
if show /= '1' then
|
||||||
|
report "no display after press" severity error;
|
||||||
|
errors := errors + 1;
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
loop
|
||||||
|
wait until led = '1' or show = '0';
|
||||||
|
exit when show = '0';
|
||||||
|
t0 := now;
|
||||||
|
wait until led = '0';
|
||||||
|
w := now - t0;
|
||||||
|
n := n + 1;
|
||||||
|
if w < wn - 3 ms or w > wn + 3 ms then
|
||||||
|
report "flash " & integer'image(n) & " lasts " & time'image(w) &
|
||||||
|
", want " & time'image(wn) severity error;
|
||||||
|
errors := errors + 1;
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
if (expect = 0 and n /= 1) or (expect > 0 and n /= expect) then
|
||||||
|
report "flashes " & integer'image(n) & " for gain " &
|
||||||
|
integer'image(expect) severity error;
|
||||||
|
errors := errors + 1;
|
||||||
|
else
|
||||||
|
report "gain " & integer'image(expect) & ": " & integer'image(n) &
|
||||||
|
" flash(es) of " & time'image(wn);
|
||||||
|
end if;
|
||||||
|
end procedure;
|
||||||
|
|
||||||
|
begin
|
||||||
|
wait for 50 ms;
|
||||||
|
check_gain(0, "after power-up");
|
||||||
|
assert show = '0' report "display active without a press" severity error;
|
||||||
|
|
||||||
|
-- Короткий импульс -- помеха, не нажатие
|
||||||
|
btn <= '1';
|
||||||
|
wait for 5 ms;
|
||||||
|
btn <= '0';
|
||||||
|
wait for 100 ms;
|
||||||
|
check_gain(0, "after 5 ms glitch");
|
||||||
|
if show /= '0' then
|
||||||
|
report "glitch started a display" severity error;
|
||||||
|
errors := errors + 1;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- По кругу: 1, 2, 3, 0, 1
|
||||||
|
for k in 1 to GMAX + 2 loop
|
||||||
|
press;
|
||||||
|
want := (want + 1) mod (GMAX + 1);
|
||||||
|
check_gain(want, "press " & integer'image(k));
|
||||||
|
check_flashes(want);
|
||||||
|
end loop;
|
||||||
|
|
||||||
|
-- Долгое удержание -- один шаг
|
||||||
|
bounce('1');
|
||||||
|
wait for 2 sec;
|
||||||
|
bounce('0');
|
||||||
|
wait for 50 ms;
|
||||||
|
want := (want + 1) mod (GMAX + 1);
|
||||||
|
check_gain(want, "long hold");
|
||||||
|
if show = '1' then
|
||||||
|
wait until show = '0';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Нажатие посреди показа: показ заново, с новым значением
|
||||||
|
press;
|
||||||
|
want := (want + 1) mod (GMAX + 1);
|
||||||
|
wait for 700 ms;
|
||||||
|
press;
|
||||||
|
want := (want + 1) mod (GMAX + 1);
|
||||||
|
check_gain(want, "press during display");
|
||||||
|
check_flashes(want);
|
||||||
|
|
||||||
|
if errors = 0 then
|
||||||
|
report "TEST PASSED";
|
||||||
|
else
|
||||||
|
report "TEST FAILED: errors = " & integer'image(errors) severity error;
|
||||||
|
end if;
|
||||||
|
run <= false;
|
||||||
|
stop;
|
||||||
|
end process stim_proc;
|
||||||
|
|
||||||
|
end architecture sim;
|
||||||
+37
-10
@@ -14,6 +14,12 @@
|
|||||||
-- Тракт настраивается около 10 мс (запаздывание ФНЧ, заполнение FIFO),
|
-- Тракт настраивается около 10 мс (запаздывание ФНЧ, заполнение FIFO),
|
||||||
-- поэтому в каждом режиме кадры читаются до первого сошедшегося, но не
|
-- поэтому в каждом режиме кадры читаются до первого сошедшегося, но не
|
||||||
-- больше MAX_TRY.
|
-- больше MAX_TRY.
|
||||||
|
--
|
||||||
|
-- Микрофон подключён как на железе, через линию LVDS: задержка в обе
|
||||||
|
-- стороны и удержание уровня, пока микрофон отпускает линию. Задержка
|
||||||
|
-- подобрана так, чтобы смена данных приходилась на ту же фазу MIC_CLK, что
|
||||||
|
-- измерена на плате (между тактами 1 и 2 из 16), -- тогда проверяется ровно
|
||||||
|
-- та точка выборки, что стоит в Top.
|
||||||
----------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------
|
||||||
library ieee;
|
library ieee;
|
||||||
use ieee.std_logic_1164.all;
|
use ieee.std_logic_1164.all;
|
||||||
@@ -38,12 +44,21 @@ architecture sim of tb_top is
|
|||||||
|
|
||||||
constant PAYLOAD_LEN : integer := 2 + 1 + SPF * 3; -- CNT + STATUS + DATA
|
constant PAYLOAD_LEN : integer := 2 + 1 + SPF * 3; -- CNT + STATUS + DATA
|
||||||
|
|
||||||
|
-- Линия: смена данных через 16 + 18 (TDD модели) + 16 = 50 нс после спада
|
||||||
|
-- MIC_CLK -- между тактами 1 и 2. Сами SN65LVDS дают единицы наносекунд,
|
||||||
|
-- остальное -- кабель; для выборки важна только получившаяся фаза
|
||||||
|
constant LINE_FWD : time := 16 ns;
|
||||||
|
constant LINE_BACK : time := 16 ns;
|
||||||
|
|
||||||
|
signal mic_clk_far : std_logic;
|
||||||
|
signal mic_dat_far : std_logic;
|
||||||
|
signal mic_dat_kept : std_logic := '0';
|
||||||
|
|
||||||
signal clk : std_logic := '0';
|
signal clk : std_logic := '0';
|
||||||
signal run : boolean := true;
|
signal run : boolean := true;
|
||||||
|
|
||||||
signal mic_clk : std_logic;
|
signal mic_clk : std_logic;
|
||||||
signal mic_dat : std_logic;
|
signal mic_dat : std_logic;
|
||||||
signal mic_sel : std_logic;
|
|
||||||
signal uart_tx : std_logic;
|
signal uart_tx : std_logic;
|
||||||
signal led : std_logic;
|
signal led : std_logic;
|
||||||
|
|
||||||
@@ -104,12 +119,12 @@ begin
|
|||||||
)
|
)
|
||||||
port map
|
port map
|
||||||
(
|
(
|
||||||
clk => clk,
|
clk => clk,
|
||||||
MIC_DAT => mic_dat,
|
MIC_DAT => mic_dat,
|
||||||
MIC_CLK => mic_clk,
|
MIC_CLK => mic_clk,
|
||||||
MIC_SEL => mic_sel,
|
UART_TX => uart_tx,
|
||||||
UART_TX => uart_tx,
|
BTN_GAIN => '0',
|
||||||
LED => led
|
LED => led
|
||||||
);
|
);
|
||||||
|
|
||||||
mic : entity work.pdm_mic_model(model)
|
mic : entity work.pdm_mic_model(model)
|
||||||
@@ -121,11 +136,24 @@ begin
|
|||||||
)
|
)
|
||||||
port map
|
port map
|
||||||
(
|
(
|
||||||
mic_clk => mic_clk,
|
mic_clk => mic_clk_far,
|
||||||
mode => mode,
|
mode => mode,
|
||||||
mic_dat => mic_dat
|
mic_dat => mic_dat_far
|
||||||
);
|
);
|
||||||
|
|
||||||
|
mic_clk_far <= transport mic_clk after LINE_FWD;
|
||||||
|
|
||||||
|
-- Пока микрофон отпускает вывод, вход драйвера LVDS держит последний
|
||||||
|
-- уровень: на плате смены данных видны только в одной фазе
|
||||||
|
keep_proc : process (mic_dat_far)
|
||||||
|
begin
|
||||||
|
if mic_dat_far = '0' or mic_dat_far = '1' then
|
||||||
|
mic_dat_kept <= mic_dat_far;
|
||||||
|
end if;
|
||||||
|
end process keep_proc;
|
||||||
|
|
||||||
|
mic_dat <= transport mic_dat_kept after LINE_BACK;
|
||||||
|
|
||||||
check_proc : process
|
check_proc : process
|
||||||
variable b : std_logic_vector(7 downto 0);
|
variable b : std_logic_vector(7 downto 0);
|
||||||
variable prev : std_logic_vector(7 downto 0);
|
variable prev : std_logic_vector(7 downto 0);
|
||||||
@@ -144,7 +172,6 @@ begin
|
|||||||
variable errors : integer := 0;
|
variable errors : integer := 0;
|
||||||
begin
|
begin
|
||||||
wait for CLK_T;
|
wait for CLK_T;
|
||||||
assert mic_sel = '0' report "MIC_SEL does not match the sampling phase" severity error;
|
|
||||||
report "start: FIR_DC_CODE = " & integer'image(FIR_DC_CODE);
|
report "start: FIR_DC_CODE = " & integer'image(FIR_DC_CODE);
|
||||||
|
|
||||||
for m in 0 to 3 loop
|
for m in 0 to 3 loop
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import numpy as np
|
||||||
|
import wave
|
||||||
|
import sys
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import filedialog
|
||||||
|
import os
|
||||||
|
|
||||||
|
# ================= НАСТРОЙКИ =================
|
||||||
|
FS = 12500 # Частота дискретизации (должна совпадать с записью)
|
||||||
|
ADC_BITS = 24 # Разрядность исходных данных
|
||||||
|
|
||||||
|
# 📢 КОЭФФИЦИЕНТ УСИЛЕНИЯ (GAIN)
|
||||||
|
# 1.0 = без изменений
|
||||||
|
# 10.0 = усилить в 10 раз (20 дБ)
|
||||||
|
# 100.0 = усилить в 100 раз (40 дБ)
|
||||||
|
GAIN = 5
|
||||||
|
|
||||||
|
# Разрядность WAV. Пишется обычный PCM: float-WAV открывают далеко не все
|
||||||
|
# программы, а 64-битный float, который получался раньше, -- почти никакие.
|
||||||
|
# 16 -- открывается везде; 24 -- полная разрядность записи
|
||||||
|
WAV_BITS = 16
|
||||||
|
|
||||||
|
|
||||||
|
def convert_bin_to_wav(input_filename, output_filename, gain):
|
||||||
|
print(f"Чтение файла: {os.path.basename(input_filename)}...")
|
||||||
|
|
||||||
|
# --- 1. Чтение 24-битного Big Endian файла ---
|
||||||
|
# (Используем ту же логику чтения, что и в анализаторе)
|
||||||
|
raw = np.fromfile(input_filename, dtype=np.uint8)
|
||||||
|
n_samples = len(raw) // 3
|
||||||
|
raw = raw[:n_samples * 3]
|
||||||
|
data = raw.reshape(-1, 3)
|
||||||
|
|
||||||
|
# Сборка 3 байт в int32
|
||||||
|
samples = (data[:, 0].astype(np.int32) << 16) | \
|
||||||
|
(data[:, 1].astype(np.int32) << 8) | \
|
||||||
|
data[:, 2].astype(np.int32)
|
||||||
|
|
||||||
|
# Обработка знака (Two's complement)
|
||||||
|
mask = (samples & 0x800000) != 0
|
||||||
|
samples[mask] -= 0x1000000
|
||||||
|
|
||||||
|
# --- 2. Нормализация (перевод в диапазон -1.0 ... 1.0) ---
|
||||||
|
# Сразу float64: numpy 1.x всё равно приводит к нему при делении на 8388607
|
||||||
|
max_val = 2 ** (ADC_BITS - 1) - 1 # 8,388,607
|
||||||
|
audio_float = samples.astype(np.float64) / max_val
|
||||||
|
|
||||||
|
# Убираем постоянную составляющую (DC Offset), чтобы динамик не "выпирало"
|
||||||
|
audio_float -= np.mean(audio_float)
|
||||||
|
|
||||||
|
# --- 3. Применение усиления ---
|
||||||
|
print(f"Применяем усиление: x{gain}")
|
||||||
|
audio_float *= gain
|
||||||
|
|
||||||
|
# --- 4. Проверка на клиппинг (перегрузку) ---
|
||||||
|
max_amp = np.max(np.abs(audio_float))
|
||||||
|
if max_amp > 1.0:
|
||||||
|
clipped_samples = np.sum(np.abs(audio_float) > 1.0)
|
||||||
|
percent_clipped = (clipped_samples / len(audio_float)) * 100
|
||||||
|
print(f"ВНИМАНИЕ: Перегрузка сигнала! Пик: {max_amp:.2f}")
|
||||||
|
print(f" Будет обрезано {percent_clipped:.2f}% сэмплов (Hard Clipping).")
|
||||||
|
print(" Рекомендуется уменьшить GAIN.")
|
||||||
|
|
||||||
|
# Обрезаем все, что выше 1.0 или ниже -1.0
|
||||||
|
audio_float = np.clip(audio_float, -1.0, 1.0)
|
||||||
|
else:
|
||||||
|
print(f"Сигнал в норме. Пиковый уровень: {max_amp:.2f} (из 1.0)")
|
||||||
|
|
||||||
|
# --- 5. Сохранение WAV (PCM) ---
|
||||||
|
print(f"Сохранение в WAV ({WAV_BITS}-bit PCM, {FS} Гц)...")
|
||||||
|
full = 2 ** (WAV_BITS - 1) - 1
|
||||||
|
pcm = np.round(audio_float * full).astype("<i4")
|
||||||
|
if WAV_BITS == 16:
|
||||||
|
frames = pcm.astype("<i2").tobytes()
|
||||||
|
elif WAV_BITS == 24:
|
||||||
|
# Младшие три байта каждого int32 little-endian
|
||||||
|
frames = pcm.view(np.uint8).reshape(-1, 4)[:, :3].tobytes()
|
||||||
|
else:
|
||||||
|
raise ValueError(f"WAV_BITS = {WAV_BITS}: поддерживаются 16 и 24")
|
||||||
|
with wave.open(output_filename, "wb") as w:
|
||||||
|
w.setnchannels(1)
|
||||||
|
w.setsampwidth(WAV_BITS // 8)
|
||||||
|
w.setframerate(FS)
|
||||||
|
w.writeframes(frames)
|
||||||
|
print(f"Готово! Файл сохранен:\n {output_filename}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Файл можно передать в командной строке, иначе -- выбор через окно
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
input_path = sys.argv[1]
|
||||||
|
else:
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
|
||||||
|
input_path = filedialog.askopenfilename(
|
||||||
|
title="Выберите .bin файл для конвертации",
|
||||||
|
filetypes=[("Binary files", "*.bin")]
|
||||||
|
)
|
||||||
|
|
||||||
|
if input_path:
|
||||||
|
# Формируем имя выходного файла
|
||||||
|
folder = os.path.dirname(input_path)
|
||||||
|
name = os.path.basename(input_path).rsplit('.', 1)[0]
|
||||||
|
output_path = os.path.join(folder, f"{name}_gain{GAIN}.wav")
|
||||||
|
|
||||||
|
try:
|
||||||
|
convert_bin_to_wav(input_path, output_path, GAIN)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при конвертации: {e}")
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Расчёт ФНЧ-дециматора /5 и генерация src/fir_coefs_pkg.vhd.
|
"""Расчёт ФНЧ-дециматора /5 и генерация src/fir_coefs_pkg.vhd.
|
||||||
|
|
||||||
Фильтр стоит после CIC sinc^4 /50 и решает две задачи сразу:
|
Фильтр стоит после CIC sinc^3 /50 и решает две задачи сразу:
|
||||||
- давит всё выше 6250 Гц, куда после децимации на 5 сворачивается
|
- давит всё выше 6250 Гц, куда после децимации на 5 сворачивается
|
||||||
шум сигма-дельта модулятора микрофона (CIC там ничего не подавляет);
|
шум сигма-дельта модулятора микрофона (CIC там ничего не подавляет);
|
||||||
- выравнивает спад CIC в полосе (задаётся желаемая АЧХ 1/H_cic).
|
- выравнивает спад CIC в полосе (задаётся желаемая АЧХ 1/H_cic).
|
||||||
|
|
||||||
Масштаб выбран так, чтобы постоянная '1' на входе PDM давала полную шкалу
|
Масштаб выбран так, чтобы постоянная '1' на входе PDM давала полную шкалу
|
||||||
24 бит: коэффициенты домножены на 2^23 / 50^4 и сумма подогнана точно.
|
24 бит: коэффициенты домножены на 2^23 / 50^3 и сумма подогнана точно.
|
||||||
|
|
||||||
Запуск: py -3 tools/gen_fir_coefs.py
|
Запуск: py -3 tools/gen_fir_coefs.py
|
||||||
"""
|
"""
|
||||||
@@ -21,7 +21,7 @@ from scipy.signal import firls, freqz
|
|||||||
# --- параметры тракта, должны совпадать с Top.vhd ---
|
# --- параметры тракта, должны совпадать с Top.vhd ---
|
||||||
F_PDM = 3_125_000.0 # PDM_CLK = 50 МГц / 16
|
F_PDM = 3_125_000.0 # PDM_CLK = 50 МГц / 16
|
||||||
R_CIC = 50 # децимация CIC
|
R_CIC = 50 # децимация CIC
|
||||||
N_CIC = 4 # порядок CIC
|
N_CIC = 3 # порядок CIC
|
||||||
FS_IN = F_PDM / R_CIC # 62500 Гц на входе FIR
|
FS_IN = F_PDM / R_CIC # 62500 Гц на входе FIR
|
||||||
DECIM = 5 # децимация FIR
|
DECIM = 5 # децимация FIR
|
||||||
FS_OUT = FS_IN / DECIM # 12500 Гц
|
FS_OUT = FS_IN / DECIM # 12500 Гц
|
||||||
@@ -29,7 +29,7 @@ FS_OUT = FS_IN / DECIM # 12500 Гц
|
|||||||
# --- параметры фильтра ---
|
# --- параметры фильтра ---
|
||||||
NTAPS = 221 # нечётное: линейная фаза, симметрия
|
NTAPS = 221 # нечётное: линейная фаза, симметрия
|
||||||
COEF_W = 18 # разрядность коэффициента
|
COEF_W = 18 # разрядность коэффициента
|
||||||
SHIFT = 17 # выход = (сумма произведений) >> SHIFT
|
SHIFT = 13 # выход = (сумма произведений) >> SHIFT
|
||||||
PASS = 5000.0 # граница полосы пропускания
|
PASS = 5000.0 # граница полосы пропускания
|
||||||
STOP = 6250.0 # граница полосы задерживания = FS_OUT/2
|
STOP = 6250.0 # граница полосы задерживания = FS_OUT/2
|
||||||
WSTOP = 300.0 # вес полосы задерживания
|
WSTOP = 300.0 # вес полосы задерживания
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Скрипты ПК для стенда цифрового микрофона.
|
||||||
|
# Установка: python -m pip install -r tools/requirements.txt
|
||||||
|
pyserial>=3.5 # uart_capture, uart_live, uart_audio
|
||||||
|
numpy>=1.24 # все скрипты
|
||||||
|
scipy>=1.10 # gen_fir_coefs, uart_audio
|
||||||
|
matplotlib>=3.7 # uart_live, окно на TkAgg
|
||||||
|
sounddevice>=0.4 # uart_audio, вывод на звуковую карту
|
||||||
@@ -0,0 +1,574 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Прослушивание микрофона: поток UART -> звуковая карта -> джек.
|
||||||
|
|
||||||
|
ЦАП здесь -- сама звуковая карта, внешнего железа не нужно. Кадры разбирает
|
||||||
|
тот же FrameParser, что и запись, отсчёты 24 бит / 12 500 Гц пересчитываются
|
||||||
|
в частоту устройства вывода и уходят на выход.
|
||||||
|
|
||||||
|
Отдельный процесс от uart_live.py не для удобства, а по необходимости:
|
||||||
|
COM-порт держит кто-то один. Либо смотреть, либо слушать, либо -- вообще без
|
||||||
|
порта -- проиграть уже записанный .bin ключом --file.
|
||||||
|
|
||||||
|
Два места, на которых такая программа обычно и ломается:
|
||||||
|
|
||||||
|
* частоты не совпадают. 12 500 Гц отмеряет кварц платы, частоту вывода --
|
||||||
|
кварц звуковой карты; расхождение в десятки ppm за минуту накапливает
|
||||||
|
миллисекунды и рано или поздно опустошает либо переполняет буфер. Поэтому
|
||||||
|
шаг пересчёта не постоянный: его подстраивает регулятор по заполнению
|
||||||
|
буфера, и постоянного отношения частот в программе нет нигде;
|
||||||
|
* у микрофона заметная постоянная составляющая -- она съедает запас по
|
||||||
|
шкале и даёт щелчок при старте, поэтому на входе стоит ФВЧ 20 Гц.
|
||||||
|
|
||||||
|
Полоса та же, что у тракта, 5 кГц: звук телефонного качества -- это норма, а
|
||||||
|
не признак поломки. Слушать лучше в наушниках, динамик рядом с микрофоном
|
||||||
|
даёт самовозбуждение.
|
||||||
|
|
||||||
|
py -3 tools/uart_audio.py --port COM10
|
||||||
|
py -3 tools/uart_audio.py --file live_01.bin --gain 12
|
||||||
|
py -3 tools/uart_audio.py --list
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.signal import butter, lfilter, lfilter_zi
|
||||||
|
from scipy.signal.windows import blackmanharris
|
||||||
|
|
||||||
|
import uart_capture as uc
|
||||||
|
|
||||||
|
try:
|
||||||
|
import sounddevice as sd
|
||||||
|
except Exception as _e: # пакета может не быть -- --selftest и --list живут без него
|
||||||
|
sd = None
|
||||||
|
_SD_ERR = _e
|
||||||
|
|
||||||
|
FS_IN = uc.FS_DEFAULT # 12 500 Гц, задаёт кварц платы
|
||||||
|
FS_CODE = float(uc.FS_CODE) # полная шкала 24 бит
|
||||||
|
HP_HZ = 20.0 # ФВЧ: постоянная составляющая микрофона
|
||||||
|
BUF_MS = 120.0 # запас буфера: перекрывает заминки Windows
|
||||||
|
TRIM_K = 0.05 # П-регулятор шага, постоянная времени ~2.5 с
|
||||||
|
TRIM_MAX = 0.01 # предел подстройки, 1% = 17 центов
|
||||||
|
FILL_TAU = 0.5 # сглаживание оценки заполнения, с
|
||||||
|
|
||||||
|
|
||||||
|
class Resampler:
|
||||||
|
"""Пересчёт частоты с произвольным и меняющимся на ходу шагом.
|
||||||
|
|
||||||
|
Постоянного отношения мало: именно шагом компенсируется расхождение
|
||||||
|
кварцев. Поэтому таблица фаз считается один раз, а дробная позиция чтения
|
||||||
|
двигается тем шагом, который на этот блок назначил регулятор.
|
||||||
|
"""
|
||||||
|
|
||||||
|
TAPS = 32 # окно свёртки, в отсчётах входа
|
||||||
|
PHASES = 2048 # квантование дробной задержки, спуры ниже -80 дБ
|
||||||
|
OFF = TAPS // 2 - 1 # отводов слева от позиции чтения
|
||||||
|
LOOK = TAPS - 1 - OFF # и справа: столько входа всегда лежит впрок
|
||||||
|
BETA = 7.857 # окно Кайзера на 80 дБ
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# Срез на Найквисте входа. Давить надо не полосу 6250...12500 Гц -- её
|
||||||
|
# во входе нет, -- а образы, ближайший из них на 12500-5000 = 7500 Гц.
|
||||||
|
# На переход 5000...7500 Гц 32 отводов хватает с запасом.
|
||||||
|
mu = np.arange(self.PHASES) / self.PHASES
|
||||||
|
d = mu[:, None] + self.OFF - np.arange(self.TAPS)[None, :]
|
||||||
|
arg = np.maximum(0.0, 1.0 - (2.0 * d / self.TAPS) ** 2)
|
||||||
|
bank = np.sinc(d) * (np.i0(self.BETA * np.sqrt(arg)) / np.i0(self.BETA))
|
||||||
|
# Нормировка построчно: иначе усиление гуляет от фазы к фазе, и
|
||||||
|
# медленный дрейф шага слышен как дрожание громкости.
|
||||||
|
self.bank = bank / bank.sum(axis=1, keepdims=True)
|
||||||
|
self.buf = np.zeros(self.TAPS)
|
||||||
|
self.pos = float(self.OFF)
|
||||||
|
|
||||||
|
def feed(self, x):
|
||||||
|
self.buf = np.concatenate((self.buf, x))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pending(self):
|
||||||
|
"""Сколько отсчётов входа ещё не проиграно. Отводы справа не в счёт:
|
||||||
|
они лежат всегда, и по ним конец потока не отличить от середины."""
|
||||||
|
return max(0.0, len(self.buf) - self.pos - self.LOOK)
|
||||||
|
|
||||||
|
def avail(self, step):
|
||||||
|
"""Сколько отсчётов выхода можно выдать из того, что уже лежит."""
|
||||||
|
imax = len(self.buf) - 1 - self.LOOK
|
||||||
|
return max(int((imax - self.pos) / step) + 1, 0)
|
||||||
|
|
||||||
|
def pull(self, n, step):
|
||||||
|
if n <= 0:
|
||||||
|
return np.zeros(0)
|
||||||
|
t = self.pos + step * np.arange(n)
|
||||||
|
i = t.astype(np.int64)
|
||||||
|
p = ((t - i) * self.PHASES).astype(np.int64)
|
||||||
|
idx = i[:, None] - self.OFF + np.arange(self.TAPS)[None, :]
|
||||||
|
y = (self.buf[idx] * self.bank[p]).sum(axis=1)
|
||||||
|
|
||||||
|
self.pos = t[-1] + step
|
||||||
|
drop = int(self.pos) - self.OFF
|
||||||
|
if drop > 0:
|
||||||
|
self.buf = self.buf[drop:]
|
||||||
|
self.pos -= drop
|
||||||
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
class Source(threading.Thread):
|
||||||
|
"""Приём отсчётов: из порта или из записанного .bin. Кладёт в очередь.
|
||||||
|
|
||||||
|
ФВЧ стоит здесь, а не в звуковом обратном вызове: он работает на частоте
|
||||||
|
входа, где считать вчетверо дешевле, и не в потоке реального времени.
|
||||||
|
"""
|
||||||
|
|
||||||
|
daemon = True
|
||||||
|
|
||||||
|
def __init__(self, port=None, baud=921600, path=None, hp=HP_HZ):
|
||||||
|
super().__init__()
|
||||||
|
self.port, self.baud, self.path = port, baud, path
|
||||||
|
self.q = collections.deque()
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.n_queued = 0
|
||||||
|
self.stop_evt = threading.Event()
|
||||||
|
self.parser = uc.FrameParser()
|
||||||
|
self.err = None
|
||||||
|
self.eof = False
|
||||||
|
self.gaps = 0
|
||||||
|
self.hp_zi = None
|
||||||
|
self.hp_b = self.hp_a = None
|
||||||
|
if hp > 0:
|
||||||
|
self.hp_b, self.hp_a = butter(2, hp, "highpass", fs=FS_IN)
|
||||||
|
|
||||||
|
# --- очередь ---
|
||||||
|
|
||||||
|
def put(self, y):
|
||||||
|
if self.hp_b is not None and len(y):
|
||||||
|
if self.hp_zi is None:
|
||||||
|
# Начальное состояние по первому отсчёту: иначе постоянная
|
||||||
|
# составляющая уезжает к нулю ступенькой, и это слышно щелчком
|
||||||
|
self.hp_zi = lfilter_zi(self.hp_b, self.hp_a) * y[0]
|
||||||
|
y, self.hp_zi = lfilter(self.hp_b, self.hp_a, y, zi=self.hp_zi)
|
||||||
|
with self.lock:
|
||||||
|
self.q.append(y)
|
||||||
|
self.n_queued += len(y)
|
||||||
|
|
||||||
|
def take(self, n):
|
||||||
|
"""Не больше n отсчётов; сколько есть, столько и отдаёт."""
|
||||||
|
out, got = [], 0
|
||||||
|
with self.lock:
|
||||||
|
while self.q and got < n:
|
||||||
|
c = self.q[0]
|
||||||
|
if got + len(c) <= n:
|
||||||
|
self.q.popleft()
|
||||||
|
out.append(c)
|
||||||
|
got += len(c)
|
||||||
|
else:
|
||||||
|
k = n - got
|
||||||
|
out.append(c[:k])
|
||||||
|
self.q[0] = c[k:]
|
||||||
|
got = n
|
||||||
|
self.n_queued -= got
|
||||||
|
return np.concatenate(out) if out else np.zeros(0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def queued(self):
|
||||||
|
with self.lock:
|
||||||
|
return self.n_queued
|
||||||
|
|
||||||
|
# --- источники ---
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
self._run_file() if self.path else self._run_port()
|
||||||
|
except Exception as e:
|
||||||
|
self.err = str(e)
|
||||||
|
self.eof = True
|
||||||
|
|
||||||
|
def _run_port(self):
|
||||||
|
ser = uc.open_port(self.port, self.baud)
|
||||||
|
try:
|
||||||
|
while not self.stop_evt.is_set():
|
||||||
|
chunk = ser.read(4096)
|
||||||
|
if not chunk:
|
||||||
|
continue
|
||||||
|
for _cnt, _st, data, gap in self.parser.feed(chunk):
|
||||||
|
if gap:
|
||||||
|
# Как и в записи: дыру добиваем нулями, иначе звук
|
||||||
|
# уедет по времени относительно источника
|
||||||
|
self.gaps += gap
|
||||||
|
self.put(np.zeros(gap * uc.SAMPLES_PER_FRAME))
|
||||||
|
self.put(np.asarray(uc.decode_samples(data), float) / FS_CODE)
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
def _run_file(self):
|
||||||
|
with open(self.path, "rb") as f:
|
||||||
|
raw = f.read()
|
||||||
|
a = np.frombuffer(raw[:len(raw) // 3 * 3], dtype=np.uint8).reshape(-1, 3).astype(np.int32)
|
||||||
|
v = (a[:, 0] << 16) | (a[:, 1] << 8) | a[:, 2]
|
||||||
|
y = np.where(v & 0x800000, v - 0x1000000, v) / FS_CODE
|
||||||
|
|
||||||
|
# Файл отдаётся в темпе реального времени: иначе он влетел бы в буфер
|
||||||
|
# целиком, и проверялся бы не тот режим, что на живом порту
|
||||||
|
block = 250
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
for i in range(0, len(y), block):
|
||||||
|
if self.stop_evt.is_set():
|
||||||
|
return
|
||||||
|
self.put(y[i:i + block])
|
||||||
|
dt = t0 + (i + block) / FS_IN - time.perf_counter()
|
||||||
|
if dt > 0:
|
||||||
|
self.stop_evt.wait(dt)
|
||||||
|
|
||||||
|
|
||||||
|
class Player:
|
||||||
|
"""Звуковой обратный вызов и регулятор шага пересчёта."""
|
||||||
|
|
||||||
|
def __init__(self, src, fs_out, channels, gain_db, target):
|
||||||
|
self.src = src
|
||||||
|
self.rs = Resampler()
|
||||||
|
self.fs_out = fs_out
|
||||||
|
self.nominal = FS_IN / fs_out
|
||||||
|
self.channels = channels
|
||||||
|
self.gain = 10.0 ** (gain_db / 20.0)
|
||||||
|
self.target = target # желаемый запас, отсчётов входа
|
||||||
|
self.fill_avg = target
|
||||||
|
self.fill_min = target
|
||||||
|
self.trim = 0.0
|
||||||
|
self.underruns = 0
|
||||||
|
self.resyncs = 0
|
||||||
|
self.clipped = 0
|
||||||
|
self.late = 0
|
||||||
|
self.peak = 0.0
|
||||||
|
|
||||||
|
def fill(self):
|
||||||
|
return self.src.queued + self.rs.pending
|
||||||
|
|
||||||
|
def callback(self, outdata, frames, _time, status):
|
||||||
|
if status:
|
||||||
|
self.late += 1
|
||||||
|
|
||||||
|
fill = self.fill()
|
||||||
|
self.fill_min = min(self.fill_min, fill)
|
||||||
|
if fill > 3 * self.target:
|
||||||
|
# После долгой заминки выбросить дешевле, чем догонять регулятором:
|
||||||
|
# 1% в секунду разгребал бы полсекунды задержки почти минуту
|
||||||
|
self.resyncs += 1
|
||||||
|
self.src.take(int(fill - self.target))
|
||||||
|
fill = self.fill()
|
||||||
|
self.fill_avg = fill
|
||||||
|
|
||||||
|
# Регулятор ведём по сглаженному заполнению, а не по мгновенному:
|
||||||
|
# порт и планировщик Windows отдают данные рывками в единицы
|
||||||
|
# миллисекунд, и на мгновенном шаг гулял бы на слышимые доли процента,
|
||||||
|
# хотя расхождение кварцев, которое и надо ловить, -- десятки ppm
|
||||||
|
a = min(1.0, frames / (self.fs_out * FILL_TAU))
|
||||||
|
self.fill_avg += a * (fill - self.fill_avg)
|
||||||
|
self.trim = float(np.clip(TRIM_K * (self.fill_avg - self.target) / self.target,
|
||||||
|
-TRIM_MAX, TRIM_MAX))
|
||||||
|
step = self.nominal * (1.0 + self.trim)
|
||||||
|
|
||||||
|
need = int(frames * step) + self.rs.TAPS + 2 - len(self.rs.buf)
|
||||||
|
if need > 0:
|
||||||
|
self.rs.feed(self.src.take(need))
|
||||||
|
|
||||||
|
n = min(frames, self.rs.avail(step))
|
||||||
|
y = self.rs.pull(n, step)
|
||||||
|
if n < frames:
|
||||||
|
self.underruns += frames - n
|
||||||
|
y = np.concatenate((y, np.zeros(frames - n)))
|
||||||
|
|
||||||
|
y = y * self.gain
|
||||||
|
if len(y):
|
||||||
|
self.peak = max(self.peak, float(np.abs(y).max()))
|
||||||
|
n_clip = int(np.count_nonzero(np.abs(y) > 1.0))
|
||||||
|
if n_clip:
|
||||||
|
self.clipped += n_clip
|
||||||
|
y = np.clip(y, -1.0, 1.0)
|
||||||
|
|
||||||
|
outdata[:] = np.repeat(y[:, None], self.channels, axis=1).astype(np.float32)
|
||||||
|
|
||||||
|
|
||||||
|
def pick_device(name):
|
||||||
|
"""Без указания -- то устройство, что выбрано в Windows, но по возможности
|
||||||
|
оно же через WASAPI: там поток идёт на родной частоте устройства и не
|
||||||
|
проходит через ресемплер Windows, а пересчёт у нас свой.
|
||||||
|
|
||||||
|
Просто взять устройство WASAPI по умолчанию нельзя: список WASAPI в
|
||||||
|
PortAudio неполон, и джек может там не значиться вовсе.
|
||||||
|
"""
|
||||||
|
if name is not None:
|
||||||
|
return int(name) if str(name).isdigit() else name
|
||||||
|
dev = sd.default.device[1]
|
||||||
|
try:
|
||||||
|
want = sd.query_devices(dev)["name"][:31] # MME режет имена до 31 знака
|
||||||
|
except Exception:
|
||||||
|
return dev
|
||||||
|
for i, d in enumerate(sd.query_devices()):
|
||||||
|
if (d["max_output_channels"] > 0
|
||||||
|
and "WASAPI" in sd.query_hostapis(d["hostapi"])["name"]
|
||||||
|
and d["name"][:31] == want):
|
||||||
|
return i
|
||||||
|
return dev
|
||||||
|
|
||||||
|
|
||||||
|
def list_all():
|
||||||
|
from serial.tools import list_ports
|
||||||
|
print("порты:")
|
||||||
|
for p in list_ports.comports():
|
||||||
|
print(f" {p.device} {p.description}")
|
||||||
|
if sd is None:
|
||||||
|
print(f"\nустройства вывода: нет пакета sounddevice ({_SD_ERR})")
|
||||||
|
return 0
|
||||||
|
print("\nустройства вывода:")
|
||||||
|
apis = sd.query_hostapis()
|
||||||
|
for i, d in enumerate(sd.query_devices()):
|
||||||
|
if d["max_output_channels"] > 0:
|
||||||
|
print(f" {i:3d} {d['name']} [{apis[d['hostapi']]['name']}] "
|
||||||
|
f"{d['default_samplerate']:.0f} Гц")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def run(args):
|
||||||
|
if sd is None:
|
||||||
|
print(f"нужен пакет sounddevice: py -3 -m pip install sounddevice\n({_SD_ERR})")
|
||||||
|
return 2
|
||||||
|
|
||||||
|
dev = pick_device(args.device)
|
||||||
|
info = sd.query_devices(dev, "output")
|
||||||
|
fs_out = float(args.rate or info["default_samplerate"])
|
||||||
|
channels = min(2, int(info["max_output_channels"]))
|
||||||
|
if fs_out < FS_IN:
|
||||||
|
print(f"частота устройства {fs_out:.0f} Гц ниже входной: будет алиасинг, "
|
||||||
|
f"задайте --rate")
|
||||||
|
|
||||||
|
target = args.buffer / 1000.0 * FS_IN
|
||||||
|
try:
|
||||||
|
latency = float(args.latency)
|
||||||
|
except ValueError:
|
||||||
|
latency = args.latency
|
||||||
|
|
||||||
|
src = Source(args.port, args.baud, args.file, args.hp)
|
||||||
|
src.start()
|
||||||
|
|
||||||
|
print(f"вход : {'файл ' + args.file if args.file else args.port}, "
|
||||||
|
f"{FS_IN:.0f} Гц / 24 бита")
|
||||||
|
print(f"выход : {info['name']}, {fs_out:.0f} Гц, "
|
||||||
|
f"{channels} кан., буфер {args.buffer:.0f} мс")
|
||||||
|
|
||||||
|
# Накопить запас до старта: иначе первые доли секунды идёт тишина, а
|
||||||
|
# регулятор стартует с заведомо пустого буфера и зря разгоняет шаг
|
||||||
|
t0 = time.time()
|
||||||
|
while src.queued < target and not src.err and not src.eof and time.time() - t0 < 5.0:
|
||||||
|
time.sleep(0.02)
|
||||||
|
if src.err:
|
||||||
|
print(f"источник: {src.err}")
|
||||||
|
return 1
|
||||||
|
if src.queued == 0:
|
||||||
|
print("данных нет: проверьте порт, скорость и питание платы")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
pl = Player(src, fs_out, channels, args.gain, target)
|
||||||
|
try:
|
||||||
|
stream = sd.OutputStream(device=dev, samplerate=fs_out, channels=channels,
|
||||||
|
dtype="float32", latency=latency, callback=pl.callback)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"устройство не открылось: {e}\nсписок выходов: --list, выбор: --device")
|
||||||
|
return 1
|
||||||
|
t_start = time.time()
|
||||||
|
try:
|
||||||
|
with stream:
|
||||||
|
while True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
if src.err:
|
||||||
|
print(f"\nисточник: {src.err}")
|
||||||
|
return 1
|
||||||
|
if src.eof and pl.fill() < 2:
|
||||||
|
break
|
||||||
|
peak, pl.peak = pl.peak, 0.0
|
||||||
|
# Минимум за интервал, а не мгновенное: до недобора буфер
|
||||||
|
# доводит именно провал, а его мгновенное чтение не поймает
|
||||||
|
lo, pl.fill_min = pl.fill_min, pl.fill()
|
||||||
|
db = 20.0 * np.log10(max(peak, 1e-9))
|
||||||
|
sys.stdout.write(
|
||||||
|
f"\r{time.time() - t_start:6.1f} с буфер {pl.fill() / FS_IN * 1000:5.1f}"
|
||||||
|
f" мс (мин {lo / FS_IN * 1000:5.1f})"
|
||||||
|
f" шаг {pl.trim * 1e6:+6.0f} ppm пик {db:6.1f} dBFS"
|
||||||
|
f" кадры {src.parser.frames_ok} CRC {src.parser.crc_errors}"
|
||||||
|
f" дыры {src.gaps} недобор {pl.underruns}"
|
||||||
|
f" клип {pl.clipped} сброс {pl.resyncs} опоздания {pl.late} ")
|
||||||
|
sys.stdout.flush()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
src.stop_evt.set()
|
||||||
|
print()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _peak_hz(sp, f):
|
||||||
|
"""Пик спектра с параболической интерполяцией: корзина тут 6 Гц, и без
|
||||||
|
интерполяции числа в отчёте выглядят промахом, хотя промаха нет."""
|
||||||
|
k = int(np.argmax(sp))
|
||||||
|
if 0 < k < len(sp) - 1:
|
||||||
|
a, b, c = np.log(sp[k - 1:k + 2] + 1e-30)
|
||||||
|
return k, float(f[k] + 0.5 * (a - c) / (a - 2 * b + c) * (f[1] - f[0]))
|
||||||
|
return k, float(f[k])
|
||||||
|
|
||||||
|
|
||||||
|
def selftest():
|
||||||
|
"""Полная проверка тракта без порта и без звука."""
|
||||||
|
import struct
|
||||||
|
|
||||||
|
# --- разбор кадров: формат общий с записью ---
|
||||||
|
parser = uc.FrameParser()
|
||||||
|
got = []
|
||||||
|
for k in range(200):
|
||||||
|
s = [int(4_000_000 * np.sin(2 * np.pi * 1000 * (k * 50 + i) / FS_IN))
|
||||||
|
for i in range(uc.SAMPLES_PER_FRAME)]
|
||||||
|
data = b"".join(struct.pack(">i", v & 0xFFFFFF)[1:] for v in s)
|
||||||
|
payload = struct.pack(">HB", k, 0) + data
|
||||||
|
for _c, _st, d, _g in parser.feed(uc.SYNC + payload
|
||||||
|
+ struct.pack(">H", uc.crc16_ccitt(payload))):
|
||||||
|
got.extend(np.asarray(uc.decode_samples(d), float) / FS_CODE)
|
||||||
|
assert parser.frames_ok == 200 and parser.crc_errors == 0
|
||||||
|
x = np.asarray(got)
|
||||||
|
assert len(x) == 200 * uc.SAMPLES_PER_FRAME
|
||||||
|
print(f"кадры : {parser.frames_ok}, отсчётов {len(x)}, "
|
||||||
|
f"размах {x.min():+.3f}...{x.max():+.3f}")
|
||||||
|
|
||||||
|
# --- пересчёт 12500 -> 48000: частота, уровень, паразитные составляющие ---
|
||||||
|
fs_out = 48000.0
|
||||||
|
step = FS_IN / fs_out
|
||||||
|
rs = Resampler()
|
||||||
|
rs.feed(x)
|
||||||
|
y = rs.pull(int(len(x) / step) - 40, step)
|
||||||
|
# Окно Блэкмена-Харриса, а не Ханна: у Ханна боковые лепестки сами по себе
|
||||||
|
# около -60 дБ и меряться будет окно, а не пересчёт
|
||||||
|
z = y[256:256 + 8192]
|
||||||
|
sp = np.abs(np.fft.rfft(z * blackmanharris(len(z))))
|
||||||
|
f = np.fft.rfftfreq(len(z), 1 / fs_out)
|
||||||
|
k, f_pk = _peak_hz(sp, f)
|
||||||
|
assert abs(f_pk - 1000.0) < 1.0, f_pk
|
||||||
|
amp = np.abs(y[256:]).max()
|
||||||
|
assert abs(amp - x.max()) < 0.02 * x.max(), (amp, x.max())
|
||||||
|
spur = sp.copy()
|
||||||
|
spur[max(k - 12, 0):k + 13] = 0.0
|
||||||
|
j = int(np.argmax(spur))
|
||||||
|
db = 20.0 * np.log10(spur[j] / sp[k])
|
||||||
|
assert db < -70.0, (f[j], db)
|
||||||
|
print(f"пересчёт : пик {f_pk:.1f} Гц, уровень {amp:.4f} против "
|
||||||
|
f"{x.max():.4f}, паразитные {db:.1f} дБ на {f[j]:.0f} Гц")
|
||||||
|
|
||||||
|
# --- непрерывность на границах блоков: в звуке разрыв слышен щелчком ---
|
||||||
|
rs2 = Resampler()
|
||||||
|
parts = []
|
||||||
|
pos = 0
|
||||||
|
while pos < len(x):
|
||||||
|
rs2.feed(x[pos:pos + 137])
|
||||||
|
pos += 137
|
||||||
|
parts.append(rs2.pull(min(97, rs2.avail(step)), step))
|
||||||
|
yb = np.concatenate(parts)
|
||||||
|
n = min(len(y), len(yb))
|
||||||
|
# На один шаг фазовой таблицы разойтись они вправе: позиция чтения копится
|
||||||
|
# по-разному, и индекс фазы может отличаться на единицу. Слышимого это не
|
||||||
|
# даёт, поэтому проверяется другое -- что на стыках нет скачка, то есть
|
||||||
|
# приращение выхода не больше, чем у того же сигнала, посчитанного разом.
|
||||||
|
bound = x.max() * 2 * np.pi * 1000 / FS_IN / Resampler.PHASES
|
||||||
|
err = float(np.abs(y[:n] - yb[:n]).max())
|
||||||
|
assert err < 2 * bound, (err, bound)
|
||||||
|
jump = float(np.abs(np.diff(yb[300:n])).max())
|
||||||
|
smooth = float(np.abs(np.diff(y[300:n])).max())
|
||||||
|
assert jump < smooth * 1.05, (jump, smooth)
|
||||||
|
print(f"блоки : {len(parts)} кусков, расхождение {err:.1e} "
|
||||||
|
f"при пороге {2 * bound:.1e}, скачок {jump:.4f} против {smooth:.4f}")
|
||||||
|
|
||||||
|
# --- шаг действительно тянет частоту: этим и компенсируется дрейф ---
|
||||||
|
rs3 = Resampler()
|
||||||
|
rs3.feed(x)
|
||||||
|
y3 = rs3.pull(int(len(x) / (step * 1.01)) - 40, step * 1.01)
|
||||||
|
z3 = y3[256:256 + 8192]
|
||||||
|
sp3 = np.abs(np.fft.rfft(z3 * blackmanharris(len(z3))))
|
||||||
|
_k3, f3 = _peak_hz(sp3, np.fft.rfftfreq(len(z3), 1 / fs_out))
|
||||||
|
assert abs(f3 - 1010.0) < 1.0, f3
|
||||||
|
print(f"подстройка : шаг +1% дал {f3:.1f} Гц вместо 1000")
|
||||||
|
|
||||||
|
# --- регулятор: пустой буфер замедляет чтение, полный ускоряет ---
|
||||||
|
target = 1500.0
|
||||||
|
blk = np.zeros((512, 2), dtype=np.float32)
|
||||||
|
src = Source(hp=0.0)
|
||||||
|
pl = Player(src, fs_out, 2, 0.0, target)
|
||||||
|
pl.callback(blk, 512, None, None)
|
||||||
|
lo = pl.trim
|
||||||
|
assert pl.underruns > 500, "пустой буфер обязан дать недобор, а не тишину молча"
|
||||||
|
|
||||||
|
src.put(np.zeros(3000))
|
||||||
|
pl.callback(blk, 512, None, None)
|
||||||
|
hi = pl.trim
|
||||||
|
assert lo < 0 < hi, (lo, hi)
|
||||||
|
|
||||||
|
# Завал буфера разгребается выбросом, а не подстройкой: 1% в секунду
|
||||||
|
# тянул бы полсекунды задержки почти минуту
|
||||||
|
src.put(np.zeros(int(10 * target)))
|
||||||
|
pl.callback(blk, 512, None, None)
|
||||||
|
assert pl.resyncs == 1, pl.resyncs
|
||||||
|
assert abs(pl.fill() - target) < 0.2 * target, pl.fill()
|
||||||
|
print(f"регулятор : пусто {lo * 1e6:+.0f} ppm, полно {hi * 1e6:+.0f} ppm, "
|
||||||
|
f"недобор {pl.underruns}, сброс {pl.resyncs} до {pl.fill():.0f} "
|
||||||
|
f"из {target:.0f}")
|
||||||
|
|
||||||
|
# --- ФВЧ: постоянная составляющая уходит, синус остаётся ---
|
||||||
|
s2 = Source(hp=HP_HZ)
|
||||||
|
s2.put(0.25 + 0.1 * np.sin(2 * np.pi * 1000 * np.arange(12500) / FS_IN))
|
||||||
|
d = s2.take(12500)
|
||||||
|
assert abs(d[-2500:].mean()) < 1e-3, d[-2500:].mean()
|
||||||
|
assert abs(np.abs(d[-2500:]).max() - 0.1) < 0.005
|
||||||
|
print(f"ФВЧ : постоянная {d[-2500:].mean():+.1e}, "
|
||||||
|
f"размах {np.abs(d[-2500:]).max():.4f} из 0.1")
|
||||||
|
|
||||||
|
if sd is None:
|
||||||
|
print(f"устройство : нет пакета sounddevice ({_SD_ERR})")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
dev = pick_device(None)
|
||||||
|
info = sd.query_devices(dev, "output")
|
||||||
|
with sd.OutputStream(device=dev, dtype="float32",
|
||||||
|
channels=min(2, int(info["max_output_channels"])),
|
||||||
|
samplerate=float(info["default_samplerate"])):
|
||||||
|
pass
|
||||||
|
print(f"устройство : {info['name']}, "
|
||||||
|
f"{info['default_samplerate']:.0f} Гц -- открывается")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"устройство : не открылось ({e})")
|
||||||
|
|
||||||
|
print("\nвсё сошлось")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument("--port", help="например COM10")
|
||||||
|
ap.add_argument("--baud", type=int, default=921600)
|
||||||
|
ap.add_argument("--file", help="проиграть записанный .bin вместо порта")
|
||||||
|
ap.add_argument("--device", help="устройство вывода: номер или часть имени")
|
||||||
|
ap.add_argument("--rate", type=float, help="частота вывода, по умолчанию своя у устройства")
|
||||||
|
ap.add_argument("--gain", type=float, default=0.0, help="усиление, дБ")
|
||||||
|
ap.add_argument("--buffer", type=float, default=BUF_MS, help="запас буфера, мс")
|
||||||
|
ap.add_argument("--latency", default="high", help="запрос к PortAudio: low, high или секунды")
|
||||||
|
ap.add_argument("--hp", type=float, default=HP_HZ, help="срез ФВЧ, Гц; 0 -- выключить")
|
||||||
|
ap.add_argument("--list", action="store_true", help="порты и устройства вывода")
|
||||||
|
ap.add_argument("--selftest", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
if args.list:
|
||||||
|
return list_all()
|
||||||
|
if args.selftest:
|
||||||
|
return selftest()
|
||||||
|
if not args.port and not args.file:
|
||||||
|
ap.error("нужен --port или --file (список портов и устройств: --list)")
|
||||||
|
return run(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user