ADC Controller (AD7401) — техническая документация
Плата AC7020C (Zynq-7020), 4-канальный измеритель тока на сигма-дельта АЦП AD7401A.
Базовый адрес 0x43C1_0000 (пример).
1. Принцип измерения (аппаратная логика)
1.1 AD7401A — сигма-дельта модулятор
АЦП измеряет дифференциальное напряжение на шунте (ток через резистор) и выдаёт
однобитный PDM-поток (MDAT) — плотность импульсов пропорциональна входному напряжению.
I ──×R_shunt──► V_шунта ──AD7401A──► PDM-поток MDAT (плотность = V_in / V_ref)
| Параметр | Значение |
|---|---|
| Такт MCLK | 20 МГц (F_clk=100 МГц / G_MCLK_DIV=5) |
| Полная шкала | ±320 мВ (AD7401A) |
| Шунт | 0.5 Ом |
1.2 Формирование отсчёта (OSR)
Каждые G_OSR = 256 PDM-битов формируется один отсчёт. Подсчитывается число единиц
ones ∈ [0..256]:
ones = 128 → нулевой ток (50% плотность PDM)
ones > 128 → прямой ток (положительное напряжение)
ones < 128 → обратный ток (отрицательное напряжение)
Период отсчёта:
ΔT_osr = G_OSR / F_mclk = 256 / 20 МГц = 12.8 мкс
1.3 Двухтрактовая архитектура
Batch Engine (периодный, защёлкивается по спаду RUN): - Классификация каждого отсчёта на прямой/обратный. - Накопление заряда за окно RUN. - По спаду RUN → защёлка + аппаратное деление (среднее).
Stream Engine (потоковый, обновляется каждый отсчёт): - Цифровой ФНЧ 1-го порядка (EMA) прямого и обратного тока. - Непрерывный мониторинг (для OCP-защиты и наблюдения в паузах).
MDAT ──► [PDM → ones] ──► (ones-128)
│
├──► Batch: FWD/REV аккумуляторы ──(спад RUN)──► *_ACC_LAT/CNT_LAT ──► Делитель ──► *_AVG
│
└──► Stream: EMA-фильтры (Q16.16) ──► *_EMA
2. Логика работы DSP (подробно)
2.1 Полный тракт обработки
MDAT (бит) ──синхронизация──► PDM-поток
│
└─► [счётчик OSR + счётчик единиц] ──► ones (0..256) каждые 12.8 мкс
│
├─► ones > 128 → FWD_ACC += (ones−128), FWD_CNT++, обновление FWD_EMA
├─► ones < 128 → REV_ACC += (128−ones), REV_CNT++, обновление REV_EMA
└─► COUNT++ (всегда, любой знак)
2.2 Генератор MCLK
Делитель такта на G_MCLK_DIV=5: 100 МГц → 20 МГц. Счётчик 0..4; по достижении
G_MCLK_DIV−1 взводится MCLK, по G_MCLK_DIV−2 формируется sample_tick
(один раз за период). По sample_tick читается бит MDAT.
2.3 Счётчик OSR
pdm_counter считает биты 0..255. На 256-м бите (pdm_counter = G_OSR−1) блок
завершён: ones = число единиц в 256 битах. Это один валидный отсчёт.
2.4 Классификация направления
ones = 128 → ноль (не накапливается)
ones > 128 → прямой ток (FWD)
ones < 128 → обратный ток (REV)
Физический смысл (схема заряд/разряд конденсатора):
- FWD (прямой) — ток идёт от источника питания в конденсатор (заряд).
- REV (обратный) — ток идёт из конденсатора обратно в источник (разряд/рекуперация).
По сумме за окно RUN:
REV_ACC > FWD_ACC→ суммарный ток поступает в источник питания (рекуперация);FWD_ACC > REV_ACC→ суммарный ток идёт из источника в нагрузку (заряд).
Ограничение (важно): ADC меряет средний ток за 12.8 мкс (ones = плотность
PDM), а не мгновенный. Переменная (реактивная) составляющая с периодом заметно
меньше окна усредняется в ноль:
- реактивный ток →
ones ≈ 128→FACC=0,RACC=0, хотя энергия реально передаётся (например, через трансформатор); - направление быстрых переменных токов не выделяется — классифицируется только постоянная (DC) составляющая.
Это ограничение метода, а не ошибка: для реактивной передачи энергии КПД по
E_ИП = U × I_ср × t не вычислить (I_ср = 0).
Резонанс и прямой заряд. В резонансном контуре (импульсный заряд конденсатора
через трансформатор) FACC>0 появляется только на резонансе — когда частота накачки
совпадает с собственной частотой контура.
Механизм: источник (ИП) однонаправленный — он «толкает» ток вперёд при замкнутом ключе.
На резонансе каждый толчок попадает в фазу с колебанием и усиливает прямую полуволну —
колебание перекашивается вперёд (прямой полупериод больше обратного). Этот перекос и есть
постоянная (прямая) составляющая → FACC>0. Вне резонанса толчки попадают не в фазу,
колебание остаётся симметричным (вперёд = назад) → среднее 0 → FACC=0.
Аналогия: резонанс — толкаешь качели в такт (смещение вперёд растёт); вне резонанса — вразнобой (качели дёргаются симметрично).
При этом в обоих случаях конденсатор заряжается: во вторичной цепи диод Шоттки выпрямляет ток, поэтому заряд конденсатора не уменьшается и растёт до порога компаратора. Ноль в ADC относится только к постоянной составляющей первичного тока в шунте, а не к заряду конденсатора.
Фиксация реактивной составляющей. Наличие реактивной (переменной) составляющей фиксируется косвенно — по комбинации регистров:
COUNT > 0— ADC активно сэмплирует (отсчёты идут);FACC = 0иRACC = 0— ни прямого, ни обратного накопления (средний ток нулевой).
Совокупность COUNT>0 + FACC=0 + RACC=0 = реактивный режим (ток есть, но постоянной
составляющей нет). Чтобы отличить его от «тока нет», смотрят на напряжение конденсатора:
если оно растёт при FACC=0 — энергия передаётся реактивно.
Величину реактивной составляющей измерить нельзя. Амплитуда переменного тока
определяет, насколько плотность PDM отклоняется от 50%, но за окно OSR (12.8 мкс) любая
симметричная осцилляция усредняется ровно в 50% (ones=128) независимо от амплитуды:
- 10 мА → плотность 49.2% ↔ 50.8% → среднее 50% →
ones=128; - 100 мА → плотность 42.2% ↔ 57.8% → среднее 50% →
ones=128; - 640 мА (полная шкала) → плотность 0% ↔ 100% → среднее 50% →
ones=128.
То есть усреднение стирает и направление, и амплитуду — ADC индицирует только «среднее = 0». Для измерения величины нужен другой тракт: короткий OSR, пиковый детектор на шунт или быстрый АЦП (pipeline/SAR на 10–20 MSPS).
Главное: ADC измеряет ток, а для задачи нужна энергия. ADC показывает средний (DC) ток, а не энергию:
- средний ток =
(1/T)·∫ I(t) dt— для симметричного колебания равен 0; - энергия =
∫ V(t)·I(t) dt— мгновенная мощность, не равна 0.
Отсюда «парадокс»: ADC видит ноль, хотя конденсатор заряжается. Он меряет не ту
величину — средний ток, а энергию переносит мгновенная мощность V×I.
Аналогия (качели): толкая качели вперёд-назад, рука ходит туда-сюда (среднее положение
= 0), но энергия передаётся, потому что толчок приходится в фазе с движением. Так же
в контуре: ток «качается» (среднее 0), но энергия идёт в моменты импульса, где V×I > 0.
Аналогия (весы): ADC — как весы, которые показывают только равновесие (перекос
FWD − REV), а не вес (амплитуду). Прямой ток — левая чаша, обратный — правая. При
реактивном токе «влево» и «вправо» поровну → весы уравновешены → стрелка на нуле, но вес
каждой чаши (амплитуда колебаний) неизвестен. Энергия переносится именно «весом»
(мгновенной мощностью), а не перекосом.
Поэтому для измерения реальной энергии нужна мгновенная мощность: быстро мерить I(t)
и V(t) одновременно и перемножать — а не полагаться на средний ток ADC.
Это не ошибка ADC, а несовпадение назначений. ADC корректно выполняет свою задачу —
измеряет средний ток (постоянную составляющую). Для задач, где нужен именно ток
(направление, амплитуда DC, заряд по постоянной составляющей), он подходит. Для задач,
где нужно мерить энергию (мгновенную мощность V×I при переменном/реактивном токе),
он не подходит — это другой класс измерений, требующий отдельного тракта: быстрые
замеры I(t) и V(t) одновременно.
2.5 Batch Engine (накопление + защёлка)
Во время RUN (на каждом OSR-отсчёте):
- FWD_ACC += (ones−128), FWD_CNT++ — если прямой;
- REV_ACC += (128−ones), REV_CNT++ — если обратный.
По спаду RUN (run_fell):
- рабочие накопители копируются в защёлкнутые регистры (*_lat);
- запускается делитель, AVG_VALID=0.
По переднему фронту RUN (run_rose):
- рабочие накопители обнуляются (для нового окна);
- делитель сбрасывается.
Обработка
run_rose/run_fellвыполняется независимо от LOCK/START_STOP — защёлка срабатывает даже при активном LOCK.
2.6 Делитель Q8 (восстанавливающее деление)
Вычисляет AVG = ACC × 256 / CNT (Q8) за две фазы по 40 тактов (~82 такта):
- FWD-фаза:
FWD_AVG = FWD_ACC × 256 / FWD_CNT; - REV-фаза:
REV_AVG = REV_ACC × 256 / REV_CNT; - по завершении —
AVG_VALID = 1.
Алгоритм — классическое восстанавливающее деление: на каждом такте остаток
сдвигается влево, подтягивается очередной бит делимого, вычитается делитель
(если умещается) и фиксируется бит частного. CNT=0 → результат 0.
2.7 Stream Engine (EMA)
ФНЧ 1-го порядка, α = 2⁻⁶ = 1/64:
y[k] = y[k-1] + (x[k] − y[k-1]) / 64
x[k] = |ones − 128| (для своего направления; иначе 0)
Внутренняя точность Q16.16, выход Q8 (биты 15:0). Обновляется на каждом OSR-отсчёте, фронтами RUN не сбрасывается.
2.8 Управляющие сигналы (приоритет)
RST_COUNT > LOCK > START_STOP
RST_COUNT=1— сброс COUNT и ERROR (строб, самоочистка).LOCK=1— остановить PDM-приём, отбросить частичный блок (фронты RUN всё равно обрабатываются).START_STOP=0— канал остановлен.
3. Регистровая карта (4 канала × 11 регистров)
Страйд канала = 0x40 (64 байта). Декодирование: канал = addr[7:6], регистр = addr[5:2].
| Канал | Диапазон |
|---|---|
| CH0 | 0x00 – 0x28 |
| CH1 | 0x40 – 0x68 |
| CH2 | 0x80 – 0xA8 |
| CH3 | 0xC0 – 0xE8 |
Регистры одного канала (смещение от базы канала)
| Смещение | Имя | Доступ | Разр. | Формат | Описание |
|---|---|---|---|---|---|
| 0x00 | CTRL | RW | 32 | биты | управление |
| 0x04 | STATUS | RO | 32 | биты | статус |
| 0x08 | COUNT | RO | 32 | u32 | всего OSR-отсчётов с RST_COUNT |
| 0x0C | FWD_ACC | RO | 32 | u32 (кванты) | интеграл прямого заряда за RUN |
| 0x10 | FWD_CNT | RO | 32 | u32 | прямых отсчётов за RUN |
| 0x14 | FWD_AVG | RO | 32 | Q8 | средний прямой ток |
| 0x18 | FWD_EMA | RO | 32 | Q8 (15:0) | EMA прямого тока |
| 0x1C | REV_ACC | RO | 32 | u32 (кванты) | интеграл обратного заряда за RUN |
| 0x20 | REV_CNT | RO | 32 | u32 | обратных отсчётов за RUN |
| 0x24 | REV_AVG | RO | 32 | Q8 | средний обратный ток |
| 0x28 | REV_EMA | RO | 32 | Q8 (15:0) | EMA обратного тока |
Q8 — формат с фиксированной точкой: значение × 256 (младшие 8 бит — дробная часть).
4. Регистр CTRL (0x00, RW)
| Бит | Имя | Тип | Смысл |
|---|---|---|---|
| 8 | LOCK | уровень | 1 = заморозить тракт оцифровки (отбросить частичный PDM-блок, зафиксировать накопители) |
| 9 | RST_COUNT | строб | запись 1 → сброс COUNT и ERROR (самоочищается за 1 такт) |
| 10 | START_STOP | уровень | 1 = канал разрешён; 0 = канал остановлен |
| остальные | — | — | резерв (читаются 0) |
Семантика:
START_STOP=1ИLOCK=0— канал принимает PDM и накапливает.LOCK=1— PDM-приём остановлен (частичный блок отброшен), но фронты RUN продолжают обрабатываться (защёлка/делитель работают).RST_COUNT— разовый сброс (строб), не уровень.- Сброс по
reset_n— все нули → канал остановлен (START_STOP=0).
5. Регистр STATUS (0x04, RO)
| Бит | Имя | Смысл |
|---|---|---|
| 0 | READY | 1 = был хотя бы один валидный отсчёт с момента сброса |
| 1 | RUN_STATE | текущий синхронизированный уровень RUN (1 = генератор активен) |
| 2 | ERROR | 1 = переполнение COUNT (0xFFFFFFFF), сброс по RST_COUNT |
| 3 | AVG_VALID | 1 = делитель завершил, регистры ACC/CNT/AVG консистентны (защёлкнуты) |
Протокол чтения (контракт):
- ARM ждёт
AVG_VALID=1(завершение окна RUN). - Читает
FWD_ACC/FWD_CNT/FWD_AVGиREV_ACC/REV_CNT/REV_AVG— они защёлкнуты и согласованы. AVG_VALIDсбрасывается по переднему фронту следующего RUN.
6. Математика для ARM
5.1 Константы
| Константа | Значение |
|---|---|
| F_MCLK | 20 МГц |
| OSR | 256 бит/отсчёт |
| DT_OSR | 12.8 мкс (256 / 20 МГц) |
| V_REF | 320 мВ (полная шкала) |
| R_SHUNT | 0.5 Ом |
| мА на квант | 5.0 (2.5 мВ / 0.5 Ом) |
5.2 Ток (из AVG или EMA, формат Q8)
I_fwd_mA = FWD_AVG / 256 × 5
Целочисленно (без float):
ones = FWD_AVG >> 8 // средние кванты (0..128)
I_mA = (ones - 128) × 5 // знаковый ток, мА
Это теоретическая формула (шунт ровно 0.5 Ом, офсет 0). Рабочая откалиброванная формула — в разделе 5.8.
5.3 Средний ток (деление аппаратное, уже в регистре)
Аппаратный делитель уже считает:
FWD_AVG = FWD_ACC × 256 / FWD_CNT (Q8; = 0 при FWD_CNT=0)
REV_AVG = REV_ACC × 256 / REV_CNT
ARM не обязан делить — *_AVG уже готовое среднее (Q8). Для контроля доступно сырое деление:
fwd_avg = FWD_ACC / FWD_CNT // кванты (если CNT > 0)
5.4 Заряд (Кулоны)
Квант заряда:
Q_quant = (V_REF/128) / R_SHUNT × ΔT_osr
= 2.5 мВ / 0.5 Ом × 12.8 мкс
= 5 мА × 12.8 мкс
= 64 нКл
Q_fwd_C = FWD_ACC × 64e-9 // прямой заряд, Кл
Q_rev_C = REV_ACC × 64e-9 // обратный заряд, Кл
5.5 Время (по счётчикам)
t_fwd_s = FWD_CNT × 12.8 мкс
t_rev_s = REV_CNT × 12.8 мкс
t_total_s = COUNT × 12.8 мкс
Аналитика (DCM/разрывный режим): если FWD_CNT + REV_CNT < COUNT за окно —
разница = длительность паузы с нулевым током.
5.6 EMA (Stream Engine, реализация в ПЛИС)
Фильтр 1-го порядка, α = 2⁻⁶ = 1/64:
y[k] = y[k-1] + (x[k] − y[k-1]) / 64
x[k] = |ones − 128| (для своего направления; иначе 0)
Внутренняя точность Q16.16, выход *_EMA — Q8 (биты 15:0). Для ARM это
мгновенный сглаженный ток:
I_ema_fwd_mA = (FWD_EMA & 0xFFFF) / 256 × 5
5.7 Итоговый знаковый ток
I_net_mA = (FWD_AVG − REV_AVG) / 256 × 5
I_net_ema_mA = ((FWD_EMA & 0xFFFF) − (REV_EMA & 0xFFFF)) / 256 × 5
5.8 Калибровка (2026-10-10)
Источник ODP6033 в режиме CC (нагрузка ~47 Ом, уставка 50 В). Калибровка по серии 51–301 мА, контроль по 25–275 мА (независимая самопроверка).
Формула пересчёта FWD_AVG (Q8) → ток (мкА), целочисленно:
int64_t t = (int64_t)FWD_AVG * 1287443 + 32768; // × 19.6448, с округлением
int32_t I_uA = (int32_t)((t >> 16) - 548); // вычесть офсет 548 мкА
if (I_uA < 0) I_uA = 0; // ограничение снизу нулём
Смысл коэффициентов:
- наклон 5.029 мА/квант (идеал 5.0, фактический шунт/шкала +0.58%);
- офсет 548 мкА (вычитается до ограничения снизу).
Точность: максимальное расхождение на контрольных точках 0.126 мА.
| Лимит, мА | I факт, мА | FWD_AVG (Q8) | I калибр., мА | Ошибка, мА |
|---|---|---|---|---|
| 25 | 26 | 1347 | 25.914 | −0.086 |
| 50 | 51 | 2621 | 50.941 | −0.059 |
| 75 | 76 | 3894 | 75.949 | −0.051 |
| 100 | 101 | 5167 | 100.957 | −0.043 |
| 125 | 126 | 6444 | 126.043 | +0.043 |
| 150 | 151 | 7715 | 151.012 | +0.012 |
| 175 | 176 | 8986 | 175.980 | −0.020 |
| 200 | 201 | 10263 | 201.067 | +0.067 |
| 225 | 226 | 11532 | 225.996 | −0.004 |
| 250 | 251 | 12802 | 250.945 | −0.055 |
| 275 | 276 | 14071 | 275.874 | −0.126 |
| 300 | 301 | 15346 | 300.921 | −0.079 |
Обратный ток (REV), 2026-10-10
Отдельная серия с обратной полярностью (25–300 мА, шаг 25 мА). Регрессия:
I_rev_мА = 0.01951758695 × REV_AVG + 3.2187987
Формула пересчёта REV_AVG (Q8) → величина обратного тока (мкА):
int64_t t = (int64_t)REV_AVG * 1279105 + 32768; // × 19.5176, с округлением
int32_t I_uA = (int32_t)((t >> 16) + 3219); // прибавить офсет 3219 мкА
if (I_uA < 0) I_uA = 0; // ограничение снизу нулём
Формула даёт положительную величину обратного тока; знак определяется REV-трактом. Максимальное расхождение на контрольных точках 0.583 мА.
7. Жизненный цикл окна RUN
RUN=1 (генератор) → накопление FWD/REV, COUNT++, READY=1, EMA обновляется
│
└── RUN=0 (спад) → защёлка ACC/CNT → делитель (82 такта) → AVG_VALID=1
│
└── RUN=1 (следующий фронт) → сброс накопителей, AVG_VALID=0
RUNприходит от timing_controller (не от ARM).- Во время непрерывного RUN: ACC/CNT/AVG заморожены (результат прошлого окна), живое — COUNT и EMA.
----------------------------------------------------------------------------------
-- File : ad7401_channel.vhd
-- Project : AC7020C / Zynq-7020
-- Device : AD7401
--
-- Один канал сигма-дельта АЦП AD7401 с двухтрактовой обработкой.
--
-- Тракт 1 (Batch Engine): накопление прямого (FWD) и обратного (REV) заряда
-- за окно RUN, защёлкивание по спаду RUN и аппаратное деление (Q8).
-- Тракт 2 (Stream Engine): экспоненциальное скользящее среднее (EMA, Q16.16)
-- прямого/обратного тока, обновляется на каждом OSR-отсчёте.
--
-- RUN : от timing_controller (не от ARM).
-- LOCK : ARM -> PL. 1 = заморозить тракт, отбросить частичный PDM-блок.
-- RST_COUNT : ARM -> PL. 1 = сбросить COUNT и ERROR.
-- START_STOP : ARM -> PL. 1 = канал разрешён, 0 = остановлен.
--
-- Регистры (PL -> ARM):
-- READY : хотя бы один валидный OSR-отсчёт с момента сброса.
-- RUN_STATE : синхронизированное состояние входа RUN.
-- ERROR : переполнение COUNT (0xFFFF_FFFF).
-- AVG_VALID : делитель завершил FWD_AVG/REV_AVG, регистры периода консистентны.
-- COUNT : общее число валидных OSR-отсчётов (сброс по RST_COUNT).
-- FWD_ACC / FWD_CNT / FWD_AVG / FWD_EMA
-- REV_ACC / REV_CNT / REV_AVG / REV_EMA
----------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity ad7401_channel is
generic (
-- F_MCLK = F_CLK / G_MCLK_DIV (100 MHz / 5 = 20 MHz)
G_MCLK_DIV : positive := 5;
-- Количество PDM-битов на один готовый отсчёт
G_OSR : positive := 256;
-- Коэффициент EMA: alpha = 2^-G_EMA_SHIFT
G_EMA_SHIFT : natural := 6;
-- Число дробных бит результата AVG (Q8)
C_AVG_FRAC : natural := 8
);
port (
-- Системные сигналы
clk : in std_logic;
reset_n : in std_logic;
-- Окно измерения от timing_controller
run : in std_logic;
-- ARM -> PL
lock : in std_logic;
rst_count : in std_logic;
start_stop : in std_logic;
-- AD7401
mdat : in std_logic;
mclkin : out std_logic;
-- PL -> ARM
ready : out std_logic;
run_state : out std_logic;
error : out std_logic;
avg_valid : out std_logic;
count : out std_logic_vector(31 downto 0);
fwd_acc : out std_logic_vector(31 downto 0);
fwd_cnt : out std_logic_vector(31 downto 0);
fwd_avg : out std_logic_vector(31 downto 0);
fwd_ema : out std_logic_vector(31 downto 0);
rev_acc : out std_logic_vector(31 downto 0);
rev_cnt : out std_logic_vector(31 downto 0);
rev_avg : out std_logic_vector(31 downto 0);
rev_ema : out std_logic_vector(31 downto 0)
);
end entity ad7401_channel;
architecture Behavioral of ad7401_channel is
function clog2(n : positive) return natural is
variable v : natural := n - 1;
variable r : natural := 0;
begin
while v > 0 loop
v := v / 2;
r := r + 1;
end loop;
if r = 0 then
return 1;
else
return r;
end if;
end function;
constant C_MCLK_DIV_WIDTH : natural := clog2(G_MCLK_DIV);
constant C_OSR_WIDTH : natural := clog2(G_OSR);
constant MAX_COUNT : unsigned(31 downto 0) := (others => '1');
-- MCLK
signal mclk_counter : unsigned(C_MCLK_DIV_WIDTH-1 downto 0) := (others => '0');
signal mclk_reg : std_logic := '0';
signal sample_tick : std_logic := '0';
-- MDAT synchronizer
signal mdat_meta : std_logic := '0';
signal mdat_sync : std_logic := '0';
-- RUN synchronizer
signal run_meta : std_logic := '0';
signal run_sync : std_logic := '0';
signal run_prev : std_logic := '0';
signal run_rose : std_logic;
signal run_fell : std_logic;
-- PDM accumulation
signal pdm_counter : unsigned(C_OSR_WIDTH-1 downto 0) := (others => '0');
signal ones_counter : unsigned(C_OSR_WIDTH downto 0) := (others => '0');
-- Cumulative count / flags
signal count_reg : unsigned(31 downto 0) := (others => '0');
signal ready_reg : std_logic := '0';
signal error_reg : std_logic := '0';
-- Batch working accumulators (current RUN window)
signal fwd_acc_reg : unsigned(31 downto 0) := (others => '0');
signal fwd_cnt_reg : unsigned(31 downto 0) := (others => '0');
signal rev_acc_reg : unsigned(31 downto 0) := (others => '0');
signal rev_cnt_reg : unsigned(31 downto 0) := (others => '0');
-- Batch latched registers (frozen at RUN falling edge)
signal fwd_acc_lat : unsigned(31 downto 0) := (others => '0');
signal fwd_cnt_lat : unsigned(31 downto 0) := (others => '0');
signal rev_acc_lat : unsigned(31 downto 0) := (others => '0');
signal rev_cnt_lat : unsigned(31 downto 0) := (others => '0');
-- Divider outputs (Q8)
signal fwd_avg_reg : unsigned(31 downto 0) := (others => '0');
signal rev_avg_reg : unsigned(31 downto 0) := (others => '0');
signal avg_valid_reg : std_logic := '0';
-- EMA accumulators (Q16.16, signed)
signal ema_fwd_acc : signed(31 downto 0) := (others => '0');
signal ema_rev_acc : signed(31 downto 0) := (others => '0');
-- Divider FSM
type div_state_t is (DIV_IDLE, DIV_FWD, DIV_REV, DIV_DONE);
signal div_state : div_state_t := DIV_IDLE;
signal div_cnt : integer range 0 to 40 := 0;
signal div_rem : unsigned(31 downto 0) := (others => '0');
signal div_q : unsigned(39 downto 0) := (others => '0');
signal div_num : unsigned(39 downto 0) := (others => '0');
signal div_den : unsigned(31 downto 0) := (others => '0');
begin
--------------------------------------------------------------------------
-- Outputs
--------------------------------------------------------------------------
mclkin <= mclk_reg;
ready <= ready_reg;
run_state <= run_sync;
error <= error_reg;
avg_valid <= avg_valid_reg;
count <= std_logic_vector(count_reg);
fwd_acc <= std_logic_vector(fwd_acc_lat);
fwd_cnt <= std_logic_vector(fwd_cnt_lat);
fwd_avg <= std_logic_vector(fwd_avg_reg);
rev_acc <= std_logic_vector(rev_acc_lat);
rev_cnt <= std_logic_vector(rev_cnt_lat);
rev_avg <= std_logic_vector(rev_avg_reg);
fwd_ema <= x"0000" & std_logic_vector(ema_fwd_acc(23 downto 8));
rev_ema <= x"0000" & std_logic_vector(ema_rev_acc(23 downto 8));
run_rose <= '1' when run_prev = '0' and run_sync = '1' else '0';
run_fell <= '1' when run_prev = '1' and run_sync = '0' else '0';
--------------------------------------------------------------------------
-- Проверка параметров
--------------------------------------------------------------------------
assert G_MCLK_DIV >= 4
report "G_MCLK_DIV must be >= 4"
severity failure;
assert G_OSR = 256
report "Current scaling requires G_OSR = 256"
severity failure;
--------------------------------------------------------------------------
-- Синхронизация внешних сигналов MDAT и RUN
--------------------------------------------------------------------------
process(clk)
begin
if rising_edge(clk) then
if reset_n = '0' then
mdat_meta <= '0';
mdat_sync <= '0';
run_meta <= '0';
run_sync <= '0';
run_prev <= '0';
else
mdat_meta <= mdat;
mdat_sync <= mdat_meta;
run_meta <= run;
run_sync <= run_meta;
run_prev <= run_sync;
end if;
end if;
end process;
--------------------------------------------------------------------------
-- Генератор MCLK (делитель G_MCLK_DIV), sample_tick один раз за период
--------------------------------------------------------------------------
process(clk)
variable v_counter : natural;
begin
if rising_edge(clk) then
if reset_n = '0' then
mclk_counter <= (others => '0');
mclk_reg <= '0';
sample_tick <= '0';
else
sample_tick <= '0';
v_counter := to_integer(mclk_counter);
if v_counter = G_MCLK_DIV - 1 then
mclk_counter <= (others => '0');
mclk_reg <= '1';
elsif v_counter = 1 then
mclk_counter <= mclk_counter + 1;
mclk_reg <= '0';
elsif v_counter = G_MCLK_DIV - 2 then
mclk_counter <= mclk_counter + 1;
sample_tick <= '1';
else
mclk_counter <= mclk_counter + 1;
end if;
end if;
end if;
end process;
--------------------------------------------------------------------------
-- Основной DSP: Batch + EMA + Divider (один процесс, нет multi-driver)
--------------------------------------------------------------------------
process(clk)
variable v_ones : unsigned(C_OSR_WIDTH downto 0);
variable in_q16 : signed(31 downto 0);
variable diff_q16 : signed(31 downto 0);
variable tent_rem : unsigned(32 downto 0);
begin
if rising_edge(clk) then
if reset_n = '0' then
pdm_counter <= (others => '0');
ones_counter <= (others => '0');
count_reg <= (others => '0');
ready_reg <= '0';
error_reg <= '0';
fwd_acc_reg <= (others => '0');
fwd_cnt_reg <= (others => '0');
rev_acc_reg <= (others => '0');
rev_cnt_reg <= (others => '0');
fwd_acc_lat <= (others => '0');
fwd_cnt_lat <= (others => '0');
rev_acc_lat <= (others => '0');
rev_cnt_lat <= (others => '0');
fwd_avg_reg <= (others => '0');
rev_avg_reg <= (others => '0');
avg_valid_reg <= '0';
ema_fwd_acc <= (others => '0');
ema_rev_acc <= (others => '0');
div_state <= DIV_IDLE;
div_cnt <= 0;
div_rem <= (others => '0');
div_q <= (others => '0');
div_num <= (others => '0');
div_den <= (others => '0');
else
------------------------------------------------------------------
-- RST_COUNT: сброс COUNT и ERROR (приоритет над остальным)
------------------------------------------------------------------
if rst_count = '1' then
count_reg <= (others => '0');
error_reg <= '0';
end if;
------------------------------------------------------------------
-- Передний фронт RUN: обнулить рабочие накопители, сбросить делитель
-- (обрабатывается независимо от LOCK / START_STOP)
------------------------------------------------------------------
if run_rose = '1' then
fwd_acc_reg <= (others => '0');
fwd_cnt_reg <= (others => '0');
rev_acc_reg <= (others => '0');
rev_cnt_reg <= (others => '0');
div_state <= DIV_IDLE;
avg_valid_reg <= '0';
end if;
------------------------------------------------------------------
-- Спад RUN: защёлкнуть накопители, запустить делитель
-- (обрабатывается независимо от LOCK / START_STOP)
------------------------------------------------------------------
if run_fell = '1' then
fwd_acc_lat <= fwd_acc_reg;
fwd_cnt_lat <= fwd_cnt_reg;
rev_acc_lat <= rev_acc_reg;
rev_cnt_lat <= rev_cnt_reg;
div_state <= DIV_FWD;
div_cnt <= 0;
div_rem <= (others => '0');
div_q <= (others => '0');
div_num <= shift_left(resize(fwd_acc_reg, 40), 8);
div_den <= fwd_cnt_reg;
avg_valid_reg <= '0';
end if;
------------------------------------------------------------------
-- Приём PDM (зависит от LOCK / START_STOP)
------------------------------------------------------------------
if lock = '1' or start_stop = '0' then
pdm_counter <= (others => '0');
ones_counter <= (others => '0');
elsif run_sync = '1' then
if sample_tick = '1' then
v_ones := ones_counter;
if mdat_sync = '1' then
v_ones := ones_counter + 1;
end if;
if to_integer(pdm_counter) = G_OSR - 1 then
--------------------------------------------------------
-- Полный OSR-блок: новый валидный отсчёт
--------------------------------------------------------
if v_ones > 128 then
fwd_acc_reg <= fwd_acc_reg + resize(v_ones - 128, 32);
fwd_cnt_reg <= fwd_cnt_reg + 1;
elsif v_ones < 128 then
rev_acc_reg <= rev_acc_reg + resize(128 - v_ones, 32);
rev_cnt_reg <= rev_cnt_reg + 1;
end if;
if count_reg = MAX_COUNT then
error_reg <= '1';
else
count_reg <= count_reg + 1;
end if;
ready_reg <= '1';
--------------------------------------------------------
-- EMA FWD
--------------------------------------------------------
if v_ones > 128 then
in_q16 := shift_left(to_signed(to_integer(v_ones) - 128, 32), 16);
else
in_q16 := (others => '0');
end if;
diff_q16 := in_q16 - ema_fwd_acc;
ema_fwd_acc <= ema_fwd_acc + shift_right(diff_q16, G_EMA_SHIFT);
--------------------------------------------------------
-- EMA REV
--------------------------------------------------------
if v_ones < 128 then
in_q16 := shift_left(to_signed(128 - to_integer(v_ones), 32), 16);
else
in_q16 := (others => '0');
end if;
diff_q16 := in_q16 - ema_rev_acc;
ema_rev_acc <= ema_rev_acc + shift_right(diff_q16, G_EMA_SHIFT);
pdm_counter <= (others => '0');
ones_counter <= (others => '0');
else
pdm_counter <= pdm_counter + 1;
ones_counter <= v_ones;
end if;
end if;
end if;
------------------------------------------------------------------
-- Делитель (восстанавливающий, Q8): 2 фазы по 40 тактов
------------------------------------------------------------------
case div_state is
when DIV_IDLE =>
null;
when DIV_FWD =>
if div_den = 0 then
fwd_avg_reg <= (others => '0');
div_state <= DIV_REV;
div_cnt <= 0;
div_rem <= (others => '0');
div_q <= (others => '0');
div_num <= shift_left(resize(rev_acc_lat, 40), 8);
div_den <= rev_cnt_lat;
elsif div_cnt = 40 then
fwd_avg_reg <= div_q(31 downto 0);
div_state <= DIV_REV;
div_cnt <= 0;
div_rem <= (others => '0');
div_q <= (others => '0');
div_num <= shift_left(resize(rev_acc_lat, 40), 8);
div_den <= rev_cnt_lat;
else
-- Восстанавливающее деление: частное = (ACC << 8) / CNT (Q8).
-- Такт: сдвиг остатка влево + бит делимого, сравнение с делителем,
-- вычитание и фиксация бита частного. Через 40 тактов div_q(31:0) = результат.
tent_rem := div_rem & div_num(39);
if tent_rem >= resize(div_den, 33) then
div_rem <= resize(tent_rem - resize(div_den, 33), 32);
div_q <= div_q(38 downto 0) & '1';
else
div_rem <= tent_rem(31 downto 0);
div_q <= div_q(38 downto 0) & '0';
end if;
div_num <= div_num(38 downto 0) & '0';
div_cnt <= div_cnt + 1;
end if;
when DIV_REV =>
if div_den = 0 then
rev_avg_reg <= (others => '0');
div_state <= DIV_DONE;
avg_valid_reg <= '1';
elsif div_cnt = 40 then
rev_avg_reg <= div_q(31 downto 0);
div_state <= DIV_DONE;
avg_valid_reg <= '1';
else
-- Восстанавливающее деление: частное = (ACC << 8) / CNT (Q8).
-- Такт: сдвиг остатка влево + бит делимого, сравнение с делителем,
-- вычитание и фиксация бита частного. Через 40 тактов div_q(31:0) = результат.
tent_rem := div_rem & div_num(39);
if tent_rem >= resize(div_den, 33) then
div_rem <= resize(tent_rem - resize(div_den, 33), 32);
div_q <= div_q(38 downto 0) & '1';
else
div_rem <= tent_rem(31 downto 0);
div_q <= div_q(38 downto 0) & '0';
end if;
div_num <= div_num(38 downto 0) & '0';
div_cnt <= div_cnt + 1;
end if;
when DIV_DONE =>
null;
end case;
end if;
end if;
end process;
end architecture Behavioral;