diff --git a/src/fir_decim.vhd b/src/fir_decim.vhd index 5e6971b..303059a 100644 --- a/src/fir_decim.vhd +++ b/src/fir_decim.vhd @@ -47,7 +47,9 @@ end entity fir_decim; architecture rtl of fir_decim is type buf_t is array (0 to 2 ** BUF_AW - 1) of signed(DIN_W - 1 downto 0); - signal buf : buf_t; + -- Начальное значение, а не сброс: блочная память его поддерживает, и первые + -- свёртки не тянут неопределённость через весь тракт + signal buf : buf_t := (others => (others => '0')); signal wr_ptr : unsigned(BUF_AW - 1 downto 0) := (others => '0'); signal base : unsigned(BUF_AW - 1 downto 0) := (others => '0'); diff --git a/tb/pdm_mic_model.vhd b/tb/pdm_mic_model.vhd new file mode 100644 index 0000000..d35081f --- /dev/null +++ b/tb/pdm_mic_model.vhd @@ -0,0 +1,72 @@ +---------------------------------------------------------------------------------- +-- Module Name: pdm_mic_model +-- Project Name: DigMic_prj +-- Description: +-- Поведенческая модель PDM-микрофона: сигма-дельта модулятор первого порядка +-- с передачей сигнала 1 и шумом (1 - z^-1). Что подаётся на модулятор, +-- выбирает вход mode. +-- +-- Вывод данных как в даташите: бит выставляется через TDD после рабочего +-- фронта (при SELECT = '0' -- по спаду) и через TDZ после противоположного +-- уходит в высокоимпедансное. Так тестбенч ловит выборку не в той фазе. +---------------------------------------------------------------------------------- +library ieee; +use ieee.std_logic_1164.all; +use ieee.math_real.all; + +entity pdm_mic_model is + generic ( + F_PDM : real := 3_125_000.0; + SEL_HIGH : std_logic := '0'; + AMP : real := 0.5; -- амплитуда синуса в долях шкалы + FREQ : real := 1000.0; -- частота синуса, Гц + TDD : time := 18 ns; -- задержка выдачи после рабочего фронта + TDZ : time := 15 ns -- задержка ухода в Z + ); + port ( + mic_clk : in std_logic; + mode : in integer; -- 0 +шкала, 1 -шкала, 2 ноль, 3 синус + mic_dat : out std_logic + ); +end entity pdm_mic_model; + +architecture model of pdm_mic_model is + constant T_HALF : time := (1 sec / integer(F_PDM)) / 2; +begin + + mod_proc : process + variable e : real := 0.0; -- накопитель ошибки + variable y : real := 0.0; -- предыдущий выход + variable x : real; + variable n : integer := 0; + variable b : std_logic; + begin + if SEL_HIGH = '1' then + wait until rising_edge(mic_clk); + else + wait until falling_edge(mic_clk); + end if; + + case mode is + when 0 => x := 1.0; + when 1 => x := - 1.0; + when 2 => x := 0.0; + when others => x := AMP * sin(2.0 * MATH_PI * FREQ * real(n) / F_PDM); + end case; + + e := e + x - y; + if e >= 0.0 then + y := 1.0; + b := '1'; + else + y := - 1.0; + b := '0'; + end if; + n := n + 1; + + -- Обе точки одной формой сигнала: раздельными присваиваниями инерционная + -- задержка второго вычистила бы транзакцию первого + mic_dat <= b after TDD, 'Z' after T_HALF + TDZ; + end process mod_proc; + +end architecture model; diff --git a/tb/tb_top.vhd b/tb/tb_top.vhd new file mode 100644 index 0000000..0c19ce6 --- /dev/null +++ b/tb/tb_top.vhd @@ -0,0 +1,249 @@ +---------------------------------------------------------------------------------- +-- 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. +---------------------------------------------------------------------------------- +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 + + signal clk : std_logic := '0'; + signal run : boolean := true; + + signal mic_clk : std_logic; + signal mic_dat : std_logic; + signal mic_sel : 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, + MIC_SEL => mic_sel, + UART_TX => uart_tx, + 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, + mode => mode, + mic_dat => mic_dat + ); + + 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; + 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); + + 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;