diff --git a/src/cic_decim.vhd b/src/cic_decim.vhd new file mode 100644 index 0000000..5d6895c --- /dev/null +++ b/src/cic_decim.vhd @@ -0,0 +1,114 @@ +---------------------------------------------------------------------------------- +-- Company: +-- Engineer: +-- Create Date: +-- Design Name: +-- Module Name: cic_decim - rtl +-- Project Name: DigMic_prj +-- Target Devices: Gowin GW5A-25 +-- Description: +-- CIC-дециматор sinc^ORDER с дифференциальной задержкой 1: интеграторы на +-- частоте PDM, гребёнки -- на пониженной. Умножителей не требует, нули АЧХ +-- стоят ровно на частотах, которые складываются в полосу при децимации. +-- +-- Разрядность: ORDER*log2(DECIM) + 1 бит на знак. Для 4 и 50 это 24 бита, +-- ACC_W взят с запасом. Переполнение внутри тракта штатно: результат верен +-- за счёт дополнительного кода, пока ACC_W не меньше требуемого. +-- Постоянному '1' на входе отвечает DECIM^ORDER = 6 250 000. +-- +-- Каждая ступень защёлкнута отдельно -- это только задержка на ORDER +-- отсчётов, зато нет длинной цепи сумматоров в одном такте. +---------------------------------------------------------------------------------- +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity cic_decim is + generic ( + ORDER : integer := 4; + DECIM : integer := 50; + ACC_W : integer := 26 + ); + port ( + clk : in std_logic; + rst_n : in std_logic; + + din : in std_logic; -- бит PDM: '1' -> +1, '0' -> -1 + din_valid : in std_logic; + + dout : out signed(ACC_W - 1 downto 0); + dout_valid : out std_logic + ); +end entity cic_decim; + +architecture rtl of cic_decim is + + type acc_arr_t is array (0 to ORDER - 1) of signed(ACC_W - 1 downto 0); + + signal integ : acc_arr_t := (others => (others => '0')); + signal comb_y : acc_arr_t := (others => (others => '0')); + signal comb_d : acc_arr_t := (others => (others => '0')); + signal comb_v : std_logic_vector(ORDER - 1 downto 0) := (others => '0'); + + signal cnt : integer range 0 to DECIM - 1 := 0; + signal tick : std_logic := '0'; + +begin + + dout <= comb_y(ORDER - 1); + dout_valid <= comb_v(ORDER - 1); + + integ_proc : process (clk, rst_n) + begin + if rst_n = '0' then + integ <= (others => (others => '0')); + cnt <= 0; + tick <= '0'; + elsif rising_edge(clk) then + tick <= '0'; + if din_valid = '1' then + if din = '1' then + integ(0) <= integ(0) + 1; + else + integ(0) <= integ(0) - 1; + end if; + for i in 1 to ORDER - 1 loop + integ(i) <= integ(i) + integ(i - 1); + end loop; + + if cnt = DECIM - 1 then + cnt <= 0; + tick <= '1'; + else + cnt <= cnt + 1; + end if; + end if; + end if; + end process integ_proc; + + -- Гребёнки разнесены по тактам: ступень i срабатывает по признаку годности + -- предыдущей, поэтому её линия задержки всё равно обновляется раз на отсчёт + comb_proc : process (clk, rst_n) + begin + if rst_n = '0' then + comb_y <= (others => (others => '0')); + comb_d <= (others => (others => '0')); + comb_v <= (others => '0'); + elsif rising_edge(clk) then + comb_v(0) <= tick; + if tick = '1' then + comb_y(0) <= integ(ORDER - 1) - comb_d(0); + comb_d(0) <= integ(ORDER - 1); + end if; + + for i in 1 to ORDER - 1 loop + comb_v(i) <= comb_v(i - 1); + if comb_v(i - 1) = '1' then + comb_y(i) <= comb_y(i - 1) - comb_d(i); + comb_d(i) <= comb_y(i - 1); + end if; + end loop; + end if; + end process comb_proc; + +end architecture rtl; diff --git a/src/fir_coefs_pkg.vhd b/src/fir_coefs_pkg.vhd new file mode 100644 index 0000000..0b6cce1 --- /dev/null +++ b/src/fir_coefs_pkg.vhd @@ -0,0 +1,91 @@ +---------------------------------------------------------------------------------- +-- Module Name: fir_coefs_pkg +-- Project Name: DigMic_prj +-- Target Devices: Gowin GW5A-25 +-- Description: +-- Коэффициенты ФНЧ-дециматора /5, стоящего после CIC. +-- Сгенерировано tools/gen_fir_coefs.py, руками не править. +-- +-- вход 62500 Гц -> выход 12500 Гц, отводов 221, симметричный +-- полоса 0..5000 Гц, задерживание от 6250 Гц +-- неравномерность полосы с учётом CIC: 0.032 дБ +-- подавление в полосе задерживания: -72.1 дБ +-- худшее подавление сворачиваемых полос: -77.5 дБ +---------------------------------------------------------------------------------- +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +package fir_coefs_pkg is + + constant FIR_NTAPS : integer := 221; + constant FIR_COEF_W : integer := 18; + constant FIR_SHIFT : integer := 17; + + -- Отклик тракта на постоянную '1' на выводе данных микрофона: + -- сумма коэффициентов x усиление CIC, округлённая как в fir_decim. + constant FIR_SUM : integer := 175921; + constant FIR_DC_CODE : integer := 8388567; + + 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 := ( + "000000000000000001", "000000000000000010", "000000000000000100", "000000000000000111", -- 0 + "000000000000001000", "000000000000001000", "000000000000000110", "000000000000000001", -- 4 + "111111111111111010", "111111111111110100", "111111111111101110", "111111111111101101", -- 8 + "111111111111110010", "111111111111111011", "000000000000001000", "000000000000010110", -- 12 + "000000000000011111", "000000000000100000", "000000000000010111", "000000000000000101", -- 16 + "111111111111101101", "111111111111010111", "111111111111001010", "111111111111001100", -- 20 + "111111111111011111", "000000000000000000", "000000000000100110", "000000000001000111", -- 24 + "000000000001010110", "000000000001001100", "000000000000101000", "111111111111110001", -- 28 + "111111111110110111", "111111111110001100", "111111111101111111", "111111111110011010", -- 32 + "111111111111011010", "000000000000101111", "000000000010000000", "000000000010110011", -- 36 + "000000000010110100", "000000000001111011", "000000000000010100", "111111111110011001", -- 40 + "111111111100101111", "111111111011111011", "111111111100010100", "111111111101111100", -- 44 + "000000000000011010", "000000000011000010", "000000000101000001", "000000000101100111", -- 48 + "000000000100011111", "000000000001110100", "111111111110010000", "111111111010110111", -- 52 + "111111111000101111", "111111111000101100", "111111111010111110", "111111111111000110", -- 56 + "000000000011111100", "000000001000000011", "000000001010000001", "000000001001000000", -- 60 + "000000000101000001", "111111111111000010", "111111111000101111", "111111110100001000", -- 64 + "111111110010110100", "111111110101100100", "111111111011111011", "000000000100001101", -- 68 + "000000001100000000", "000000010000110000", "000000010000101010", "000000001011010000", -- 72 + "000000000001101101", "111111110110101001", "111111101101011100", "111111101001001100", -- 76 + "111111101011110001", "111111110101000101", "000000000010111000", "000000010001001111", -- 80 + "000000011011101011", "000000011110011001", "000000010111101101", "000000001000101000", -- 84 + "111111110100111111", "111111100010100000", "111111010111001101", "111111010111100110", -- 88 + "111111100101001000", "111111111101001111", "000000011001110001", "000000110010010000", -- 92 + "000000111110001111", "000000110111110101", "000000011101110100", "111111110100110100", -- 96 + "111111000110110101", "111110100001011001", "111110010010011110", "111110100100111011", -- 100 + "111111011101011111", "000000111000110011", "000010101011011110", "000100100011111001", -- 104 + "000110001101101111", "000111010110001001", "000111110000000101", "000111010110001001", -- 108 + "000110001101101111", "000100100011111001", "000010101011011110", "000000111000110011", -- 112 + "111111011101011111", "111110100100111011", "111110010010011110", "111110100001011001", -- 116 + "111111000110110101", "111111110100110100", "000000011101110100", "000000110111110101", -- 120 + "000000111110001111", "000000110010010000", "000000011001110001", "111111111101001111", -- 124 + "111111100101001000", "111111010111100110", "111111010111001101", "111111100010100000", -- 128 + "111111110100111111", "000000001000101000", "000000010111101101", "000000011110011001", -- 132 + "000000011011101011", "000000010001001111", "000000000010111000", "111111110101000101", -- 136 + "111111101011110001", "111111101001001100", "111111101101011100", "111111110110101001", -- 140 + "000000000001101101", "000000001011010000", "000000010000101010", "000000010000110000", -- 144 + "000000001100000000", "000000000100001101", "111111111011111011", "111111110101100100", -- 148 + "111111110010110100", "111111110100001000", "111111111000101111", "111111111111000010", -- 152 + "000000000101000001", "000000001001000000", "000000001010000001", "000000001000000011", -- 156 + "000000000011111100", "111111111111000110", "111111111010111110", "111111111000101100", -- 160 + "111111111000101111", "111111111010110111", "111111111110010000", "000000000001110100", -- 164 + "000000000100011111", "000000000101100111", "000000000101000001", "000000000011000010", -- 168 + "000000000000011010", "111111111101111100", "111111111100010100", "111111111011111011", -- 172 + "111111111100101111", "111111111110011001", "000000000000010100", "000000000001111011", -- 176 + "000000000010110100", "000000000010110011", "000000000010000000", "000000000000101111", -- 180 + "111111111111011010", "111111111110011010", "111111111101111111", "111111111110001100", -- 184 + "111111111110110111", "111111111111110001", "000000000000101000", "000000000001001100", -- 188 + "000000000001010110", "000000000001000111", "000000000000100110", "000000000000000000", -- 192 + "111111111111011111", "111111111111001100", "111111111111001010", "111111111111010111", -- 196 + "111111111111101101", "000000000000000101", "000000000000010111", "000000000000100000", -- 200 + "000000000000011111", "000000000000010110", "000000000000001000", "111111111111111011", -- 204 + "111111111111110010", "111111111111101101", "111111111111101110", "111111111111110100", -- 208 + "111111111111111010", "000000000000000001", "000000000000000110", "000000000000001000", -- 212 + "000000000000001000", "000000000000000111", "000000000000000100", "000000000000000010", -- 216 + "000000000000000001" -- 220 + ); + +end package fir_coefs_pkg; diff --git a/src/fir_decim.vhd b/src/fir_decim.vhd new file mode 100644 index 0000000..5e6971b --- /dev/null +++ b/src/fir_decim.vhd @@ -0,0 +1,184 @@ +---------------------------------------------------------------------------------- +-- Company: +-- Engineer: +-- Create Date: +-- Design Name: +-- Module Name: fir_decim - rtl +-- Project Name: DigMic_prj +-- Target Devices: Gowin GW5A-25 +-- Description: +-- ФНЧ с децимацией на DECIM. Коэффициенты, их разрядность и выходной сдвиг -- +-- в fir_coefs_pkg, он генерируется tools/gen_fir_coefs.py. +-- +-- Свёртка последовательная, один умножитель: между выходными отсчётами +-- 4000 тактов, а свёртка занимает FIR_NTAPS + 3. +-- Конвейер: адрес -> отсчёт и коэффициент -> произведение -> накопление. +-- +-- Кольцевой буфер на 2^BUF_AW слов заметно длиннее фильтра, поэтому запись, +-- попавшая внутрь свёртки, ложится в ячейку, которую свёртка уже не читает. +---------------------------------------------------------------------------------- +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.fir_coefs_pkg.all; + +entity fir_decim is + generic ( + DIN_W : integer := 26; + DOUT_W : integer := 24; + DECIM : integer := 5; + BUF_AW : integer := 8; -- глубина буфера 2^BUF_AW, должна быть больше FIR_NTAPS + ACC_W : integer := 48 + ); + port ( + clk : in std_logic; + rst_n : in std_logic; + + din : in signed(DIN_W - 1 downto 0); + din_valid : in std_logic; + + dout : out signed(DOUT_W - 1 downto 0); + dout_valid : out std_logic; + sat : out std_logic -- выход упёрся в шкалу + ); +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 wr_ptr : unsigned(BUF_AW - 1 downto 0) := (others => '0'); + signal base : unsigned(BUF_AW - 1 downto 0) := (others => '0'); + signal phase : integer range 0 to DECIM - 1 := 0; + signal start : std_logic := '0'; + + signal run : std_logic := '0'; + signal tap : integer range 0 to FIR_NTAPS - 1 := 0; + signal addr : unsigned(BUF_AW - 1 downto 0) := (others => '0'); + + signal buf_q : signed(DIN_W - 1 downto 0) := (others => '0'); + signal coef : signed(FIR_COEF_W - 1 downto 0) := (others => '0'); + signal prod : signed(DIN_W + FIR_COEF_W - 1 downto 0) := (others => '0'); + signal acc : signed(ACC_W - 1 downto 0) := (others => '0'); + + -- Годность по ступеням конвейера и метка последнего отвода + signal v_a, v_b, v_c : std_logic := '0'; + signal l_b, l_c, l_d : std_logic := '0'; + + constant ROUND : signed(ACC_W - 1 downto 0) := to_signed(2 ** (FIR_SHIFT - 1), ACC_W); + constant DMAX : signed(ACC_W - 1 downto 0) := to_signed(2 ** (DOUT_W - 1) - 1, ACC_W); + constant DMIN : signed(ACC_W - 1 downto 0) := to_signed(-(2 ** (DOUT_W - 1)), ACC_W); + +begin + + -- Запись отсчётов и отсчёт фазы децимации + wr_proc : process (clk, rst_n) + begin + if rst_n = '0' then + wr_ptr <= (others => '0'); + base <= (others => '0'); + phase <= 0; + start <= '0'; + elsif rising_edge(clk) then + start <= '0'; + if din_valid = '1' then + base <= wr_ptr; -- куда лёг самый свежий отсчёт + wr_ptr <= wr_ptr + 1; + if phase = DECIM - 1 then + phase <= 0; + start <= '1'; -- свёртку пускаем со следующего такта: запись уже видна + else + phase <= phase + 1; + end if; + end if; + end if; + end process wr_proc; + + -- Память и ПЗУ коэффициентов: без сброса, иначе синтезатор не выведет блочную + mem_proc : process (clk) + begin + if rising_edge(clk) then + if din_valid = '1' then + buf(to_integer(wr_ptr)) <= din; + end if; + buf_q <= buf(to_integer(addr)); + coef <= FIR_COEFS(tap); + end if; + end process mem_proc; + + mac_proc : process (clk, rst_n) + variable res : signed(ACC_W - 1 downto 0); + begin + if rst_n = '0' then + run <= '0'; + tap <= 0; + addr <= (others => '0'); + prod <= (others => '0'); + acc <= (others => '0'); + v_a <= '0'; + v_b <= '0'; + v_c <= '0'; + l_b <= '0'; + l_c <= '0'; + l_d <= '0'; + dout <= (others => '0'); + dout_valid <= '0'; + sat <= '0'; + elsif rising_edge(clk) then + dout_valid <= '0'; + sat <= '0'; + l_b <= '0'; + + -- Ступень A: выдача адреса отвода + if start = '1' then + run <= '1'; + tap <= 0; + addr <= base; + acc <= (others => '0'); + v_a <= '1'; + elsif run = '1' then + if tap = FIR_NTAPS - 1 then + run <= '0'; + v_a <= '0'; + l_b <= '1'; + else + tap <= tap + 1; + addr <= addr - 1; + end if; + else + v_a <= '0'; + end if; + + -- Ступени B и C + v_b <= v_a; + v_c <= v_b; + l_c <= l_b; + l_d <= l_c; + + if v_b = '1' then + prod <= buf_q * coef; + end if; + if v_c = '1' then + acc <= acc + resize(prod, ACC_W); + end if; + + -- Округление и ограничение шкалой + if l_d = '1' then + res := shift_right(acc + ROUND, FIR_SHIFT); + if res > DMAX then + dout <= to_signed(2 ** (DOUT_W - 1) - 1, DOUT_W); + sat <= '1'; + elsif res < DMIN then + dout <= to_signed(-(2 ** (DOUT_W - 1)), DOUT_W); + sat <= '1'; + else + dout <= resize(res, DOUT_W); + end if; + dout_valid <= '1'; + end if; + end if; + end process mac_proc; + +end architecture rtl; diff --git a/src/pdm_rx.vhd b/src/pdm_rx.vhd new file mode 100644 index 0000000..a215f16 --- /dev/null +++ b/src/pdm_rx.vhd @@ -0,0 +1,93 @@ +---------------------------------------------------------------------------------- +-- Company: +-- Engineer: +-- Create Date: +-- Design Name: +-- Module Name: pdm_rx - rtl +-- Project Name: DigMic_prj +-- Target Devices: Gowin GW5A-25 +-- Description: +-- Тактирование микрофона и приём однобитного потока PDM. +-- MIC_CLK = clk / CLK_DIV. Микрофон при SELECT = '0' выставляет бит по спаду +-- MIC_CLK и держит его всю низкую фазу, при SELECT = '1' -- по фронту и всю +-- высокую. Защёлкиваем в середине нужной фазы: там есть запас и на задержку +-- выдачи, и на два такта синхронизатора. +---------------------------------------------------------------------------------- +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity pdm_rx is + generic ( + CLK_DIV : integer := 16; -- 50 МГц / 16 = 3.125 МГц, Normal mode микрофона + SEL_HIGH : std_logic := '0' -- уровень на выводе SELECT + ); + port ( + clk : in std_logic; + rst_n : in std_logic; + + mic_dat : in std_logic; + mic_clk : out std_logic; + mic_sel : out std_logic; + + bit_out : out std_logic; + bit_valid : out std_logic -- один такт clk на каждый бит PDM + ); +end entity pdm_rx; + +architecture rtl of pdm_rx is + + -- Середина фазы, в которой микрофон держит данные, плюс два такта синхронизатора + function sample_point return integer is + begin + if SEL_HIGH = '1' then + return 3 * CLK_DIV / 4 + 2; + else + return CLK_DIV / 4 + 2; + end if; + end function; + + constant SMP_CNT : integer := sample_point; + + signal cnt : integer range 0 to CLK_DIV - 1 := 0; + signal clk_r : std_logic := '0'; + signal sync : std_logic_vector(1 downto 0) := (others => '0'); + +begin + + mic_clk <= clk_r; + mic_sel <= SEL_HIGH; + + rx_proc : process (clk, rst_n) + begin + if rst_n = '0' then + cnt <= 0; + clk_r <= '0'; + sync <= (others => '0'); + bit_out <= '0'; + bit_valid <= '0'; + elsif rising_edge(clk) then + bit_valid <= '0'; + sync <= sync(0) & mic_dat; + + if cnt = CLK_DIV - 1 then + cnt <= 0; + else + cnt <= cnt + 1; + end if; + + -- Меандр: низкая фаза cnt = 0..CLK_DIV/2-1, высокая -- остальное + if cnt = CLK_DIV - 1 then + clk_r <= '0'; + elsif cnt = CLK_DIV/2 - 1 then + clk_r <= '1'; + end if; + + if cnt = SMP_CNT then + bit_out <= sync(1); + bit_valid <= '1'; + end if; + end if; + end process rx_proc; + +end architecture rtl; diff --git a/tools/gen_fir_coefs.py b/tools/gen_fir_coefs.py new file mode 100644 index 0000000..455f040 --- /dev/null +++ b/tools/gen_fir_coefs.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +"""Расчёт ФНЧ-дециматора /5 и генерация src/fir_coefs_pkg.vhd. + +Фильтр стоит после CIC sinc^4 /50 и решает две задачи сразу: + - давит всё выше 6250 Гц, куда после децимации на 5 сворачивается + шум сигма-дельта модулятора микрофона (CIC там ничего не подавляет); + - выравнивает спад CIC в полосе (задаётся желаемая АЧХ 1/H_cic). + +Масштаб выбран так, чтобы постоянная '1' на входе PDM давала полную шкалу +24 бит: коэффициенты домножены на 2^23 / 50^4 и сумма подогнана точно. + +Запуск: py -3 tools/gen_fir_coefs.py +""" + +import os +import sys + +import numpy as np +from scipy.signal import firls, freqz + +# --- параметры тракта, должны совпадать с Top.vhd --- +F_PDM = 3_125_000.0 # PDM_CLK = 50 МГц / 16 +R_CIC = 50 # децимация CIC +N_CIC = 4 # порядок CIC +FS_IN = F_PDM / R_CIC # 62500 Гц на входе FIR +DECIM = 5 # децимация FIR +FS_OUT = FS_IN / DECIM # 12500 Гц + +# --- параметры фильтра --- +NTAPS = 221 # нечётное: линейная фаза, симметрия +COEF_W = 18 # разрядность коэффициента +SHIFT = 17 # выход = (сумма произведений) >> SHIFT +PASS = 5000.0 # граница полосы пропускания +STOP = 6250.0 # граница полосы задерживания = FS_OUT/2 +WSTOP = 300.0 # вес полосы задерживания +NSUB = 8 # на сколько участков бьём полосу для компенсации + +CIC_DC = R_CIC ** N_CIC # коэффициент передачи CIC на постоянном токе +FS_CODE = 2 ** 23 - 1 # полная шкала выхода + +PKG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "..", "src", "fir_coefs_pkg.vhd") + + +def cic_mag(f): + """АЧХ CIC sinc^N по частоте входного сигнала.""" + f = np.atleast_1d(np.asarray(f, dtype=float)) + num = np.sin(np.pi * f * R_CIC / F_PDM) + den = R_CIC * np.sin(np.pi * f / F_PDM) + out = np.ones_like(f) + nz = f != 0 + out[nz] = np.abs(num[nz] / den[nz]) + return out ** N_CIC + + +def design(): + """Желаемая АЧХ в полосе -- 1/H_cic, так спад CIC уходит в коэффициенты.""" + edges = np.linspace(0.0, PASS, NSUB + 1) + bands, desired, weight = [], [], [] + for i in range(NSUB): + bands += [edges[i], edges[i + 1]] + desired += [1.0 / cic_mag(edges[i])[0], 1.0 / cic_mag(edges[i + 1])[0]] + weight.append(1.0) + bands += [STOP, FS_IN / 2] + desired += [0.0, 0.0] + weight.append(WSTOP) + return firls(NTAPS, bands, desired, weight=weight, fs=FS_IN) + + +def quantize(h): + """Целые коэффициенты. Сумму подгоняем центральным отводом: он сам себе + зеркало, симметрия не рушится, а передача на постоянном токе становится + ровно такой, какую проверяет тестбенч.""" + # Наибольшая сумма, при которой полная шкала PDM ещё не даёт переполнения + sum_target = ((FS_CODE * 2 ** SHIFT) - 2 ** (SHIFT - 1)) // CIC_DC + scale = sum_target / h.sum() + c = np.round(h * scale).astype(np.int64) + c[NTAPS // 2] += sum_target - c.sum() + return c, sum_target + + +def check(c): + """Проверка уже квантованных коэффициентов: неравномерность считаем + по сквозной АЧХ CIC+FIR, подавление -- по одному FIR.""" + lo = -2 ** (COEF_W - 1) + hi = 2 ** (COEF_W - 1) - 1 + assert c.min() >= lo and c.max() <= hi, f"не влезает в {COEF_W} бит: {c.min()}..{c.max()}" + assert np.array_equal(c, c[::-1]), "симметрия нарушена" + + h = c / 2 ** SHIFT / (FS_CODE / CIC_DC) # обратно к единичному усилению + w, H = freqz(h, worN=1 << 16, fs=FS_IN) + mag = np.abs(H) + total = mag * cic_mag(w) + pb, sb = w <= PASS, w >= STOP + ripple = 20 * np.log10(total[pb].max() / total[pb].min()) + att = 20 * np.log10(mag[sb].max()) + + # Худшее подавление среди полос, сворачивающихся в 0..PASS + alias = -999.0 + for k in range(1, DECIM): + band = (w >= k * FS_OUT - PASS) & (w <= k * FS_OUT + PASS) + if band.any(): + alias = max(alias, 20 * np.log10(mag[band].max())) + return ripple, att, alias, w, total + + +def emit(c, sum_c, dc_code, ripple, att, alias): + lines = [] + a = lines.append + a("-" * 82) + a("-- Module Name: fir_coefs_pkg") + a("-- Project Name: DigMic_prj") + a("-- Target Devices: Gowin GW5A-25") + a("-- Description:") + a("-- Коэффициенты ФНЧ-дециматора /5, стоящего после CIC.") + a("-- Сгенерировано tools/gen_fir_coefs.py, руками не править.") + a("--") + a(f"-- вход {FS_IN:.0f} Гц -> выход {FS_OUT:.0f} Гц, отводов {NTAPS}, симметричный") + a(f"-- полоса 0..{PASS:.0f} Гц, задерживание от {STOP:.0f} Гц") + a(f"-- неравномерность полосы с учётом CIC: {ripple:.3f} дБ") + a(f"-- подавление в полосе задерживания: {att:.1f} дБ") + a(f"-- худшее подавление сворачиваемых полос: {alias:.1f} дБ") + a("-" * 82) + a("library ieee;") + a("use ieee.std_logic_1164.all;") + a("use ieee.numeric_std.all;") + a("") + a("package fir_coefs_pkg is") + a("") + a(f" constant FIR_NTAPS : integer := {NTAPS};") + a(f" constant FIR_COEF_W : integer := {COEF_W};") + a(f" constant FIR_SHIFT : integer := {SHIFT};") + a("") + a(" -- Отклик тракта на постоянную '1' на выводе данных микрофона:") + a(" -- сумма коэффициентов x усиление CIC, округлённая как в fir_decim.") + a(f" constant FIR_SUM : integer := {sum_c};") + a(f" constant FIR_DC_CODE : integer := {dc_code};") + a("") + a(" type fir_coefs_t is array (0 to FIR_NTAPS - 1) of signed(FIR_COEF_W - 1 downto 0);") + a("") + a(" constant FIR_COEFS : fir_coefs_t := (") + per_line = 4 + for i in range(0, NTAPS, per_line): + chunk = c[i:i + per_line] + body = ", ".join(f'"{v & (2 ** COEF_W - 1):0{COEF_W}b}"' for v in chunk) + tail = "," if i + per_line < NTAPS else "" + lines.append(f" {body}{tail} -- {i}") + a(" );") + a("") + a("end package fir_coefs_pkg;") + + with open(PKG_PATH, "w", encoding="utf-8", newline="\n") as f: + f.write("\n".join(lines) + "\n") + + +def main(): + h = design() + c, sum_target = quantize(h) + ripple, att, alias, w, total = check(c) + + dc_code = (CIC_DC * int(c.sum()) + 2 ** (SHIFT - 1)) >> SHIFT + assert dc_code <= FS_CODE, dc_code + assert int(c.sum()) == sum_target + + emit(c, int(c.sum()), dc_code, ripple, att, alias) + + print(f"отводов : {NTAPS}, {COEF_W} бит, сдвиг {SHIFT}") + print(f"диапазон коэффициентов : {c.min()} .. {c.max()}") + print(f"сумма коэффициентов : {c.sum()}") + print(f"полная шкала PDM -> код: {dc_code} из {FS_CODE}") + print(f"неравномерность 0..{PASS:.0f} Гц (CIC+FIR): {ripple:.3f} дБ") + print(f"подавление от {STOP:.0f} Гц : {att:.1f} дБ") + print(f"подавление сворачиваемых полос: {alias:.1f} дБ") + for f0 in (100, 1000, 3000, 5000, 6250): + i = np.argmin(np.abs(w - f0)) + print(f" АЧХ на {f0:5d} Гц : {20 * np.log10(total[i]):+7.3f} дБ") + print(f"записано : {os.path.normpath(PKG_PATH)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main())