Приём PDM и дециматор: CIC sinc^4 /50 плюс ФНЧ /5
Микрофон тактируется на 3.125 МГц (50/16, Normal mode). CIC снимает поток до 62.5 кГц без умножителей, дальше симметричный ФНЧ на 221 отвод даёт 12 500 Гц: он же давит полосы, сворачивающиеся при децимации на 5, и выравнивает спад CIC. Коэффициенты считает tools/gen_fir_coefs.py и кладёт в fir_coefs_pkg.vhd. Масштаб подобран так, что постоянная единица на выводе данных микрофона даёт полную шкалу 24 бит, поэтому шкала выхода -- dBFS. Достигнуто: неравномерность полосы 0..5 кГц 0.032 дБ, подавление от 6250 Гц 72 дБ, подавление сворачиваемых полос 77 дБ.
This commit is contained in:
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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())
|
||||||
Reference in New Issue
Block a user