Files
Gromov_IA 2672f8b8a8 Стенд: ЦАП PCM5102A, кнопка усиления, линия LVDS, UART через BL616
- вторая ветвь тракта: отсчёты ФНЧ -> пересчёт в 48 828 Гц -> I2S на
  PCM5102A (dac_stream.v, i2s_transmitter.v), тест tb_dac_stream.v;
- кнопка S1 (H11): усиление ветви ЦАП ступенями по +6 дБ, показ ступени
  светодиодом (gain_button.vhd), тест tb_gain_button.vhd;
- микрофон через линию SN65LVDS: точка выборки MIC_SMP_CNT = 12 по
  измерению на плате (pdm_rx.vhd), в tb_top модель линии;
- новая распиновка: MIC_CLK K8, MIC_DAT K9, ЦАП G1/G2/L4/L3, UART_TX C3
  на встроенный мост BL616;
- сборка под GW5A-25 ревизии A, настройки PnR (CPU/SSPI/READY как GPIO);
- отладочный выход PLL 80 МГц на E3.
2026-10-02 14:21:06 +03:00

277 lines
9.5 KiB
VHDL

----------------------------------------------------------------------------------
-- Module Name: tb_top
-- Project Name: DigMic_prj
-- Description:
-- Сквозной тест: модель микрофона -> Top -> UART -> разбор кадра в тестбенче.
-- Проверяются формат кадра, CRC, счётчик и коэффициент передачи тракта.
--
-- Три режима дают точно вычислимый ответ:
-- постоянная '1' и '0' на выходе микрофона -- плюс и минус полная шкала,
-- ровно FIR_DC_CODE (сумма коэффициентов на усиление CIC);
-- чередование 1010 -- ровно ноль: частота PDM/2 попадает в нуль АЧХ CIC.
-- Четвёртый режим -- синус, у него проверяется размах.
--
-- Тракт настраивается около 10 мс (запаздывание ФНЧ, заполнение FIFO),
-- поэтому в каждом режиме кадры читаются до первого сошедшегося, но не
-- больше MAX_TRY.
--
-- Микрофон подключён как на железе, через линию LVDS: задержка в обе
-- стороны и удержание уровня, пока микрофон отпускает линию. Задержка
-- подобрана так, чтобы смена данных приходилась на ту же фазу MIC_CLK, что
-- измерена на плате (между тактами 1 и 2 из 16), -- тогда проверяется ровно
-- та точка выборки, что стоит в Top.
----------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.env.all;
use work.fir_coefs_pkg.all;
entity tb_top is
generic (
MAX_TRY : integer := 12 -- сколько кадров ждать настройки тракта в режиме
);
end entity tb_top;
architecture sim of tb_top is
constant CLK_T : time := 20 ns;
constant BAUD : integer := 921600;
constant BIT_T : time := 1 sec / BAUD;
constant SPF : integer := 50; -- отсчётов в кадре
constant SIN_AMP : real := 0.5;
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 run : boolean := true;
signal mic_clk : std_logic;
signal mic_dat : std_logic;
signal uart_tx : std_logic;
signal led : std_logic;
signal mode : integer := 2;
type byte_arr_t is array (natural range <>) of std_logic_vector(7 downto 0);
function crc16_upd(crc : std_logic_vector(15 downto 0);
d : std_logic_vector(7 downto 0))
return std_logic_vector is
variable c : std_logic_vector(15 downto 0) := crc;
variable fb : std_logic;
begin
for i in 7 downto 0 loop
fb := c(15) xor d(i);
c := c(14 downto 0) & '0';
if fb = '1' then
c := c xor x"1021";
end if;
end loop;
return c;
end function;
procedure get_byte(signal rx : in std_logic;
b : out std_logic_vector(7 downto 0)) is
variable v : std_logic_vector(7 downto 0);
begin
wait until rx = '0'; -- фронт старт-бита
wait for BIT_T / 2;
assert rx = '0' report "start bit not confirmed" severity error;
for i in 0 to 7 loop
wait for BIT_T;
v(i) := rx;
end loop;
wait for BIT_T;
assert rx = '1' report "stop bit not at expected position" severity error;
b := v;
end procedure;
function mode_name(m : integer) return string is
begin
case m is
when 0 => return "+full scale";
when 1 => return "-full scale";
when 2 => return "zero";
when others => return "sine";
end case;
end function;
begin
clk <= not clk after CLK_T / 2 when run else '0';
dut : entity work.Top(Behavioral)
generic map(
SYS_CLK_HZ => 50_000_000,
BAUD_RATE => BAUD
)
port map
(
clk => clk,
MIC_DAT => mic_dat,
MIC_CLK => mic_clk,
UART_TX => uart_tx,
BTN_GAIN => '0',
LED => led
);
mic : entity work.pdm_mic_model(model)
generic map(
F_PDM => 3_125_000.0,
SEL_HIGH => '0',
AMP => SIN_AMP,
FREQ => 1000.0
)
port map
(
mic_clk => mic_clk_far,
mode => mode,
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
variable b : std_logic_vector(7 downto 0);
variable prev : std_logic_vector(7 downto 0);
variable pl : byte_arr_t(0 to PAYLOAD_LEN - 1);
variable crc : std_logic_vector(15 downto 0);
variable crc_rx : std_logic_vector(15 downto 0);
variable cnt_rx : integer;
variable cnt_exp : integer := -1;
variable s : integer;
variable smin : integer;
variable smax : integer;
variable ok : boolean;
variable want : integer;
variable tol : integer;
variable tries : integer;
variable errors : integer := 0;
begin
wait for CLK_T;
report "start: FIR_DC_CODE = " & integer'image(FIR_DC_CODE);
for m in 0 to 3 loop
mode <= m;
case m is
when 0 => want := FIR_DC_CODE;
tol := 0;
when 1 => want := - FIR_DC_CODE;
tol := 0;
when 2 => want := 0;
tol := 0;
when others => want := integer(SIN_AMP * real(FIR_DC_CODE));
tol := want / 20; -- 5 %
end case;
ok := false;
tries := 0;
while not ok and tries < MAX_TRY loop
tries := tries + 1;
-- Ищем SYNC A5 5A, как это будет делать приёмник на ПК
prev := (others => '0');
loop
get_byte(uart_tx, b);
exit when prev = x"A5" and b = x"5A";
prev := b;
end loop;
for i in 0 to PAYLOAD_LEN - 1 loop
get_byte(uart_tx, b);
pl(i) := b;
end loop;
get_byte(uart_tx, b);
crc_rx(15 downto 8) := b;
get_byte(uart_tx, b);
crc_rx(7 downto 0) := b;
crc := x"FFFF";
for i in 0 to PAYLOAD_LEN - 1 loop
crc := crc16_upd(crc, pl(i));
end loop;
if crc /= crc_rx then
report "CRC mismatch" severity error;
errors := errors + 1;
end if;
cnt_rx := to_integer(unsigned(std_logic_vector'(pl(0) & pl(1))));
if cnt_exp >= 0 and cnt_rx /= cnt_exp then
report "FRAME_CNT = " & integer'image(cnt_rx) &
", expected " & integer'image(cnt_exp) severity error;
errors := errors + 1;
end if;
cnt_exp := (cnt_rx + 1) mod 65536;
-- STATUS не проверяем отдельно: пока тракт не настроился, скачок на
-- полную шкалу законно даёт выброс ФНЧ и взводит бит насыщения.
-- Кадр с ненулевым STATUS просто не может стать сошедшимся.
smin := integer'high;
smax := integer'low;
for i in 0 to SPF - 1 loop
s := to_integer(signed(std_logic_vector'(
pl(3 + i * 3) & pl(4 + i * 3) & pl(5 + i * 3))));
if s < smin then
smin := s;
end if;
if s > smax then
smax := s;
end if;
end loop;
if m < 3 then
ok := smin = want and smax = want;
else
ok := abs(smax - want) <= tol and abs(smin + want) <= tol;
end if;
ok := ok and pl(2) = x"00";
end loop;
if ok then
report mode_name(m) & ": OK, min " & integer'image(smin) &
", max " & integer'image(smax) & ", frames used " & integer'image(tries);
else
report mode_name(m) & ": FAILED, want " & integer'image(want) &
" +-" & integer'image(tol) & ", got min " & integer'image(smin) &
", max " & integer'image(smax) & ", STATUS " &
integer'image(to_integer(unsigned(pl(2)))) severity error;
errors := errors + 1;
end if;
end loop;
if errors = 0 then
report "TEST PASSED";
else
report "TEST FAILED: errors = " & integer'image(errors) severity error;
end if;
run <= false;
stop;
end process check_proc;
end architecture sim;