Стенд: ЦАП 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.
This commit is contained in:
+14
-8
@@ -3,17 +3,23 @@
|
||||
<Project>
|
||||
<Template>FPGA</Template>
|
||||
<Version>5</Version>
|
||||
<Device name="" pn="">gw5a25b-003</Device>
|
||||
<Device name="GW5A-25A" pn="GW5A-LV25MG121NC1/I0">gw5a25a-002</Device>
|
||||
<FileList>
|
||||
<File path="src/UART.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/led_status.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/pdm_rx.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/cic_decim.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/fir_coefs_pkg.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/fir_decim.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/fifo_top.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/pll_init.v" type="file.verilog" enable="1"/>
|
||||
<File path="src/Packets_creator.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/Top.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/UART.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/dac_stream.v" type="file.verilog" enable="1"/>
|
||||
<File path="src/i2s_transmitter.v" type="file.verilog" enable="1"/>
|
||||
<File path="src/cic_decim.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/fifo_top.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/fir_coefs_pkg.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/fir_decim.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/gain_button.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/gowin_pll/gowin_pll.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/gowin_pll/gowin_pll_mod.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/led_status.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/pdm_rx.vhd" type="file.vhdl" enable="1"/>
|
||||
<File path="src/DigMic.cst" type="file.cst" enable="1"/>
|
||||
<File path="src/DigMic.sdc" type="file.sdc" enable="1"/>
|
||||
</FileList>
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"BACKGROUND_PROGRAMMING" : "off",
|
||||
"COMPRESS" : false,
|
||||
"CPU" : true,
|
||||
"CRC_CHECK" : true,
|
||||
"Clock_Route_Order" : 0,
|
||||
"Convert_SDP32_36_to_SDP16_18" : true,
|
||||
"Correct_Hold_Violation" : true,
|
||||
"DONE" : true,
|
||||
"DOWNLOAD_SPEED" : "default",
|
||||
"Disable_Insert_Pad" : false,
|
||||
"ENABLE_CTP" : false,
|
||||
"ENABLE_MERGE_MODE" : false,
|
||||
"ENCRYPTION_KEY" : false,
|
||||
"ENCRYPTION_KEY_TEXT" : "00000000000000000000000000000000",
|
||||
"ERROR_DECTION_AND_CORRECTION" : false,
|
||||
"ERROR_DECTION_ONLY" : false,
|
||||
"ERROR_INJECTION" : false,
|
||||
"EXTERNAL_MASTER_CONFIG_CLOCK" : false,
|
||||
"Enable_DSRM" : false,
|
||||
"FORMAT" : "binary",
|
||||
"FREQUENCY_DIVIDER" : "1",
|
||||
"Generate_Constraint_File_of_Ports" : false,
|
||||
"Generate_IBIS_File" : false,
|
||||
"Generate_Plain_Text_Timing_Report" : false,
|
||||
"Generate_Post_PNR_Simulation_Model_File" : false,
|
||||
"Generate_Post_Place_File" : false,
|
||||
"Generate_SDF_File" : false,
|
||||
"Generate_VHDL_Post_PNR_Simulation_Model_File" : false,
|
||||
"Global_Freq" : "default",
|
||||
"GwSyn_Loop_Limit" : 2000,
|
||||
"HOTBOOT" : false,
|
||||
"I2C" : false,
|
||||
"I2C_SLAVE_ADDR" : "00",
|
||||
"INCREMENTAL_PLACE_AND_ROUTING" : "0",
|
||||
"INCREMENTAL_PLACE_ONLY" : "0",
|
||||
"IncludePath" : [
|
||||
|
||||
],
|
||||
"Incremental_Compile" : "",
|
||||
"Initialize_Primitives" : false,
|
||||
"JTAG" : false,
|
||||
"MODE_IO" : false,
|
||||
"MSPI" : false,
|
||||
"MSPI_JUMP" : false,
|
||||
"MULTIBOOT_ADDRESS_WIDTH" : "24",
|
||||
"MULTIBOOT_MODE" : "Single",
|
||||
"MULTIBOOT_SPI_FLASH_ADDRESS" : "000000",
|
||||
"MULTIJUMP_ADDRESS_WIDTH" : "24",
|
||||
"MULTIJUMP_MODE" : "Single",
|
||||
"MULTIJUMP_SPI_FLASH_ADDRESS" : "000000",
|
||||
"Multi_Boot" : false,
|
||||
"OUTPUT_BASE_NAME" : "DigMic",
|
||||
"POWER_ON_RESET_MONITOR" : true,
|
||||
"PRINT_BSRAM_VALUE" : true,
|
||||
"PROGRAM_DONE_BYPASS" : false,
|
||||
"PlaceInRegToIob" : true,
|
||||
"PlaceIoRegToIob" : true,
|
||||
"PlaceOutRegToIob" : true,
|
||||
"Place_Option" : "0",
|
||||
"Process_Configuration_Verion" : "1.0",
|
||||
"Promote_Physical_Constraint_Warning_to_Error" : true,
|
||||
"READY" : true,
|
||||
"RECONFIG_N" : false,
|
||||
"Ram_RW_Check" : false,
|
||||
"Replicate_Resources" : false,
|
||||
"Report_Auto-Placed_Io_Information" : false,
|
||||
"Route_Maxfan" : 23,
|
||||
"Route_Option" : "0",
|
||||
"Run_Timing_Driven" : true,
|
||||
"SECURE_MODE" : false,
|
||||
"SECURITY_BIT" : true,
|
||||
"SEU_HANDLER" : false,
|
||||
"SEU_HANDLER_CHECKSUM" : false,
|
||||
"SEU_HANDLER_MODE" : "auto",
|
||||
"SSPI" : true,
|
||||
"STOP_SEU_HANDLER" : false,
|
||||
"SerDes_retiming" : false,
|
||||
"Show_All_Warnings" : false,
|
||||
"Synthesize_tool" : "GowinSyn",
|
||||
"TclPre" : "",
|
||||
"TopModule" : "",
|
||||
"USERCODE" : "default",
|
||||
"Unused_Pin" : "As_input_tri_stated_with_pull_up",
|
||||
"VCC" : "0.9",
|
||||
"VCCAUX" : "3.3",
|
||||
"VCCX" : "3.3",
|
||||
"VHDL_Standard" : "VHDL_Std_1993",
|
||||
"Verilog_Standard" : "Vlg_Std_2001",
|
||||
"WAKE_UP" : "0",
|
||||
"show_all_warnings" : false,
|
||||
"turn_off_bg" : false
|
||||
}
|
||||
+17
-7
@@ -4,18 +4,28 @@
|
||||
//Tool Version: V1.9.12 (64-bit)
|
||||
//Part Number: GW5A-LV25MG121NC1/I0
|
||||
//Device: GW5A-25
|
||||
//Device Version: B
|
||||
//Created Time: Mon 08 17 17:26:24 2026
|
||||
//Device Version: A
|
||||
//Created Time: Thu 10 01 17:59:10 2026
|
||||
|
||||
IO_LOC "clk_80MHz" E3;
|
||||
IO_PORT "clk_80MHz" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||
IO_LOC "LED" E8;
|
||||
IO_PORT "LED" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||
IO_LOC "UART_TX" G5;
|
||||
IO_LOC "DAC_SCK" G1;
|
||||
IO_PORT "DAC_SCK" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||
IO_LOC "I2S_DIN" L3;
|
||||
IO_PORT "I2S_DIN" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||
IO_LOC "I2S_LRCK" L4;
|
||||
IO_PORT "I2S_LRCK" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||
IO_LOC "I2S_BCK" G2;
|
||||
IO_PORT "I2S_BCK" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||
IO_LOC "UART_TX" C3;
|
||||
IO_PORT "UART_TX" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||
IO_LOC "MIC_SEL" K7;
|
||||
IO_PORT "MIC_SEL" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||
IO_LOC "MIC_CLK" J11;
|
||||
IO_LOC "MIC_CLK" K8;
|
||||
IO_PORT "MIC_CLK" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
|
||||
IO_LOC "MIC_DAT" J10;
|
||||
IO_LOC "BTN_GAIN" H11;
|
||||
IO_PORT "BTN_GAIN" IO_TYPE=LVCMOS33 PULL_MODE=DOWN BANK_VCCIO=3.3;
|
||||
IO_LOC "MIC_DAT" K9;
|
||||
IO_PORT "MIC_DAT" IO_TYPE=LVCMOS33 PULL_MODE=DOWN BANK_VCCIO=3.3;
|
||||
IO_LOC "clk" E2;
|
||||
IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=NONE BANK_VCCIO=3.3;
|
||||
|
||||
+121
-11
@@ -7,9 +7,13 @@
|
||||
-- Project Name: DigMic_prj
|
||||
-- Target Devices: Gowin GW5A-25
|
||||
-- Description:
|
||||
-- Цифровой микрофон SPH9855 -> UART.
|
||||
-- Цифровой микрофон SPH9855 -> UART и ЦАП PCM5102A.
|
||||
-- Микрофон подключён по линии LVDS (SN65LVDS): MIC_CLK уходит на вход
|
||||
-- драйвера, MIC_DAT приходит с выхода приёмника.
|
||||
-- MIC_CLK 3.125 МГц (50/16, Normal mode) -> CIC sinc^3 /50 -> 62.5 кГц ->
|
||||
-- ФНЧ /5 -> 12 500 Гц, 24 бита -> кадры по 50 отсчётов -> UART 921600.
|
||||
-- ФНЧ /5 -> 12 500 Гц, 24 бита, и дальше двумя ветвями:
|
||||
-- кадры по 50 отсчётов -> UART 921600 на ПК;
|
||||
-- пересчёт в 48 828 Гц -> I2S на PCM5102A, аналоговый выход на осциллограф.
|
||||
--
|
||||
-- Постоянная '1' на выводе данных микрофона даёт полную шкалу 24 бит,
|
||||
-- так что шкала выхода -- dBFS, как её задаёт даташит микрофона.
|
||||
@@ -30,20 +34,46 @@ entity Top is
|
||||
-- Режимы 0..2 ищут обрыв тракта: быстрое мерцание значит, что выбранный
|
||||
-- строб живой. Режим 3 меряет сам clk: период не 1 с означает, что
|
||||
-- системная частота не SYS_CLK_HZ, и тогда врут все производные от неё.
|
||||
LED_SRC : integer := 0
|
||||
LED_SRC : integer := 0;
|
||||
-- Усиление ветви ЦАП задаёт кнопка BTN_GAIN: каждое нажатие +6 дБ, после
|
||||
-- DAC_GAIN_MAX (не больше 15) снова 0, после включения 0 -- та же шкала,
|
||||
-- что на UART; на UART всегда идут исходные отсчёты. Полная шкала -- 2.1 В
|
||||
-- скз на выходе ЦАП, фон в комнате около -72 dBFS: при +48 дБ он уже на
|
||||
-- -24 dBFS, а всё громче примерно 85 дБ SPL упирается в шкалу.
|
||||
DAC_GAIN_MAX : integer := 8;
|
||||
-- Такт периода MIC_CLK (0..15), на котором берётся бит PDM, см. pdm_rx.
|
||||
-- Задержка линии LVDS туда и обратно вместе с задержкой выдачи микрофона
|
||||
-- сдвигает смену данных, поэтому точка выборки измерена на железе:
|
||||
-- MIC_DAT меняется только между тактами 1 и 2 (40-60 нс после спада
|
||||
-- MIC_CLK с точностью до целых периодов) и держится весь остальной
|
||||
-- период. Бит берём на такте 12, то есть уровень вывода на
|
||||
-- такте 10 -- около 150 нс запаса в обе стороны. Если поменяется кабель
|
||||
-- или микросхемы линии, фазу надо перемерить. Для микрофона на коротких
|
||||
-- проводах: -1.
|
||||
MIC_SMP_CNT : integer := 12
|
||||
);
|
||||
port (
|
||||
clk : in std_logic;
|
||||
|
||||
-- Микрофон
|
||||
-- Микрофон по линии LVDS; SELECT задан на плате микрофона
|
||||
MIC_DAT : in std_logic;
|
||||
MIC_CLK : out std_logic;
|
||||
MIC_SEL : out std_logic;
|
||||
|
||||
-- UART на внешний USB-мост
|
||||
-- UART на внешний USB-мост FT232
|
||||
UART_TX : out std_logic;
|
||||
|
||||
LED : out std_logic
|
||||
-- ЦАП PCM5102A
|
||||
I2S_BCK : out std_logic;
|
||||
I2S_LRCK : out std_logic;
|
||||
I2S_DIN : out std_logic;
|
||||
DAC_SCK : out std_logic;
|
||||
|
||||
-- Кнопка усиления ЦАП, нажатие -- '1'
|
||||
BTN_GAIN : in std_logic;
|
||||
|
||||
LED : out std_logic;
|
||||
|
||||
clk_80MHz : out std_logic
|
||||
);
|
||||
end entity Top;
|
||||
|
||||
@@ -54,6 +84,7 @@ architecture Behavioral of Top is
|
||||
constant CIC_N : integer := 3;
|
||||
constant CIC_W : integer := 18;
|
||||
constant FIR_DEC : integer := 5; -- 62.5 кГц / 5 = 12.5 кГц
|
||||
constant FS_OUT : integer := SYS_CLK_HZ / (PDM_DIV * CIC_R * FIR_DEC);
|
||||
|
||||
-- Сброса нет намеренно: состояние задают начальные значения регистров.
|
||||
-- Пробовал сброс по включению от счётчика -- на железе тракт от него молча
|
||||
@@ -81,10 +112,43 @@ architecture Behavioral of Top is
|
||||
|
||||
signal led_event : std_logic;
|
||||
signal led_chain : std_logic;
|
||||
signal led_norm : std_logic;
|
||||
|
||||
signal dac_gain : unsigned(3 downto 0);
|
||||
signal gain_show : std_logic;
|
||||
signal gain_led : std_logic;
|
||||
|
||||
signal beat_cnt : integer range 0 to SYS_CLK_HZ / 2 - 1 := 0;
|
||||
signal beacon : std_logic := '0';
|
||||
|
||||
component Gowin_PLL
|
||||
port (
|
||||
clkin: in std_logic;
|
||||
clkout0: out std_logic;
|
||||
mdclk: in std_logic
|
||||
);
|
||||
end component;
|
||||
|
||||
-- Verilog, взят из проекта DAC_PCM5102A: I2S и SCK оттуда без изменений
|
||||
component dac_stream
|
||||
generic (
|
||||
CLK_FREQ_HZ : integer;
|
||||
IN_RATE_HZ : integer;
|
||||
SAMPLE_RATE_HZ : integer;
|
||||
GAIN_MAX : integer
|
||||
);
|
||||
port (
|
||||
clk : in std_logic;
|
||||
din : in std_logic_vector(23 downto 0);
|
||||
din_valid : in std_logic;
|
||||
gain : in std_logic_vector(3 downto 0);
|
||||
i2s_bck : out std_logic;
|
||||
i2s_lrck : out std_logic;
|
||||
i2s_din : out std_logic;
|
||||
dac_sck : out std_logic
|
||||
);
|
||||
end component;
|
||||
|
||||
begin
|
||||
|
||||
rst_n <= '1';
|
||||
@@ -96,7 +160,7 @@ begin
|
||||
-- Маяк собирается только когда выбран: его счётчик широкий и тикает каждый
|
||||
-- такт, а такие в этом кристалле надёжно работать не обязаны
|
||||
beacon_gen : if LED_SRC = 3 generate
|
||||
LED <= beacon;
|
||||
led_norm <= beacon;
|
||||
|
||||
beacon_proc : process (clk)
|
||||
begin
|
||||
@@ -112,13 +176,39 @@ begin
|
||||
end generate beacon_gen;
|
||||
|
||||
chain_gen : if LED_SRC /= 3 generate
|
||||
LED <= led_chain;
|
||||
led_norm <= led_chain;
|
||||
end generate chain_gen;
|
||||
|
||||
-- После нажатия кнопки светодиод на несколько секунд показывает усиление
|
||||
LED <= gain_led when gain_show = '1' else led_norm;
|
||||
|
||||
gain_button_inst : entity work.gain_button(rtl)
|
||||
generic map(
|
||||
SYS_CLK_HZ => SYS_CLK_HZ,
|
||||
GAIN_MAX => DAC_GAIN_MAX,
|
||||
BTN_ACTIVE => '1' -- кнопка S1 платы замыкает вывод на +3.3 В
|
||||
)
|
||||
port map
|
||||
(
|
||||
clk => clk,
|
||||
btn => BTN_GAIN,
|
||||
gain => dac_gain,
|
||||
show => gain_show,
|
||||
led => gain_led
|
||||
);
|
||||
|
||||
PLL_dbg: Gowin_PLL
|
||||
port map (
|
||||
clkin => clk,
|
||||
clkout0 => clk_80MHz,
|
||||
mdclk => clk
|
||||
);
|
||||
|
||||
pdm_rx_inst : entity work.pdm_rx(rtl)
|
||||
generic map(
|
||||
CLK_DIV => PDM_DIV,
|
||||
SEL_HIGH => '0' -- микрофон отдаёт бит по спаду MIC_CLK
|
||||
SEL_HIGH => '0', -- микрофон отдаёт бит по спаду MIC_CLK
|
||||
SMP_CNT => MIC_SMP_CNT
|
||||
)
|
||||
port map
|
||||
(
|
||||
@@ -126,7 +216,7 @@ begin
|
||||
rst_n => rst_n,
|
||||
mic_dat => MIC_DAT,
|
||||
mic_clk => MIC_CLK,
|
||||
mic_sel => MIC_SEL,
|
||||
mic_sel => open,
|
||||
bit_out => pdm_bit,
|
||||
bit_valid => pdm_valid
|
||||
);
|
||||
@@ -228,6 +318,26 @@ begin
|
||||
tx => UART_TX
|
||||
);
|
||||
|
||||
-- Вторая ветвь: те же отсчёты ФНЧ, мимо FIFO и кадров, на ЦАП
|
||||
dac_stream_inst : dac_stream
|
||||
generic map(
|
||||
CLK_FREQ_HZ => SYS_CLK_HZ,
|
||||
IN_RATE_HZ => FS_OUT,
|
||||
SAMPLE_RATE_HZ => 48000, -- номинал делителя I2S, выйдет 48 828.125 Гц
|
||||
GAIN_MAX => DAC_GAIN_MAX
|
||||
)
|
||||
port map
|
||||
(
|
||||
clk => clk,
|
||||
din => std_logic_vector(fir_data),
|
||||
din_valid => fir_valid,
|
||||
gain => std_logic_vector(dac_gain),
|
||||
i2s_bck => I2S_BCK,
|
||||
i2s_lrck => I2S_LRCK,
|
||||
i2s_din => I2S_DIN,
|
||||
dac_sck => DAC_SCK
|
||||
);
|
||||
|
||||
|
||||
led_status_inst : entity work.led_status(rtl)
|
||||
generic map(
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
`timescale 1ns / 1ps
|
||||
// =============================================================================
|
||||
// Module: dac_stream
|
||||
// Project Name: DigMic_prj
|
||||
// Description:
|
||||
// Отсчёты микрофона -> ЦАП PCM5102A по I2S.
|
||||
//
|
||||
// I2S и SCK -- ровно как в проверенном на железе dac_top проекта
|
||||
// DAC_PCM5102A: fS = 50 МГц / 1024 = 48 828.125 Гц, BCK = 64 fS,
|
||||
// SCK = 12.5 МГц = 256 fS, тот же i2s_transmitter без изменений.
|
||||
//
|
||||
// Частота отсчётов микрофона (12 500 Гц) и частота ЦАП делятся из одного
|
||||
// clk, поэтому их отношение постоянно и точно: 4000 / 1024 тактов. Между
|
||||
// ними нужен только пересчёт, без FIFO и подстройки по заполнению.
|
||||
//
|
||||
// Пересчёт -- линейная интерполяция между двумя последними входными
|
||||
// отсчётами. Доля интервала берётся из числа тактов с прихода последнего
|
||||
// отсчёта, так что каждый выходной отсчёт взят в своей точке времени.
|
||||
// Простое удержание дало бы ступени по 3-4 повтора с дрожанием на период
|
||||
// ЦАП и зеркала 12.5 кГц +- f в полосе ЦАП: для тона 1 кГц это -21 дБ,
|
||||
// интерполяция опускает их до -42 дБ. Цена -- задержка на один входной
|
||||
// отсчёт, 80 мкс.
|
||||
//
|
||||
// gain поднимает уровень только для ЦАП, на UART уходят исходные отсчёты.
|
||||
// Полная шкала 24 бит -- это 2.1 В скз на выходе PCM5102A, а обычный
|
||||
// уровень с микрофона -60...-70 dBFS, то есть единицы милливольт. Сдвиг на
|
||||
// gain даёт ровно +6.02 * gain дБ с ограничением по шкале. Новое значение
|
||||
// действует с очередного входного отсчёта; больше GAIN_MAX не бывает.
|
||||
// =============================================================================
|
||||
|
||||
module dac_stream #(
|
||||
parameter integer CLK_FREQ_HZ = 50000000,
|
||||
parameter integer IN_RATE_HZ = 12500, // частота входных отсчётов
|
||||
parameter integer SAMPLE_RATE_HZ = 48000, // номинал для делителя I2S, выйдет 48 828.125
|
||||
parameter integer GAIN_MAX = 8 // наибольший сдвиг, не больше 15
|
||||
)(
|
||||
input wire clk,
|
||||
|
||||
input wire [23:0] din, // отсчёт микрофона, дополнительный код
|
||||
input wire din_valid, // строб отсчёта, раз в IN_PERIOD тактов
|
||||
input wire [3:0] gain, // усиление для ЦАП: +6 дБ на единицу
|
||||
|
||||
output wire i2s_bck,
|
||||
output wire i2s_lrck,
|
||||
output wire i2s_din,
|
||||
output wire dac_sck
|
||||
);
|
||||
|
||||
localparam integer IN_PERIOD = CLK_FREQ_HZ / IN_RATE_HZ; // 4000 тактов
|
||||
localparam integer CNT_W = $clog2(IN_PERIOD);
|
||||
|
||||
// Доля интервала cnt / IN_PERIOD в Q16: умножение на обратную величину
|
||||
// вместо деления. Ошибка доли меньше 3e-5, это -100 dBFS на крутом склоне.
|
||||
localparam integer FRAC_W = 16;
|
||||
localparam integer RECIP_SH = 10;
|
||||
localparam integer RECIP = (1 << (FRAC_W + RECIP_SH)) / IN_PERIOD;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 1. Усиление с ограничением по шкале
|
||||
// -------------------------------------------------------------------------
|
||||
localparam integer GW = 24 + GAIN_MAX;
|
||||
localparam [3:0] GMAX4 = GAIN_MAX;
|
||||
|
||||
wire [3:0] g = (gain > GMAX4) ? GMAX4 : gain;
|
||||
wire signed [GW-1:0] din_wide = $signed(din) <<< g;
|
||||
wire [GAIN_MAX:0] din_top = din_wide[GW-1:23];
|
||||
wire din_ovf = ~(&din_top) & (|din_top);
|
||||
|
||||
wire signed [23:0] din_gain = !din_ovf ? din_wide[23:0] :
|
||||
din_wide[GW-1] ? 24'sh800000 :
|
||||
24'sh7FFFFF;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2. Два последних отсчёта и время с прихода последнего
|
||||
// -------------------------------------------------------------------------
|
||||
// Если отсчёты перестанут приходить, счётчик встанет на конце интервала
|
||||
// и выход останется на последнем отсчёте.
|
||||
reg signed [23:0] x_cur = 24'sd0;
|
||||
reg signed [23:0] x_prev = 24'sd0;
|
||||
reg [CNT_W-1:0] cnt = {CNT_W{1'b0}};
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (din_valid) begin
|
||||
x_prev <= x_cur;
|
||||
x_cur <= din_gain;
|
||||
cnt <= {CNT_W{1'b0}};
|
||||
end else if (cnt != IN_PERIOD - 1) begin
|
||||
cnt <= cnt + 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 3. Интерполяция: x_prev + (x_cur - x_prev) * доля
|
||||
// -------------------------------------------------------------------------
|
||||
// Конвейер считает каждый такт, все ступени берут состояние одного такта,
|
||||
// так что смена отсчёта посреди конвейера ничего не смешивает. Результат
|
||||
// всегда лежит между x_prev и x_cur, переполнения нет.
|
||||
reg signed [24:0] d1 = 25'sd0;
|
||||
reg [FRAC_W-1:0] f1 = {FRAC_W{1'b0}};
|
||||
reg signed [23:0] b1 = 24'sd0;
|
||||
|
||||
reg signed [FRAC_W+25:0] p2 = {(FRAC_W+26){1'b0}};
|
||||
reg signed [23:0] b2 = 24'sd0;
|
||||
|
||||
reg signed [23:0] y3 = 24'sd0;
|
||||
|
||||
wire [CNT_W+RECIP_SH+FRAC_W-1:0] frac_full = cnt * RECIP;
|
||||
|
||||
wire signed [FRAC_W+25:0] p2_rnd = p2 + (1 << (FRAC_W - 1));
|
||||
wire signed [25:0] p2_sh = p2_rnd[FRAC_W+25:FRAC_W];
|
||||
wire signed [25:0] y_sum = b2 + p2_sh;
|
||||
|
||||
always @(posedge clk) begin
|
||||
d1 <= x_cur - x_prev;
|
||||
f1 <= frac_full[RECIP_SH +: FRAC_W];
|
||||
b1 <= x_prev;
|
||||
|
||||
p2 <= d1 * $signed({1'b0, f1});
|
||||
b2 <= b1;
|
||||
|
||||
y3 <= y_sum[23:0];
|
||||
end
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 4. I2S: один и тот же отсчёт в оба канала
|
||||
// -------------------------------------------------------------------------
|
||||
// На BNC платы ЦАП выведен только OUTL, но отсчёт идёт в оба канала, как в
|
||||
// dac_top. Защёлка по sample_tick (за 16 тактов до начала кадра) нужна,
|
||||
// чтобы правый канал получил тот же отсчёт, а не взятый на полкадра позже.
|
||||
wire sample_tick;
|
||||
reg signed [23:0] dac_sample = 24'sd0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (sample_tick)
|
||||
dac_sample <= y3;
|
||||
end
|
||||
|
||||
i2s_transmitter #(
|
||||
.CLK_FREQ_HZ (CLK_FREQ_HZ),
|
||||
.SAMPLE_RATE_HZ (SAMPLE_RATE_HZ),
|
||||
.DATA_WIDTH (24)
|
||||
) u_i2s_transmitter (
|
||||
.clk (clk),
|
||||
.rst_n (1'b1),
|
||||
.data_left (dac_sample),
|
||||
.data_right (dac_sample),
|
||||
.bck (i2s_bck),
|
||||
.lrck (i2s_lrck),
|
||||
.din (i2s_din),
|
||||
.sample_tick (sample_tick),
|
||||
.dbg_bit_cnt (),
|
||||
.dbg_bck_fall ()
|
||||
);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 5. Системный клок ЦАП, как в dac_top
|
||||
// -------------------------------------------------------------------------
|
||||
// SCK = 256 fS из того же clk, поэтому SCK, BCK и LRCK синхронны по
|
||||
// построению. Без SCK ЦАП перешёл бы на внутренний PLL от BCK, но пока
|
||||
// вывод ПЛИС не сконфигурирован, это состояние неоднозначно.
|
||||
localparam integer BCK_CLK_DIV = CLK_FREQ_HZ / (SAMPLE_RATE_HZ * 64 * 2);
|
||||
localparam integer SCK_HALF = (BCK_CLK_DIV / 4) > 0 ? (BCK_CLK_DIV / 4) : 1;
|
||||
localparam integer SCK_CNT_W = (SCK_HALF > 1) ? $clog2(SCK_HALF) : 1;
|
||||
|
||||
reg [SCK_CNT_W-1:0] sck_cnt = {SCK_CNT_W{1'b0}};
|
||||
reg sck_reg = 1'b0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (sck_cnt >= SCK_HALF - 1) begin
|
||||
sck_cnt <= {SCK_CNT_W{1'b0}};
|
||||
sck_reg <= ~sck_reg;
|
||||
end else begin
|
||||
sck_cnt <= sck_cnt + 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
assign dac_sck = sck_reg;
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,150 @@
|
||||
----------------------------------------------------------------------------------
|
||||
-- Company:
|
||||
-- Engineer:
|
||||
-- Create Date:
|
||||
-- Design Name:
|
||||
-- Module Name: gain_button - rtl
|
||||
-- Project Name: DigMic_prj
|
||||
-- Target Devices: Gowin GW5A-25
|
||||
-- Description:
|
||||
-- Усиление ветви ЦАП с кнопки. Каждое нажатие -- следующая ступень, +6 дБ,
|
||||
-- после GAIN_MAX снова 0. После включения -- 0, то есть та же шкала, что на
|
||||
-- UART.
|
||||
--
|
||||
-- Выбранную ступень показывает светодиод: пауза, N коротких вспышек (для 0
|
||||
-- -- одна длинная), пауза, и светодиод возвращается к обычной индикации.
|
||||
-- Нажатие во время показа начинает показ заново, уже с новым значением.
|
||||
--
|
||||
-- Дребезг: новый уровень кнопки принимается, только если продержался
|
||||
-- DEBOUNCE_MS подряд.
|
||||
----------------------------------------------------------------------------------
|
||||
library ieee;
|
||||
use ieee.std_logic_1164.all;
|
||||
use ieee.numeric_std.all;
|
||||
|
||||
entity gain_button is
|
||||
generic (
|
||||
SYS_CLK_HZ : integer := 50_000_000;
|
||||
GAIN_MAX : integer := 8; -- не больше 15
|
||||
BTN_ACTIVE : std_logic := '1'; -- уровень на выводе при нажатой кнопке
|
||||
DEBOUNCE_MS : integer := 20
|
||||
);
|
||||
port (
|
||||
clk : in std_logic;
|
||||
btn : in std_logic;
|
||||
|
||||
gain : out unsigned(3 downto 0);
|
||||
show : out std_logic; -- светодиод занят показом усиления
|
||||
led : out std_logic
|
||||
);
|
||||
end entity gain_button;
|
||||
|
||||
architecture rtl of gain_button is
|
||||
|
||||
constant MS_DIV : integer := SYS_CLK_HZ / 1000;
|
||||
|
||||
-- Длительности показа, мс
|
||||
constant T_GAP : integer := 600; -- пауза до и после вспышек
|
||||
constant T_ON : integer := 150; -- короткая вспышка
|
||||
constant T_OFF : integer := 350; -- между вспышками
|
||||
constant T_ZERO : integer := 1000; -- длинная вспышка для 0
|
||||
|
||||
type state_t is (S_IDLE, S_GAP1, S_ON, S_OFF, S_GAP2);
|
||||
|
||||
signal state : state_t := S_IDLE;
|
||||
|
||||
signal div : integer range 0 to MS_DIV - 1 := 0;
|
||||
signal tick : std_logic := '0'; -- раз в миллисекунду
|
||||
|
||||
signal sync : std_logic_vector(1 downto 0) := (others => not BTN_ACTIVE);
|
||||
signal stable : std_logic := not BTN_ACTIVE;
|
||||
signal db_cnt : integer range 0 to DEBOUNCE_MS := 0;
|
||||
|
||||
signal g : integer range 0 to GAIN_MAX := 0;
|
||||
signal cnt : integer range 0 to GAIN_MAX := 0; -- сколько вспышек показать
|
||||
signal left : integer range 0 to GAIN_MAX := 0; -- сколько ещё после текущей
|
||||
signal t : integer range 0 to T_ZERO := 0; -- мс до конца состояния
|
||||
|
||||
begin
|
||||
|
||||
gain <= to_unsigned(g, 4);
|
||||
show <= '0' when state = S_IDLE else '1';
|
||||
led <= '1' when state = S_ON else '0';
|
||||
|
||||
main_proc : process (clk)
|
||||
variable press : boolean;
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
sync <= sync(0) & btn;
|
||||
tick <= '0';
|
||||
press := false;
|
||||
|
||||
if div = MS_DIV - 1 then
|
||||
div <= 0;
|
||||
tick <= '1';
|
||||
else
|
||||
div <= div + 1;
|
||||
end if;
|
||||
|
||||
-- Дребезг
|
||||
if sync(1) = stable then
|
||||
db_cnt <= 0;
|
||||
elsif tick = '1' then
|
||||
if db_cnt = DEBOUNCE_MS - 1 then
|
||||
db_cnt <= 0;
|
||||
stable <= sync(1);
|
||||
press := sync(1) = BTN_ACTIVE;
|
||||
else
|
||||
db_cnt <= db_cnt + 1;
|
||||
end if;
|
||||
end if;
|
||||
|
||||
-- Показ
|
||||
if tick = '1' and state /= S_IDLE then
|
||||
if t /= 0 then
|
||||
t <= t - 1;
|
||||
else
|
||||
case state is
|
||||
when S_GAP1 =>
|
||||
state <= S_ON;
|
||||
if cnt = 0 then
|
||||
t <= T_ZERO - 1;
|
||||
left <= 0;
|
||||
else
|
||||
t <= T_ON - 1;
|
||||
left <= cnt - 1;
|
||||
end if;
|
||||
when S_ON =>
|
||||
if left = 0 then
|
||||
state <= S_GAP2;
|
||||
t <= T_GAP - 1;
|
||||
else
|
||||
state <= S_OFF;
|
||||
t <= T_OFF - 1;
|
||||
end if;
|
||||
when S_OFF =>
|
||||
state <= S_ON;
|
||||
t <= T_ON - 1;
|
||||
left <= left - 1;
|
||||
when others =>
|
||||
state <= S_IDLE;
|
||||
end case;
|
||||
end if;
|
||||
end if;
|
||||
|
||||
-- Нажатие: следующая ступень и показ с начала
|
||||
if press then
|
||||
if g = GAIN_MAX then
|
||||
g <= 0;
|
||||
cnt <= 0;
|
||||
else
|
||||
g <= g + 1;
|
||||
cnt <= g + 1;
|
||||
end if;
|
||||
state <= S_GAP1;
|
||||
t <= T_GAP - 1;
|
||||
end if;
|
||||
end if;
|
||||
end process main_proc;
|
||||
|
||||
end architecture rtl;
|
||||
@@ -0,0 +1,147 @@
|
||||
[General]
|
||||
ipc_version=4
|
||||
file=gowin_pll
|
||||
module=Gowin_PLL
|
||||
target_device=gw5a25b-003
|
||||
type=clock_plladv
|
||||
version=1.0
|
||||
|
||||
[Config]
|
||||
AdvancedMode=false
|
||||
ClkfbDivideFactorStatic=true
|
||||
ClkfbDivideFactorStaticValue=1
|
||||
ClkfbInternal=true
|
||||
ClkfboutExpectedFrequency=400
|
||||
ClkfboutTolerance=0.0
|
||||
ClkfboutVCODivideFactorStatic=true
|
||||
ClkfboutVCODivideFactorStaticValue=16
|
||||
ClkfboutVCOFractionalDivideFactorStaticValue=0
|
||||
ClkinClockFrequency=50
|
||||
ClkinDividerFactorStatic=true
|
||||
ClkinDividerFactorStaticValue=1
|
||||
ClkinDividerReset=false
|
||||
Clkou1BPhaseStatic=true
|
||||
Clkou2BPhaseStatic=true
|
||||
Clkou3BPhaseStatic=true
|
||||
Clkout0Bypass=false
|
||||
Clkout0DutyCycleDynamic=false
|
||||
Clkout0DutyCycleDynamicValue=0
|
||||
Clkout0DutyCycleStatic=true
|
||||
Clkout0DutyTrimStatic=true
|
||||
Clkout0DutyTrimStaticFalling=false
|
||||
Clkout0DutyTrimStaticRising=true
|
||||
Clkout0DutyTrimStaticStep=0
|
||||
Clkout0ExpectedFrequency=80
|
||||
Clkout0PhaseDynamic=false
|
||||
Clkout0PhaseStatic=true
|
||||
Clkout0PhaseStaticValue=0
|
||||
Clkout0Tolerance=0.0
|
||||
Clkout0VCODivideFactorStatic=true
|
||||
Clkout0VCODivideFactorStaticValue=10
|
||||
Clkout0VCOFractionalDivideFactorStaticValue=0
|
||||
Clkout1Bypass=false
|
||||
Clkout1DutyCycleDynamic=false
|
||||
Clkout1DutyCycleDynamicValue=0
|
||||
Clkout1DutyCycleStatic=true
|
||||
Clkout1DutyTrimStatic=true
|
||||
Clkout1DutyTrimStaticFalling=false
|
||||
Clkout1DutyTrimStaticRising=true
|
||||
Clkout1DutyTrimStaticStep=0
|
||||
Clkout1ExpectedFrequency=400
|
||||
Clkout1PhaseDynamic=false
|
||||
Clkout1PhaseStaticValue=0
|
||||
Clkout1Tolerance=0.0
|
||||
Clkout1VCODivideFactorStatic=true
|
||||
Clkout1VCODivideFactorStaticValue=8
|
||||
Clkout2Bypass=false
|
||||
Clkout2DutyCycleDynamic=false
|
||||
Clkout2DutyCycleDynamicValue=0
|
||||
Clkout2DutyCycleStatic=true
|
||||
Clkout2DutyTrimStatic=true
|
||||
Clkout2DutyTrimStaticFalling=false
|
||||
Clkout2DutyTrimStaticRising=true
|
||||
Clkout2DutyTrimStaticStep=0
|
||||
Clkout2ExpectedFrequency=400
|
||||
Clkout2PhaseDynamic=false
|
||||
Clkout2PhaseStaticValue=0
|
||||
Clkout2Tolerance=0.0
|
||||
Clkout2VCODivideFactorStatic=true
|
||||
Clkout2VCODivideFactorStaticValue=8
|
||||
Clkout3Bypass=false
|
||||
Clkout3DutyCycleDynamic=false
|
||||
Clkout3DutyCycleDynamicValue=0
|
||||
Clkout3DutyCycleStatic=true
|
||||
Clkout3DutyTrimStatic=true
|
||||
Clkout3DutyTrimStaticFalling=false
|
||||
Clkout3DutyTrimStaticRising=true
|
||||
Clkout3DutyTrimStaticStep=0
|
||||
Clkout3ExpectedFrequency=400
|
||||
Clkout3PhaseDynamic=false
|
||||
Clkout3PhaseStaticValue=0
|
||||
Clkout3Tolerance=0.0
|
||||
Clkout3VCODivideFactorStatic=true
|
||||
Clkout3VCODivideFactorStaticValue=8
|
||||
Clkout4Bypass=false
|
||||
Clkout4DutyCycleDynamic=false
|
||||
Clkout4DutyCycleDynamicValue=0
|
||||
Clkout4DutyCycleStatic=true
|
||||
Clkout4ExpectedFrequency=400
|
||||
Clkout4PhaseDynamic=false
|
||||
Clkout4PhaseStatic=true
|
||||
Clkout4PhaseStaticValue=0
|
||||
Clkout4Tolerance=0.0
|
||||
Clkout4VCODivideFactorStatic=true
|
||||
Clkout4VCODivideFactorStaticValue=8
|
||||
Clkout5Bypass=false
|
||||
Clkout5DutyCycleDynamic=false
|
||||
Clkout5DutyCycleDynamicValue=0
|
||||
Clkout5DutyCycleStatic=true
|
||||
Clkout5ExpectedFrequency=400
|
||||
Clkout5PhaseDynamic=false
|
||||
Clkout5PhaseStatic=true
|
||||
Clkout5PhaseStaticValue=0
|
||||
Clkout5Tolerance=0.0
|
||||
Clkout5VCODivideFactorStatic=true
|
||||
Clkout5VCODivideFactorStaticValue=8
|
||||
Clkout6Bypass=false
|
||||
Clkout6DutyCycleDynamic=false
|
||||
Clkout6DutyCycleDynamicValue=0
|
||||
Clkout6DutyCycleStatic=true
|
||||
Clkout6ExpectedFrequency=400
|
||||
Clkout6PhaseDynamic=false
|
||||
Clkout6PhaseStatic=true
|
||||
Clkout6PhaseStaticValue=0
|
||||
Clkout6Tolerance=0.0
|
||||
Clkout6VCODivideFactorStatic=true
|
||||
Clkout6VCODivideFactorStaticValue=8
|
||||
ClkoutDividerReset=false
|
||||
EnableCascade=false
|
||||
EnableClkfbout=false
|
||||
EnableClkout0Divider=false
|
||||
EnableClkout1=false
|
||||
EnableClkout1Divider=false
|
||||
EnableClkout2=false
|
||||
EnableClkout2Divider=false
|
||||
EnableClkout3=false
|
||||
EnableClkout3Divider=false
|
||||
EnableClkout4=false
|
||||
EnableClkout4Divider=false
|
||||
EnableClkout5=false
|
||||
EnableClkout5Divider=false
|
||||
EnableClkout6=false
|
||||
EnableClkout6Divider=false
|
||||
EnableLock=false
|
||||
EnableMDRP=false
|
||||
EnableSsc=false
|
||||
GeneralMode=true
|
||||
ICPSELStatic=true
|
||||
ICPSELStaticValue=X
|
||||
LANG=1
|
||||
LPFCAPStaticValue=C0
|
||||
LPFRESStaticValue=X
|
||||
LPFSELStatic=true
|
||||
PLLPowerDown=false
|
||||
PLLReset=false
|
||||
clkfbExternal=false
|
||||
clkfbExternalValue=
|
||||
mDRP_Clock_Frequency=50
|
||||
@@ -0,0 +1,44 @@
|
||||
-series GW5A
|
||||
-device GW5A-25
|
||||
-device_version B
|
||||
-package MBGA121N
|
||||
-part_number GW5A-LV25MG121NC1/I0
|
||||
|
||||
|
||||
-mod_name Gowin_PLL_MOD
|
||||
-file_name gowin_pll_mod
|
||||
-path F:/gitea/DigMic_prj/src/gowin_pll/
|
||||
-type PLL_ADV
|
||||
-file_type vhdl
|
||||
-ssc false
|
||||
-rst true
|
||||
-rst_pwd false
|
||||
-rst_i false
|
||||
-mdrp_en true
|
||||
-rst_o false
|
||||
-fclkin 50
|
||||
-idiv_sel 1
|
||||
-clkfb_sel 0
|
||||
-fbdiv_sel 1
|
||||
-en_lock true
|
||||
-dyn_dpa_en false
|
||||
-clkout0_bypass false
|
||||
-odiv0_sel 10
|
||||
-odiv0_frac_sel 0
|
||||
-clkout0_dt_dir 1
|
||||
-clkout0_dt_step 0
|
||||
-dyn_pe0_sel false
|
||||
-clkout0_pe_coarse 0
|
||||
-clkout0_pe_fine 0
|
||||
-de0_en false
|
||||
-clkout0_dt_dir 1
|
||||
-clkout0_dt_step 0
|
||||
-en_clkout1 false
|
||||
-en_clkout2 false
|
||||
-en_clkout3 false
|
||||
-en_clkout4 false
|
||||
-en_clkout5 false
|
||||
-en_clkout6 false
|
||||
-en_clkfbout false
|
||||
-mdiv_sel 16
|
||||
-mdiv_frac_sel 0
|
||||
@@ -0,0 +1,102 @@
|
||||
library IEEE;
|
||||
use IEEE.std_logic_1164.all;
|
||||
|
||||
|
||||
entity Gowin_PLL is
|
||||
port (
|
||||
clkin: in std_logic;
|
||||
clkout0: out std_logic;
|
||||
mdclk: in std_logic
|
||||
);
|
||||
end Gowin_PLL;
|
||||
|
||||
|
||||
architecture Behavioral of Gowin_PLL is
|
||||
signal mdrdo: std_logic_vector(7 downto 0);
|
||||
signal lock: std_logic;
|
||||
signal wMdOpc: std_logic_vector(1 downto 0);
|
||||
signal wMdAInc: std_logic;
|
||||
signal wMdDIn: std_logic_vector(7 downto 0);
|
||||
signal wMdQOut: std_logic_vector(7 downto 0);
|
||||
signal pll_lock: std_logic;
|
||||
signal pll_rst: std_logic;
|
||||
|
||||
|
||||
component Gowin_PLL_MOD
|
||||
port (
|
||||
clkout0: out std_logic;
|
||||
lock: out std_logic;
|
||||
reset: in std_logic;
|
||||
mdrdo: out std_logic_vector(7 downto 0);
|
||||
clkin: in std_logic;
|
||||
mdclk: in std_logic;
|
||||
mdopc: in std_logic_vector(1 downto 0);
|
||||
mdainc: in std_logic;
|
||||
mdwdi: in std_logic_vector(7 downto 0)
|
||||
);
|
||||
end component;
|
||||
|
||||
|
||||
component PLL_INIT
|
||||
generic (
|
||||
CLK_PERIOD: INTEGER:= 20;
|
||||
MULTI_FAC: INTEGER:= 24
|
||||
);
|
||||
port (
|
||||
I_RST: in std_logic;
|
||||
O_RST: out std_logic;
|
||||
I_LOCK: in std_logic;
|
||||
O_LOCK: out std_logic;
|
||||
I_MD_CLK: in std_logic;
|
||||
O_MD_INC: out std_logic;
|
||||
O_MD_OPC: out std_logic_vector(1 downto 0);
|
||||
O_MD_WR_DATA: out std_logic_vector(7 downto 0);
|
||||
I_MD_RD_DATA: in std_logic_vector(7 downto 0);
|
||||
PLL_INIT_BYPASS: in std_logic;
|
||||
MDRDO: out std_logic_vector(7 downto 0);
|
||||
MDOPC: in std_logic_vector(1 downto 0);
|
||||
MDAINC: in std_logic;
|
||||
MDWDI: in std_logic_vector(7 downto 0)
|
||||
);
|
||||
end component;
|
||||
|
||||
|
||||
begin
|
||||
u_pll: Gowin_PLL_MOD
|
||||
port map (
|
||||
clkout0 => clkout0,
|
||||
lock => pll_lock,
|
||||
mdrdo => wMdQOut,
|
||||
clkin => clkin,
|
||||
reset => pll_rst,
|
||||
mdclk => mdclk,
|
||||
mdopc => wMdOpc,
|
||||
mdainc => wMdAInc,
|
||||
mdwdi => wMdDIn
|
||||
);
|
||||
|
||||
|
||||
u_pll_init: PLL_INIT
|
||||
generic map (
|
||||
CLK_PERIOD => 20,
|
||||
MULTI_FAC => 16
|
||||
)
|
||||
port map (
|
||||
I_RST => '0',
|
||||
O_RST => pll_rst,
|
||||
I_LOCK => pll_lock,
|
||||
O_LOCK => lock,
|
||||
I_MD_CLK => mdclk,
|
||||
O_MD_INC => wMdAInc,
|
||||
O_MD_OPC => wMdOpc,
|
||||
O_MD_WR_DATA => wMdDIn,
|
||||
I_MD_RD_DATA => wMdQOut,
|
||||
PLL_INIT_BYPASS => '0',
|
||||
MDRDO => mdrdo,
|
||||
MDOPC => "00",
|
||||
MDAINC => '0',
|
||||
MDWDI => "00000000"
|
||||
);
|
||||
|
||||
|
||||
end Behavioral; --Gowin_PLL
|
||||
@@ -0,0 +1,271 @@
|
||||
--Copyright (C)2014-2025 Gowin Semiconductor Corporation.
|
||||
--All rights reserved.
|
||||
--File Title: IP file
|
||||
--Tool Version: V1.9.12 (64-bit)
|
||||
--Part Number: GW5A-LV25MG121NC1/I0
|
||||
--Device: GW5A-25
|
||||
--Device Version: B
|
||||
--Created Time: Sat Sep 19 15:31:17 2026
|
||||
|
||||
library IEEE;
|
||||
use IEEE.std_logic_1164.all;
|
||||
|
||||
entity Gowin_PLL_MOD is
|
||||
port (
|
||||
lock: out std_logic;
|
||||
clkout0: out std_logic;
|
||||
mdrdo: out std_logic_vector(7 downto 0);
|
||||
clkin: in std_logic;
|
||||
reset: in std_logic;
|
||||
mdclk: in std_logic;
|
||||
mdopc: in std_logic_vector(1 downto 0);
|
||||
mdainc: in std_logic;
|
||||
mdwdi: in std_logic_vector(7 downto 0)
|
||||
);
|
||||
end Gowin_PLL_MOD;
|
||||
|
||||
architecture Behavioral of Gowin_PLL_MOD is
|
||||
|
||||
signal clkout1: std_logic;
|
||||
signal clkout2: std_logic;
|
||||
signal clkout3: std_logic;
|
||||
signal clkout4: std_logic;
|
||||
signal clkout5: std_logic;
|
||||
signal clkout6: std_logic;
|
||||
signal clkfbout: std_logic;
|
||||
signal gw_gnd: std_logic;
|
||||
signal PSSEL_i: std_logic_vector(2 downto 0);
|
||||
signal SSCMDSEL_i: std_logic_vector(6 downto 0);
|
||||
signal SSCMDSEL_FRAC_i: std_logic_vector(2 downto 0);
|
||||
|
||||
--component declaration
|
||||
component PLLA
|
||||
generic (
|
||||
FCLKIN: string := "100.0";
|
||||
IDIV_SEL: integer := 1;
|
||||
FBDIV_SEL: integer := 1;
|
||||
ODIV0_SEL: integer := 8;
|
||||
ODIV0_FRAC_SEL: integer := 0;
|
||||
ODIV1_SEL: integer := 8;
|
||||
ODIV2_SEL: integer := 8;
|
||||
ODIV3_SEL: integer := 8;
|
||||
ODIV4_SEL: integer := 8;
|
||||
ODIV5_SEL: integer := 8;
|
||||
ODIV6_SEL: integer := 8;
|
||||
MDIV_SEL: integer := 8;
|
||||
MDIV_FRAC_SEL: integer := 0;
|
||||
CLKOUT0_EN: string := "FALSE";
|
||||
CLKOUT1_EN: string := "FALSE";
|
||||
CLKOUT2_EN: string := "FALSE";
|
||||
CLKOUT3_EN: string := "FALSE";
|
||||
CLKOUT4_EN: string := "FALSE";
|
||||
CLKOUT5_EN: string := "FALSE";
|
||||
CLKOUT6_EN: string := "FALSE";
|
||||
CLKFB_SEL: string := "internal";
|
||||
CLKOUT0_DT_DIR: bit := '1';
|
||||
CLKOUT1_DT_DIR: bit := '1';
|
||||
CLKOUT2_DT_DIR: bit := '1';
|
||||
CLKOUT3_DT_DIR: bit := '1';
|
||||
CLKOUT0_DT_STEP: integer := 0;
|
||||
CLKOUT1_DT_STEP: integer := 0;
|
||||
CLKOUT2_DT_STEP: integer := 0;
|
||||
CLKOUT3_DT_STEP: integer := 0;
|
||||
CLK0_IN_SEL: bit := '0';
|
||||
CLK0_OUT_SEL: bit := '0';
|
||||
CLK1_IN_SEL: bit := '0';
|
||||
CLK1_OUT_SEL: bit := '0';
|
||||
CLK2_IN_SEL: bit := '0';
|
||||
CLK2_OUT_SEL: bit := '0';
|
||||
CLK3_IN_SEL: bit := '0';
|
||||
CLK3_OUT_SEL: bit := '0';
|
||||
CLK4_IN_SEL: bit_vector := "00";
|
||||
CLK4_OUT_SEL: bit := '0';
|
||||
CLK5_IN_SEL: bit := '0';
|
||||
CLK5_OUT_SEL: bit := '0';
|
||||
CLK6_IN_SEL: bit := '0';
|
||||
CLK6_OUT_SEL: bit := '0';
|
||||
CLKOUT0_PE_COARSE: integer := 0;
|
||||
CLKOUT0_PE_FINE: integer := 0;
|
||||
CLKOUT1_PE_COARSE: integer := 0;
|
||||
CLKOUT1_PE_FINE: integer := 0;
|
||||
CLKOUT2_PE_COARSE: integer := 0;
|
||||
CLKOUT2_PE_FINE: integer := 0;
|
||||
CLKOUT3_PE_COARSE: integer := 0;
|
||||
CLKOUT3_PE_FINE: integer := 0;
|
||||
CLKOUT4_PE_COARSE: integer := 0;
|
||||
CLKOUT4_PE_FINE: integer := 0;
|
||||
CLKOUT5_PE_COARSE: integer := 0;
|
||||
CLKOUT5_PE_FINE: integer := 0;
|
||||
CLKOUT6_PE_COARSE: integer := 0;
|
||||
CLKOUT6_PE_FINE: integer := 0;
|
||||
DE0_EN: string := "FALSE";
|
||||
DE1_EN: string := "FALSE";
|
||||
DE2_EN: string := "FALSE";
|
||||
DE3_EN: string := "FALSE";
|
||||
DE4_EN: string := "FALSE";
|
||||
DE5_EN: string := "FALSE";
|
||||
DE6_EN: string := "FALSE";
|
||||
DYN_DPA_EN: string := "FALSE";
|
||||
DYN_PE0_SEL: string := "FALSE";
|
||||
DYN_PE1_SEL: string := "FALSE";
|
||||
DYN_PE2_SEL: string := "FALSE";
|
||||
DYN_PE3_SEL: string := "FALSE";
|
||||
DYN_PE4_SEL: string := "FALSE";
|
||||
DYN_PE5_SEL: string := "FALSE";
|
||||
DYN_PE6_SEL: string := "FALSE";
|
||||
ICP_SEL : std_logic_vector(5 downto 0) := "XXXXXX";
|
||||
LPF_RES : std_logic_vector(2 downto 0) := "XXX";
|
||||
LPF_CAP: bit_vector := "00";
|
||||
RESET_I_EN: string := "FALSE";
|
||||
RESET_O_EN: string := "FALSE";
|
||||
SSC_EN: string := "FALSE"
|
||||
);
|
||||
port (
|
||||
LOCK: out std_logic;
|
||||
CLKOUT0: out std_logic;
|
||||
CLKOUT1: out std_logic;
|
||||
CLKOUT2: out std_logic;
|
||||
CLKOUT3: out std_logic;
|
||||
CLKOUT4: out std_logic;
|
||||
CLKOUT5: out std_logic;
|
||||
CLKOUT6: out std_logic;
|
||||
CLKFBOUT: out std_logic;
|
||||
MDRDO: out std_logic_vector(7 downto 0);
|
||||
CLKIN: in std_logic;
|
||||
CLKFB: in std_logic;
|
||||
RESET: in std_logic;
|
||||
PLLPWD: in std_logic;
|
||||
RESET_I: in std_logic;
|
||||
RESET_O: in std_logic;
|
||||
PSSEL: in std_logic_vector(2 downto 0);
|
||||
PSDIR: in std_logic;
|
||||
PSPULSE: in std_logic;
|
||||
SSCPOL: in std_logic;
|
||||
SSCON: in std_logic;
|
||||
SSCMDSEL: in std_logic_vector(6 downto 0);
|
||||
SSCMDSEL_FRAC: in std_logic_vector(2 downto 0);
|
||||
MDCLK: in std_logic;
|
||||
MDOPC: in std_logic_vector(1 downto 0);
|
||||
MDAINC: in std_logic;
|
||||
MDWDI: in std_logic_vector(7 downto 0)
|
||||
);
|
||||
end component;
|
||||
begin
|
||||
gw_gnd <= '0';
|
||||
|
||||
PSSEL_i <= gw_gnd & gw_gnd & gw_gnd;
|
||||
SSCMDSEL_i <= gw_gnd & gw_gnd & gw_gnd & gw_gnd & gw_gnd & gw_gnd & gw_gnd;
|
||||
SSCMDSEL_FRAC_i <= gw_gnd & gw_gnd & gw_gnd;
|
||||
|
||||
PLLA_inst: PLLA
|
||||
generic map (
|
||||
FCLKIN => "50",
|
||||
IDIV_SEL => 1,
|
||||
FBDIV_SEL => 1,
|
||||
ODIV0_SEL => 10,
|
||||
ODIV1_SEL => 8,
|
||||
ODIV2_SEL => 8,
|
||||
ODIV3_SEL => 8,
|
||||
ODIV4_SEL => 8,
|
||||
ODIV5_SEL => 8,
|
||||
ODIV6_SEL => 8,
|
||||
MDIV_SEL => 16,
|
||||
MDIV_FRAC_SEL => 0,
|
||||
ODIV0_FRAC_SEL => 0,
|
||||
CLKOUT0_EN => "TRUE",
|
||||
CLKOUT1_EN => "FALSE",
|
||||
CLKOUT2_EN => "FALSE",
|
||||
CLKOUT3_EN => "FALSE",
|
||||
CLKOUT4_EN => "FALSE",
|
||||
CLKOUT5_EN => "FALSE",
|
||||
CLKOUT6_EN => "FALSE",
|
||||
CLKFB_SEL => "INTERNAL",
|
||||
CLKOUT0_DT_DIR => '1',
|
||||
CLKOUT1_DT_DIR => '1',
|
||||
CLKOUT2_DT_DIR => '1',
|
||||
CLKOUT3_DT_DIR => '1',
|
||||
CLKOUT0_DT_STEP => 0,
|
||||
CLKOUT1_DT_STEP => 0,
|
||||
CLKOUT2_DT_STEP => 0,
|
||||
CLKOUT3_DT_STEP => 0,
|
||||
CLK0_IN_SEL => '0',
|
||||
CLK0_OUT_SEL => '0',
|
||||
CLK1_IN_SEL => '0',
|
||||
CLK1_OUT_SEL => '0',
|
||||
CLK2_IN_SEL => '0',
|
||||
CLK2_OUT_SEL => '0',
|
||||
CLK3_IN_SEL => '0',
|
||||
CLK3_OUT_SEL => '0',
|
||||
CLK4_IN_SEL => "00",
|
||||
CLK4_OUT_SEL => '0',
|
||||
CLK5_IN_SEL => '0',
|
||||
CLK5_OUT_SEL => '0',
|
||||
CLK6_IN_SEL => '0',
|
||||
CLK6_OUT_SEL => '0',
|
||||
DYN_DPA_EN => "FALSE",
|
||||
CLKOUT0_PE_COARSE => 0,
|
||||
CLKOUT0_PE_FINE => 0,
|
||||
CLKOUT1_PE_COARSE => 0,
|
||||
CLKOUT1_PE_FINE => 0,
|
||||
CLKOUT2_PE_COARSE => 0,
|
||||
CLKOUT2_PE_FINE => 0,
|
||||
CLKOUT3_PE_COARSE => 0,
|
||||
CLKOUT3_PE_FINE => 0,
|
||||
CLKOUT4_PE_COARSE => 0,
|
||||
CLKOUT4_PE_FINE => 0,
|
||||
CLKOUT5_PE_COARSE => 0,
|
||||
CLKOUT5_PE_FINE => 0,
|
||||
CLKOUT6_PE_COARSE => 0,
|
||||
CLKOUT6_PE_FINE => 0,
|
||||
DYN_PE0_SEL => "FALSE",
|
||||
DYN_PE1_SEL => "FALSE",
|
||||
DYN_PE2_SEL => "FALSE",
|
||||
DYN_PE3_SEL => "FALSE",
|
||||
DYN_PE4_SEL => "FALSE",
|
||||
DYN_PE5_SEL => "FALSE",
|
||||
DYN_PE6_SEL => "FALSE",
|
||||
DE0_EN => "FALSE",
|
||||
DE1_EN => "FALSE",
|
||||
DE2_EN => "FALSE",
|
||||
DE3_EN => "FALSE",
|
||||
DE4_EN => "FALSE",
|
||||
DE5_EN => "FALSE",
|
||||
DE6_EN => "FALSE",
|
||||
RESET_I_EN => "FALSE",
|
||||
RESET_O_EN => "FALSE",
|
||||
ICP_SEL => "XXXXXX",
|
||||
LPF_RES => "XXX",
|
||||
LPF_CAP => "00",
|
||||
SSC_EN => "FALSE"
|
||||
)
|
||||
port map (
|
||||
LOCK => lock,
|
||||
CLKOUT0 => clkout0,
|
||||
CLKOUT1 => clkout1,
|
||||
CLKOUT2 => clkout2,
|
||||
CLKOUT3 => clkout3,
|
||||
CLKOUT4 => clkout4,
|
||||
CLKOUT5 => clkout5,
|
||||
CLKOUT6 => clkout6,
|
||||
CLKFBOUT => clkfbout,
|
||||
MDRDO => mdrdo,
|
||||
CLKIN => clkin,
|
||||
CLKFB => gw_gnd,
|
||||
RESET => reset,
|
||||
PLLPWD => gw_gnd,
|
||||
RESET_I => gw_gnd,
|
||||
RESET_O => gw_gnd,
|
||||
PSSEL => PSSEL_i,
|
||||
PSDIR => gw_gnd,
|
||||
PSPULSE => gw_gnd,
|
||||
SSCPOL => gw_gnd,
|
||||
SSCON => gw_gnd,
|
||||
SSCMDSEL => SSCMDSEL_i,
|
||||
SSCMDSEL_FRAC => SSCMDSEL_FRAC_i,
|
||||
MDCLK => mdclk,
|
||||
MDOPC => mdopc,
|
||||
MDAINC => mdainc,
|
||||
MDWDI => mdwdi
|
||||
);
|
||||
|
||||
end Behavioral; --Gowin_PLL_MOD
|
||||
@@ -0,0 +1,134 @@
|
||||
`timescale 1ns / 1ps
|
||||
// =============================================================================
|
||||
// Module: i2s_transmitter
|
||||
// Description: Standard I2S Audio Transmitter for PCM5102A DAC.
|
||||
// - Fully free-running clock generation (guaranteed immediate BCK/LRCK).
|
||||
// - 64 BCK cycles per stereo frame (32 bits per channel).
|
||||
// - 24-bit audio sample depth with zero padding.
|
||||
// - Transmits MSB first with 1-cycle I2S delay after LRCK edges.
|
||||
// =============================================================================
|
||||
|
||||
module i2s_transmitter #(
|
||||
parameter integer CLK_FREQ_HZ = 50000000,
|
||||
parameter integer SAMPLE_RATE_HZ = 48000,
|
||||
parameter integer DATA_WIDTH = 24
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst_n,
|
||||
input wire signed [23:0] data_left, // Left channel audio sample
|
||||
input wire signed [23:0] data_right, // Right channel audio sample
|
||||
output wire bck, // Bit Clock to DAC
|
||||
output wire lrck, // Word Select (Left/Right) to DAC
|
||||
output wire din, // Serial Data to DAC
|
||||
output reg sample_tick, // Sample rate strobe to update DDS
|
||||
output wire [5:0] dbg_bit_cnt, // debug only - unconnected in dac_top
|
||||
output wire dbg_bck_fall // debug only - unconnected in dac_top
|
||||
);
|
||||
|
||||
// Calculate clock divider for BCK half-period
|
||||
// f_bck = SAMPLE_RATE_HZ * 64
|
||||
// BCK_HALF_PERIOD = CLK_FREQ_HZ / (f_bck * 2)
|
||||
localparam integer CLK_DIV = CLK_FREQ_HZ / (SAMPLE_RATE_HZ * 64 * 2);
|
||||
localparam integer CNT_WIDTH = (CLK_DIV > 1) ? $clog2(CLK_DIV + 1) : 1;
|
||||
|
||||
reg [CNT_WIDTH-1:0] clk_cnt = {CNT_WIDTH{1'b0}};
|
||||
reg bck_reg = 1'b0;
|
||||
reg lrck_reg = 1'b0;
|
||||
reg din_reg = 1'b0;
|
||||
|
||||
reg bck_fall_pulse = 1'b0;
|
||||
reg [5:0] bit_cnt = 6'd0; // 0 to 63 (64 bits per stereo frame)
|
||||
reg [31:0] shift_reg = 32'd0;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 1. Clock divider generating BCK (Unconditional free-running)
|
||||
// -------------------------------------------------------------------------
|
||||
always @(posedge clk) begin
|
||||
bck_fall_pulse <= 1'b0;
|
||||
if (clk_cnt >= CLK_DIV - 1) begin
|
||||
clk_cnt <= {CNT_WIDTH{1'b0}};
|
||||
bck_reg <= ~bck_reg;
|
||||
// Falling edge strobe when bck goes 1 -> 0
|
||||
if (bck_reg == 1'b1) begin
|
||||
bck_fall_pulse <= 1'b1;
|
||||
end
|
||||
end else begin
|
||||
clk_cnt <= clk_cnt + 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2. I2S Serial Frame State Machine (updates on BCK falling edge)
|
||||
// -------------------------------------------------------------------------
|
||||
always @(posedge clk) begin
|
||||
sample_tick <= 1'b0; // Default: single-cycle pulse
|
||||
|
||||
if (!rst_n) begin
|
||||
bit_cnt <= 6'd0;
|
||||
lrck_reg <= 1'b0;
|
||||
din_reg <= 1'b0;
|
||||
shift_reg <= 32'd0;
|
||||
end else if (bck_fall_pulse) begin
|
||||
if (bit_cnt == 6'd63) begin
|
||||
bit_cnt <= 6'd0;
|
||||
end else begin
|
||||
bit_cnt <= bit_cnt + 1'b1;
|
||||
end
|
||||
|
||||
case (bit_cnt)
|
||||
// --- Left Channel (LRCK = 0) ---
|
||||
6'd0: begin
|
||||
lrck_reg <= 1'b0; // Start Left channel
|
||||
din_reg <= 1'b0; // 1-bit I2S delay
|
||||
shift_reg <= {data_left, 8'd0}; // Latch 24-bit left sample
|
||||
end
|
||||
|
||||
6'd1, 6'd2, 6'd3, 6'd4, 6'd5, 6'd6, 6'd7, 6'd8,
|
||||
6'd9, 6'd10, 6'd11, 6'd12, 6'd13, 6'd14, 6'd15, 6'd16,
|
||||
6'd17, 6'd18, 6'd19, 6'd20, 6'd21, 6'd22, 6'd23, 6'd24: begin
|
||||
din_reg <= shift_reg[31]; // Transmit MSB first
|
||||
shift_reg <= {shift_reg[30:0], 1'b0};
|
||||
end
|
||||
|
||||
6'd25, 6'd26, 6'd27, 6'd28, 6'd29, 6'd30, 6'd31: begin
|
||||
din_reg <= 1'b0; // Zero padding for remaining bits
|
||||
shift_reg <= {shift_reg[30:0], 1'b0};
|
||||
end
|
||||
|
||||
// --- Right Channel (LRCK = 1) ---
|
||||
6'd32: begin
|
||||
lrck_reg <= 1'b1; // Start Right channel
|
||||
din_reg <= 1'b0; // 1-bit I2S delay
|
||||
shift_reg <= {data_right, 8'd0}; // Latch 24-bit right sample
|
||||
end
|
||||
|
||||
6'd33, 6'd34, 6'd35, 6'd36, 6'd37, 6'd38, 6'd39, 6'd40,
|
||||
6'd41, 6'd42, 6'd43, 6'd44, 6'd45, 6'd46, 6'd47, 6'd48,
|
||||
6'd49, 6'd50, 6'd51, 6'd52, 6'd53, 6'd54, 6'd55, 6'd56: begin
|
||||
din_reg <= shift_reg[31]; // Transmit MSB first
|
||||
shift_reg <= {shift_reg[30:0], 1'b0};
|
||||
end
|
||||
|
||||
6'd57, 6'd58, 6'd59, 6'd60, 6'd61, 6'd62: begin
|
||||
din_reg <= 1'b0; // Zero padding
|
||||
shift_reg <= {shift_reg[30:0], 1'b0};
|
||||
end
|
||||
|
||||
6'd63: begin
|
||||
din_reg <= 1'b0;
|
||||
sample_tick <= 1'b1; // Signal DDS to prepare next sample
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// Output assignments
|
||||
assign bck = bck_reg;
|
||||
assign lrck = lrck_reg;
|
||||
assign din = din_reg;
|
||||
|
||||
// Debug taps for the diagnostic build; dac_top leaves these unconnected.
|
||||
assign dbg_bit_cnt = bit_cnt;
|
||||
assign dbg_bck_fall = bck_fall_pulse;
|
||||
|
||||
endmodule
|
||||
+13
-4
@@ -12,6 +12,12 @@
|
||||
-- MIC_CLK и держит его всю низкую фазу, при SELECT = '1' -- по фронту и всю
|
||||
-- высокую. Защёлкиваем в середине нужной фазы: там есть запас и на задержку
|
||||
-- выдачи, и на два такта синхронизатора.
|
||||
--
|
||||
-- Если микрофон подключён через линию (LVDS и т.п.), её задержка туда и
|
||||
-- обратно сдвигает момент смены данных, и точку выборки задаёт SMP_CNT:
|
||||
-- такт периода MIC_CLK (0..CLK_DIV-1), на котором берётся выход
|
||||
-- синхронизатора. Смена данных, пришедшая между тактами k и k+1, видна на
|
||||
-- выходе синхронизатора с такта k+3; брать бит надо подальше от этого места.
|
||||
----------------------------------------------------------------------------------
|
||||
library ieee;
|
||||
use ieee.std_logic_1164.all;
|
||||
@@ -20,7 +26,8 @@ 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
|
||||
SEL_HIGH : std_logic := '0'; -- уровень на выводе SELECT
|
||||
SMP_CNT : integer := -1 -- точка выборки; -1 -- по SEL_HIGH, микрофон рядом
|
||||
);
|
||||
port (
|
||||
clk : in std_logic;
|
||||
@@ -40,14 +47,16 @@ architecture rtl of pdm_rx is
|
||||
-- Середина фазы, в которой микрофон держит данные, плюс два такта синхронизатора
|
||||
function sample_point return integer is
|
||||
begin
|
||||
if SEL_HIGH = '1' then
|
||||
if SMP_CNT >= 0 then
|
||||
return SMP_CNT;
|
||||
elsif 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;
|
||||
constant SMP_AT : integer := sample_point;
|
||||
|
||||
signal cnt : integer range 0 to CLK_DIV - 1 := 0;
|
||||
signal clk_r : std_logic := '0';
|
||||
@@ -83,7 +92,7 @@ begin
|
||||
clk_r <= '1';
|
||||
end if;
|
||||
|
||||
if cnt = SMP_CNT then
|
||||
if cnt = SMP_AT then
|
||||
bit_out <= sync(1);
|
||||
bit_valid <= '1';
|
||||
end if;
|
||||
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
`timescale 1ns/1ns
|
||||
|
||||
|
||||
module PLL_INIT #
|
||||
( parameter CLK_PERIOD = 20
|
||||
, parameter MULTI_FAC = 24
|
||||
)
|
||||
( input I_RST
|
||||
, input I_MD_CLK
|
||||
|
||||
, output O_RST
|
||||
|
||||
, output O_MD_INC
|
||||
, output [1:0] O_MD_OPC
|
||||
, output [7:0] O_MD_WR_DATA
|
||||
, input [7:0] I_MD_RD_DATA
|
||||
|
||||
, input I_LOCK
|
||||
, output O_LOCK
|
||||
|
||||
, input PLL_INIT_BYPASS
|
||||
, output [7:0] MDRDO
|
||||
, input [1:0] MDOPC
|
||||
, input MDAINC
|
||||
, input [7:0] MDWDI
|
||||
|
||||
);
|
||||
|
||||
`ifdef SIM
|
||||
`define DL #1
|
||||
`else
|
||||
`define DL
|
||||
`endif
|
||||
|
||||
`ifdef SIM
|
||||
localparam WAIT_TIME = 'd8_000;
|
||||
localparam LOCK_TIME = 'd2_000;
|
||||
`else
|
||||
localparam WAIT_TIME = 'd800_000;
|
||||
localparam LOCK_TIME = 'd200_000;
|
||||
`endif
|
||||
localparam WAIT_CNT = (WAIT_TIME + CLK_PERIOD - 1) / CLK_PERIOD; //! 1ms
|
||||
localparam LOCK_CNT = (LOCK_TIME + CLK_PERIOD - 1) / CLK_PERIOD;
|
||||
localparam WW = $clog2(WAIT_CNT + 1);
|
||||
localparam LW = $clog2(LOCK_CNT + 1);
|
||||
|
||||
localparam INIT_STEP_0 = 4'd0
|
||||
, INIT_STEP_1 = 4'd1
|
||||
, INIT_STEP_2 = 4'd2
|
||||
, INIT_STEP_3 = 4'd3
|
||||
, INIT_STEP_4 = 4'd4
|
||||
, INIT_STEP_5 = 4'd5
|
||||
, INIT_STEP_6 = 4'd6
|
||||
, INIT_STEP_7 = 4'd7
|
||||
, INIT_STEP_8 = 4'd8
|
||||
, INIT_STEP_9 = 4'd9
|
||||
;
|
||||
|
||||
reg [ 1:0] rEnable = 'b0;
|
||||
|
||||
reg rRomRd = 'b0;
|
||||
reg [ 7:0] rRomAddr = 'b0;
|
||||
reg [ 24:0] rRomData = 'b0;
|
||||
|
||||
reg [ 2:0] rReqCnt = 'b0;
|
||||
reg [ 4:0] rLoopCnt = 'b0;
|
||||
|
||||
reg [ 3:0] rChkOkCnt = 'b0;
|
||||
reg [ 5:0] rChkStart = 'b0;
|
||||
|
||||
reg [ 3:0] rInitFsmC = INIT_STEP_0 ;
|
||||
reg [ 3:0] rInitFsmN = INIT_STEP_1 ;
|
||||
|
||||
reg [ 7:0] rMdAddr = 'b0;
|
||||
|
||||
reg rMdInc = 'b0;
|
||||
reg [ 1:0] rMdOpc = 'b0;
|
||||
reg [ 7:0] rMdDOut = 'b0;
|
||||
|
||||
reg [ 7:0] rChkFlag = 'b0;
|
||||
|
||||
reg rLockReg = 'b0;
|
||||
reg [LW-1:0] rLockCnt = 'b0;
|
||||
reg rLocked = 'b0;
|
||||
reg [WW-1:0] rWaitCnt = 'b0;
|
||||
|
||||
reg rLastStep = 'b0;
|
||||
|
||||
reg rLockOut = 'b0;
|
||||
|
||||
reg rChkVld = 'b0;
|
||||
reg [ 7:0] rRomAddrVld = 'b0;
|
||||
|
||||
reg rRstOut = 1'b1;
|
||||
|
||||
wire wEnable = rEnable[1];
|
||||
wire [ 7:0] wMdAddrNext = rMdAddr + 2'd1;
|
||||
wire wLocked = (rLockCnt >= LOCK_CNT);
|
||||
|
||||
wire wIsStep0 = (rInitFsmC == INIT_STEP_0);
|
||||
wire wIsStep1 = (rInitFsmC == INIT_STEP_1);
|
||||
wire wIsStep2 = (rInitFsmC == INIT_STEP_2);
|
||||
wire wIsStep3 = (rInitFsmC == INIT_STEP_3);
|
||||
wire wIsStep4 = (rInitFsmC == INIT_STEP_4);
|
||||
wire wIsStep5 = (rInitFsmC == INIT_STEP_5);
|
||||
wire wIsStep6 = (rInitFsmC == INIT_STEP_6);
|
||||
wire wIsStep7 = (rInitFsmC == INIT_STEP_7);
|
||||
wire wIsStep8 = (rInitFsmC == INIT_STEP_8);
|
||||
wire wIsStep9 = (rInitFsmC == INIT_STEP_9);
|
||||
|
||||
wire wReqOver = ~rRomData[24];
|
||||
wire wMdrpReq = rRomData[24];
|
||||
wire wWaitLock = rReqCnt[2];//(rReqCnt == 4);
|
||||
|
||||
|
||||
wire [ 7:0] wReqAddr = rRomData[16+:8];
|
||||
wire [ 7:0] wReqMask = rRomData[ 8+:8];
|
||||
wire [ 7:0] wReqData = rRomData[ 0+:8];
|
||||
|
||||
|
||||
localparam ROM_FILE = "";
|
||||
|
||||
reg [ 27:0] rRomDReg = 'b0;
|
||||
|
||||
|
||||
reg [27:0] rRom [63:0];
|
||||
|
||||
|
||||
always @ (posedge PLL_INIT_BYPASS or posedge I_MD_CLK or posedge I_RST) begin
|
||||
if (PLL_INIT_BYPASS || I_RST) rEnable <=`DL 2'b00;
|
||||
else rEnable <=`DL {rEnable[0], 1'b1};
|
||||
end
|
||||
|
||||
generate
|
||||
if (ROM_FILE != "") begin: ram_init_file
|
||||
initial begin
|
||||
$readmemh (ROM_FILE, rRom);
|
||||
end
|
||||
|
||||
always @ (posedge I_MD_CLK) begin
|
||||
rRomDReg <=`DL rRom[rRomAddr[5:0]];
|
||||
end
|
||||
end else begin : ram_init
|
||||
always @ (posedge I_MD_CLK) begin
|
||||
if (~rEnable[1]) begin
|
||||
rRomDReg <=`DL 28'h000_0000;
|
||||
//1
|
||||
rRom[00] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F03
|
||||
: (MULTI_FAC > 16) ? 28'h10B_3F01
|
||||
: 28'h10B_3F00;
|
||||
rRom[01] <=`DL 28'h10C_E080;
|
||||
rRom[02] <=`DL 28'h111_0701;
|
||||
rRom[03] <=`DL 28'h112_0C08;
|
||||
//2
|
||||
rRom[04] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F03
|
||||
: (MULTI_FAC > 16) ? 28'h10B_3F01
|
||||
: 28'h10B_3F00;
|
||||
rRom[05] <=`DL 28'h10C_E080;
|
||||
rRom[06] <=`DL 28'h111_0703;
|
||||
rRom[07] <=`DL 28'h112_0C08;
|
||||
//3
|
||||
rRom[08] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F03
|
||||
: (MULTI_FAC > 16) ? 28'h10B_3F01
|
||||
: 28'h10B_3F00;
|
||||
rRom[09] <=`DL 28'h10C_E0A0;
|
||||
rRom[10] <=`DL 28'h111_0703;
|
||||
rRom[11] <=`DL 28'h112_0C08;
|
||||
//4
|
||||
rRom[12] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F03
|
||||
: (MULTI_FAC > 16) ? 28'h10B_3F01
|
||||
: 28'h10B_3F00;
|
||||
rRom[13] <=`DL 28'h10C_E0A0;
|
||||
rRom[14] <=`DL 28'h111_0707;
|
||||
rRom[15] <=`DL 28'h112_0C08;
|
||||
//5
|
||||
rRom[16] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F07
|
||||
: (MULTI_FAC > 16) ? 28'h10B_3F03
|
||||
: 28'h10B_3F01;
|
||||
rRom[17] <=`DL 28'h10C_E0A0;
|
||||
rRom[18] <=`DL 28'h111_0707;
|
||||
rRom[19] <=`DL 28'h112_0C08;
|
||||
//6
|
||||
rRom[20] <=`DL (MULTI_FAC > 34) ? 28'h10B_3F05
|
||||
: (MULTI_FAC > 16) ? 28'h10B_3F02
|
||||
: 28'h10B_3F01;
|
||||
rRom[21] <=`DL 28'h10C_E0C0;
|
||||
rRom[22] <=`DL 28'h111_0707;
|
||||
rRom[23] <=`DL 28'h112_0C08;
|
||||
//over
|
||||
rRom[24] <=`DL 28'h000_0000;
|
||||
rRom[25] <=`DL 28'h000_0000;
|
||||
rRom[26] <=`DL 28'h000_0000;
|
||||
rRom[27] <=`DL 28'h000_0000;
|
||||
//1.5
|
||||
rRom[28] <=`DL (MULTI_FAC > 34) ? 28'h10B3F03
|
||||
: (MULTI_FAC > 16) ? 28'h10B3F01
|
||||
: 28'h10B3F00;
|
||||
rRom[29] <=`DL 28'h10CE080;
|
||||
rRom[30] <=`DL 28'h1110702;
|
||||
rRom[31] <=`DL 28'h1120C08;
|
||||
//2.5
|
||||
rRom[32] <=`DL (MULTI_FAC > 34) ? 28'h10B3F05
|
||||
: (MULTI_FAC > 16) ? 28'h10B3F02
|
||||
: 28'h10B3F00;
|
||||
rRom[33] <=`DL 28'h10CE080;
|
||||
rRom[34] <=`DL 28'h1110703;
|
||||
rRom[35] <=`DL 28'h1120C08;
|
||||
//3.5
|
||||
rRom[36] <=`DL (MULTI_FAC > 34) ? 28'h10B3F03
|
||||
: (MULTI_FAC > 16) ? 28'h10B3F01
|
||||
: 28'h10B3F00;
|
||||
rRom[37] <=`DL 28'h10CE0A0;
|
||||
rRom[38] <=`DL 28'h1110704;
|
||||
rRom[39] <=`DL 28'h112_0C08;
|
||||
|
||||
//4.5
|
||||
rRom[40] <=`DL (MULTI_FAC > 34) ? 28'h10B3F04
|
||||
: (MULTI_FAC > 16) ? 28'h10B3F02
|
||||
: 28'h10B3F00;
|
||||
rRom[41] <=`DL 28'h10CE0A0;
|
||||
rRom[42] <=`DL 28'h1110707;
|
||||
rRom[43] <=`DL 28'h1120C08;
|
||||
|
||||
//5.5
|
||||
rRom[44] <=`DL (MULTI_FAC > 34) ? 28'h10B3F03
|
||||
: (MULTI_FAC > 16) ? 28'h10B3F01
|
||||
: 28'h10B3F00;
|
||||
rRom[45] <=`DL 28'h10CE0C0;
|
||||
rRom[46] <=`DL 28'h1110707;
|
||||
rRom[47] <=`DL 28'h1120C08;
|
||||
|
||||
rRom[48] <=`DL 28'h000_0000;
|
||||
rRom[49] <=`DL 28'h000_0000;
|
||||
rRom[50] <=`DL 28'h000_0000;
|
||||
rRom[51] <=`DL 28'h000_0000;
|
||||
rRom[52] <=`DL 28'h000_0000;
|
||||
rRom[53] <=`DL 28'h000_0000;
|
||||
rRom[54] <=`DL 28'h000_0000;
|
||||
rRom[55] <=`DL 28'h000_0000;
|
||||
rRom[56] <=`DL 28'h000_0000;
|
||||
rRom[57] <=`DL 28'h000_0000;
|
||||
rRom[58] <=`DL 28'h000_0000;
|
||||
rRom[59] <=`DL 28'h000_0000;
|
||||
rRom[60] <=`DL 28'h000_0000;
|
||||
rRom[61] <=`DL 28'h000_0000;
|
||||
rRom[62] <=`DL 28'h000_0000;
|
||||
rRom[63] <=`DL 28'h000_0000;
|
||||
|
||||
|
||||
end else
|
||||
rRomDReg <=`DL rRom[rRomAddr[5:0]];
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
|
||||
always @ (*) begin
|
||||
casex (rChkFlag[5:0])
|
||||
6'b111_111 : {rChkVld, rRomAddrVld} = 9'b1_0000_1000;
|
||||
6'b011_111 : {rChkVld, rRomAddrVld} = 9'b1_0000_1000;
|
||||
6'b111_110 : {rChkVld, rRomAddrVld} = 9'b1_0000_1100;
|
||||
6'b111_10x : {rChkVld, rRomAddrVld} = 9'b1_0000_1100;
|
||||
6'bx01_111 : {rChkVld, rRomAddrVld} = 9'b1_0000_0100;
|
||||
6'b011_110 : {rChkVld, rRomAddrVld} = 9'b1_0000_1000;
|
||||
6'bxx0_111 : {rChkVld, rRomAddrVld} = 9'b1_0000_0100;
|
||||
6'bx01_110 : {rChkVld, rRomAddrVld} = 9'b1_0000_1000;
|
||||
6'b011_10x : {rChkVld, rRomAddrVld} = 9'b1_0000_1100;
|
||||
6'b111_0xx : {rChkVld, rRomAddrVld} = 9'b1_0001_0000;
|
||||
|
||||
6'bxxx_011 : {rChkVld, rRomAddrVld} = 9'b1_0001_1100;
|
||||
6'bxx0_110 : {rChkVld, rRomAddrVld} = 9'b1_0010_0000;
|
||||
6'bx01_100 : {rChkVld, rRomAddrVld} = 9'b1_0010_0100;
|
||||
6'b011_000 : {rChkVld, rRomAddrVld} = 9'b1_0010_1000;
|
||||
6'b110_000 : {rChkVld, rRomAddrVld} = 9'b1_0010_1100;
|
||||
6'bxxx_x01 : {rChkVld, rRomAddrVld} = 9'b1_0000_0000;
|
||||
6'bxxx_010 : {rChkVld, rRomAddrVld} = 9'b1_0000_0100;
|
||||
6'bxx0_100 : {rChkVld, rRomAddrVld} = 9'b1_0000_1000;
|
||||
6'bx01_000 : {rChkVld, rRomAddrVld} = 9'b1_0000_1100;
|
||||
6'b010_000 : {rChkVld, rRomAddrVld} = 9'b1_0001_0000;
|
||||
6'b100_000 : {rChkVld, rRomAddrVld} = 9'b1_0001_0100;
|
||||
|
||||
6'b000_000 : {rChkVld, rRomAddrVld} = 9'b0_0000_0000;
|
||||
|
||||
default : {rChkVld, rRomAddrVld} = 9'b0_0000_0000;
|
||||
endcase
|
||||
end
|
||||
|
||||
always @ (posedge I_MD_CLK) begin
|
||||
rInitFsmC <=`DL wEnable ? rInitFsmN : INIT_STEP_0;
|
||||
//!_____________________________________________________________________________
|
||||
rRomRd <=`DL wEnable & (wIsStep1 | wIsStep6 & ~wWaitLock);
|
||||
//!_____________________________________________________________________________
|
||||
rRomData <=`DL rRomRd ? rRomDReg[24:0] : rRomData;
|
||||
//!_____________________________________________________________________________
|
||||
rRomAddr <=`DL ~wEnable ? 8'd0
|
||||
: (wIsStep3 & wReqOver) ? rRomAddrVld
|
||||
: rRomRd ? rRomAddr + 1'd1
|
||||
: rRomAddr;
|
||||
//!_____________________________________________________________________________
|
||||
rLastStep <=`DL wEnable & (wIsStep3 & wReqOver | rLastStep);
|
||||
//!_____________________________________________________________________________
|
||||
rReqCnt <=`DL ~wEnable ? 3'd0
|
||||
: wIsStep7 ? 3'd0
|
||||
: wIsStep5 ? rReqCnt + 1'd1
|
||||
: rReqCnt;
|
||||
//!_____________________________________________________________________________
|
||||
rLoopCnt <=`DL ~wEnable ? 3'd0
|
||||
: wIsStep8 ? rLoopCnt + 1'd1
|
||||
: rLoopCnt;
|
||||
//!_____________________________________________________________________________
|
||||
rLockReg <=`DL wEnable & I_LOCK;
|
||||
//!_____________________________________________________________________________
|
||||
rLockCnt <=`DL (~wEnable) ? {LW{1'b0}}
|
||||
: (~wIsStep7&~wIsStep9) ? {LW{1'b0}}
|
||||
: (~rLockReg) ? {LW{1'b0}}
|
||||
: (rLockCnt<LOCK_CNT) ? rLockCnt + 1'd1
|
||||
: rLockCnt;
|
||||
//!_____________________________________________________________________________
|
||||
rLocked <=`DL wLocked;
|
||||
//!_____________________________________________________________________________
|
||||
rWaitCnt <=`DL wIsStep3 ? WAIT_CNT[0+:WW]
|
||||
: ~wIsStep7 ? rWaitCnt
|
||||
: (|rWaitCnt) ? rWaitCnt - 1'd1
|
||||
: {WW{1'b0}};
|
||||
//!_____________________________________________________________________________
|
||||
if (~wEnable) rChkFlag <=`DL 8'h00;
|
||||
else if (wIsStep7 & rLocked) rChkFlag[rLoopCnt[2:0]] <=`DL 1'b1;
|
||||
//!_____________________________________________________________________________
|
||||
rChkOkCnt <=`DL ~wEnable ? 4'd0
|
||||
: ~wIsStep7 ? rChkOkCnt
|
||||
: (rWaitCnt==0) ? 4'd0
|
||||
: rLocked ? rChkOkCnt + 1'd1
|
||||
: rChkOkCnt;
|
||||
end
|
||||
|
||||
always @(*) begin
|
||||
if (~wEnable) rInitFsmN =`DL INIT_STEP_1;
|
||||
else case (rInitFsmC) /* synthesis full_case */
|
||||
INIT_STEP_0 : rInitFsmN =`DL INIT_STEP_1;
|
||||
INIT_STEP_1 : rInitFsmN =`DL INIT_STEP_2;
|
||||
INIT_STEP_2 : rInitFsmN =`DL INIT_STEP_3;
|
||||
INIT_STEP_3 : rInitFsmN =`DL wWaitLock ? INIT_STEP_7
|
||||
: wReqOver & rLastStep ? INIT_STEP_9
|
||||
: wReqOver &~rLastStep ? INIT_STEP_1
|
||||
: (rMdAddr >= wReqAddr) ? INIT_STEP_4
|
||||
: INIT_STEP_3;
|
||||
INIT_STEP_4 : rInitFsmN =`DL INIT_STEP_5;
|
||||
INIT_STEP_5 : rInitFsmN =`DL INIT_STEP_6;
|
||||
INIT_STEP_6 : rInitFsmN =`DL INIT_STEP_2;
|
||||
INIT_STEP_7 : rInitFsmN =`DL (rWaitCnt==0) ? INIT_STEP_8
|
||||
: ~rLocked ? INIT_STEP_7
|
||||
: rLastStep ? INIT_STEP_9
|
||||
: INIT_STEP_8;
|
||||
INIT_STEP_8 : rInitFsmN =`DL ~rLastStep ? INIT_STEP_1
|
||||
: rChkVld ? INIT_STEP_1
|
||||
: INIT_STEP_9;
|
||||
INIT_STEP_9 : rInitFsmN =`DL INIT_STEP_9;
|
||||
default : rInitFsmN =`DL INIT_STEP_9;
|
||||
endcase
|
||||
end
|
||||
|
||||
always @ (posedge I_MD_CLK) begin
|
||||
rMdOpc <=`DL ~wEnable ? 2'b00
|
||||
: wIsStep7 ? 2'b00
|
||||
: wIsStep5 ? 2'b01
|
||||
: 2'b10;
|
||||
//!_____________________________________________________________________________
|
||||
rMdInc <=`DL wIsStep3 & (~rMdInc ? (rMdAddr<wReqAddr)
|
||||
: (wMdAddrNext < wReqAddr));
|
||||
//!_____________________________________________________________________________
|
||||
rMdAddr <=`DL ~wEnable ? 8'd0
|
||||
: wIsStep7 ? 8'd0
|
||||
: rMdInc ? wMdAddrNext
|
||||
: rMdAddr;
|
||||
//!_____________________________________________________________________________
|
||||
rMdDOut <=`DL wIsStep5 ? (I_MD_RD_DATA&~wReqMask|wReqData) : rMdDOut;
|
||||
//!_____________________________________________________________________________
|
||||
rRstOut <=`DL ~wEnable | (~wIsStep7 & ~wIsStep9);
|
||||
//!_____________________________________________________________________________
|
||||
rLockOut <=`DL rLocked & wIsStep9 & rChkVld;
|
||||
end
|
||||
|
||||
|
||||
/////////////mdrp switch//////////////////////////
|
||||
assign O_LOCK = (PLL_INIT_BYPASS == 1'b1) ? I_LOCK : rLockOut;
|
||||
assign O_RST = (PLL_INIT_BYPASS == 1'b1) ? I_RST : rRstOut;
|
||||
assign O_MD_INC = (PLL_INIT_BYPASS == 1'b1) ? MDAINC : rMdInc;
|
||||
assign O_MD_OPC = (PLL_INIT_BYPASS == 1'b1) ? MDOPC : rMdOpc;
|
||||
assign O_MD_WR_DATA= (PLL_INIT_BYPASS == 1'b1) ? MDWDI : rMdDOut;
|
||||
assign MDRDO = (PLL_INIT_BYPASS == 1'b1) ? I_MD_RD_DATA : 8'b0000_0000;
|
||||
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
endmodule//: PLL_INIT
|
||||
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
`timescale 1ns / 1ps
|
||||
// =============================================================================
|
||||
// Module: tb_dac_stream
|
||||
// Project Name: DigMic_prj
|
||||
// Description:
|
||||
// Тест тракта на ЦАП: отсчёты 12 500 Гц -> dac_stream -> I2S на выводах ->
|
||||
// разбор слов в тестбенче, как это делает PCM5102A (по фронту BCK, MSB
|
||||
// через один BCK после смены LRCK).
|
||||
//
|
||||
// Каждое слово левого канала сверяется с эталоном двумя способами:
|
||||
// точно -- та же целочисленная интерполяция, что должна быть в ПЛИС,
|
||||
// с тем же моментом отсчёта; ловит ошибки разрядности, знака и сдвига;
|
||||
// приближённо -- интерполяция в вещественных числах; ловит ошибку в
|
||||
// самой формуле доли интервала.
|
||||
// Правый канал должен совпасть с левым. Отдельно меряются периоды SCK,
|
||||
// BCK и LRCK.
|
||||
//
|
||||
// WAVE = 0 -- синус AMP на FREQ; WAVE = 1 -- меандр между крайними кодами
|
||||
// шкалы, самые большие разности соседних отсчётов.
|
||||
//
|
||||
// Усиление: GAIN -- постоянное значение на входе gain. GAIN_STEP > 0 --
|
||||
// каждые GAIN_STEP входных отсчётов gain растёт на 1 по кругу 0..15, то есть
|
||||
// заходит и за GAIN_MAX: там dac_stream обязан ограничить сдвиг. Эталон берёт
|
||||
// то усиление, что действовало в такт взятия каждого отсчёта.
|
||||
// Разобранные отсчёты пишутся в OUT_FILE для анализа спектра.
|
||||
//
|
||||
// iverilog -g2012 -o build/tb_dac_stream tb/tb_dac_stream.v src/dac_stream.v src/i2s_transmitter.v
|
||||
// vvp build/tb_dac_stream
|
||||
// =============================================================================
|
||||
|
||||
module tb_dac_stream;
|
||||
|
||||
parameter integer GAIN = 0;
|
||||
parameter integer GAIN_MAX = 8;
|
||||
parameter integer GAIN_STEP = 0;
|
||||
parameter integer WAVE = 0;
|
||||
parameter real AMP = 0.5; // доля шкалы
|
||||
parameter real FREQ = 1000.0;
|
||||
parameter integer N_WORDS = 2500; // сколько слов ЦАП проверить
|
||||
parameter OUT_FILE = "build/dac_stream_out.txt";
|
||||
|
||||
localparam integer CLK_HZ = 50000000;
|
||||
localparam integer IN_HZ = 12500;
|
||||
localparam integer IN_PERIOD = CLK_HZ / IN_HZ;
|
||||
localparam integer RECIP = (1 << 26) / IN_PERIOD;
|
||||
localparam integer FS_MAX = 8388607;
|
||||
localparam integer FS_MIN = -8388608;
|
||||
|
||||
// От фронта clk, на котором сменился LRCK, назад до такта, чьё состояние
|
||||
// попало в слово: 16 тактов от sample_tick до начала кадра и 3 ступени
|
||||
// конвейера интерполяции
|
||||
localparam integer LAT = 19;
|
||||
|
||||
reg clk = 1'b0;
|
||||
always #10 clk = ~clk;
|
||||
|
||||
integer cyc = 0; // номер текущего фронта clk
|
||||
always @(posedge clk) cyc <= cyc + 1;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Источник отсчётов: строб раз в IN_PERIOD тактов, выставляется по спаду
|
||||
// -------------------------------------------------------------------------
|
||||
reg [23:0] din = 24'd0;
|
||||
reg din_valid = 1'b0;
|
||||
integer ph = IN_PERIOD - 1234; // произвольная фаза относительно I2S
|
||||
integer k_in = 0;
|
||||
|
||||
function integer gen(input integer k);
|
||||
real v;
|
||||
begin
|
||||
if (WAVE == 0) begin
|
||||
v = AMP * FS_MAX * $sin(2.0 * 3.14159265358979 * FREQ * k / IN_HZ);
|
||||
gen = (v >= 0.0) ? $rtoi(v + 0.5) : -$rtoi(-v + 0.5);
|
||||
end else begin
|
||||
gen = ((k / 7) % 2) ? FS_MIN : FS_MAX;
|
||||
end
|
||||
end
|
||||
endfunction
|
||||
|
||||
function integer sat_gain(input integer x, input integer g);
|
||||
reg signed [63:0] w;
|
||||
begin
|
||||
w = x;
|
||||
w = w <<< ((g > GAIN_MAX) ? GAIN_MAX : g);
|
||||
if (w > FS_MAX) sat_gain = FS_MAX;
|
||||
else if (w < FS_MIN) sat_gain = FS_MIN;
|
||||
else sat_gain = w;
|
||||
end
|
||||
endfunction
|
||||
|
||||
reg [3:0] gain = GAIN;
|
||||
|
||||
always @(negedge clk) begin
|
||||
din_valid <= 1'b0;
|
||||
if (ph == IN_PERIOD - 1) begin
|
||||
ph <= 0;
|
||||
din <= gen(k_in);
|
||||
din_valid <= 1'b1;
|
||||
k_in <= k_in + 1;
|
||||
end else begin
|
||||
ph <= ph + 1;
|
||||
// Смена усиления в середине интервала между отсчётами
|
||||
if (GAIN_STEP > 0 && ph == IN_PERIOD / 2 && k_in % GAIN_STEP == 0)
|
||||
gain <= gain + 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
// История входа: на каком фронте ПЛИС взяла отсчёт и что получила после усиления
|
||||
integer hist_t [0:8191];
|
||||
integer hist_x [0:8191];
|
||||
integer n_hist = 0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (din_valid) begin
|
||||
hist_t[n_hist] = cyc;
|
||||
hist_x[n_hist] = sat_gain($signed(din), gain);
|
||||
n_hist = n_hist + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Эталон: что должно быть на выходе по состоянию после фронта s
|
||||
// -------------------------------------------------------------------------
|
||||
// Состояние кладётся в e_cur, e_prev, e_cnt: они же нужны для
|
||||
// вещественного эталона и сообщений об ошибке
|
||||
integer e_cur, e_prev, e_cnt;
|
||||
|
||||
function integer ref_exact(input integer s);
|
||||
integer i;
|
||||
reg signed [63:0] d, f, p;
|
||||
begin
|
||||
i = n_hist - 1;
|
||||
while (i >= 0 && hist_t[i] > s)
|
||||
i = i - 1;
|
||||
e_cur = (i >= 0) ? hist_x[i] : 0;
|
||||
e_prev = (i >= 1) ? hist_x[i - 1] : 0;
|
||||
e_cnt = (i >= 0) ? s - hist_t[i] : s;
|
||||
if (e_cnt > IN_PERIOD - 1)
|
||||
e_cnt = IN_PERIOD - 1;
|
||||
|
||||
d = e_cur - e_prev;
|
||||
f = (e_cnt * RECIP) >>> 10;
|
||||
p = (d * f + 32768) >>> 16;
|
||||
ref_exact = e_prev + p;
|
||||
end
|
||||
endfunction
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Устройство
|
||||
// -------------------------------------------------------------------------
|
||||
wire i2s_bck, i2s_lrck, i2s_din, dac_sck;
|
||||
|
||||
dac_stream #(
|
||||
.CLK_FREQ_HZ (CLK_HZ),
|
||||
.IN_RATE_HZ (IN_HZ),
|
||||
.SAMPLE_RATE_HZ (48000),
|
||||
.GAIN_MAX (GAIN_MAX)
|
||||
) dut (
|
||||
.clk (clk),
|
||||
.din (din),
|
||||
.din_valid (din_valid),
|
||||
.gain (gain),
|
||||
.i2s_bck (i2s_bck),
|
||||
.i2s_lrck (i2s_lrck),
|
||||
.i2s_din (i2s_din),
|
||||
.dac_sck (dac_sck)
|
||||
);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Начало кадра: спад LRCK, в тактах clk
|
||||
// -------------------------------------------------------------------------
|
||||
reg lrck_d = 1'b0;
|
||||
integer p0 = -1; // фронт clk, на котором LRCK ушёл в 0
|
||||
integer p0_prev = -1;
|
||||
integer errors = 0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
lrck_d <= i2s_lrck;
|
||||
if (lrck_d && !i2s_lrck) begin
|
||||
p0_prev = p0;
|
||||
p0 = cyc - 1;
|
||||
if (p0_prev >= 0 && p0 - p0_prev != 1024) begin
|
||||
$display("ERROR: LRCK period %0d clk, want 1024", p0 - p0_prev);
|
||||
errors = errors + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Периоды SCK и BCK
|
||||
integer t_sck = -1, t_bck = -1;
|
||||
always @(posedge dac_sck) begin
|
||||
if (t_sck >= 0 && cyc - t_sck != 4) begin
|
||||
$display("ERROR: SCK period %0d clk, want 4", cyc - t_sck);
|
||||
errors = errors + 1;
|
||||
end
|
||||
t_sck = cyc;
|
||||
end
|
||||
always @(posedge i2s_bck) begin
|
||||
if (t_bck >= 0 && cyc - t_bck != 16) begin
|
||||
$display("ERROR: BCK period %0d clk, want 16", cyc - t_bck);
|
||||
errors = errors + 1;
|
||||
end
|
||||
t_bck = cyc;
|
||||
end
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Приём I2S как в ЦАП
|
||||
// -------------------------------------------------------------------------
|
||||
reg lr_b = 1'b0;
|
||||
integer bitn = 99;
|
||||
reg [23:0] sh = 24'd0;
|
||||
integer left = 0;
|
||||
integer n_w = 0;
|
||||
integer n_chk = 0;
|
||||
integer max_dev = 0;
|
||||
integer fd;
|
||||
|
||||
integer got, want, dev;
|
||||
real ideal, tol;
|
||||
|
||||
always @(posedge i2s_bck) begin
|
||||
if (i2s_lrck !== lr_b) begin
|
||||
lr_b = i2s_lrck;
|
||||
bitn = 0; // бит задержки I2S
|
||||
end else if (bitn < 24) begin
|
||||
sh = {sh[22:0], i2s_din};
|
||||
bitn = bitn + 1;
|
||||
if (bitn == 24) begin
|
||||
got = $signed(sh);
|
||||
if (!lr_b) begin
|
||||
left = got;
|
||||
n_w = n_w + 1;
|
||||
// Первые кадры пропускаем: там ещё нет двух отсчётов в истории
|
||||
if (n_hist >= 3) begin
|
||||
want = ref_exact(p0 - LAT);
|
||||
if (got !== want) begin
|
||||
if (errors < 10)
|
||||
$display("ERROR: word %0d at clk %0d: got %0d, want %0d (cnt %0d)",
|
||||
n_w, p0, got, want, e_cnt);
|
||||
errors = errors + 1;
|
||||
end
|
||||
ideal = e_prev + (e_cur - e_prev) * (e_cnt * 1.0 / IN_PERIOD);
|
||||
tol = ((e_cur > e_prev) ? e_cur - e_prev : e_prev - e_cur) * 3.0e-5 + 1.0;
|
||||
dev = (got > ideal) ? $rtoi(got - ideal + 0.5) : $rtoi(ideal - got + 0.5);
|
||||
if (dev > max_dev)
|
||||
max_dev = dev;
|
||||
if (dev > tol) begin
|
||||
if (errors < 10)
|
||||
$display("ERROR: word %0d: got %0d, ideal %0f, tol %0f",
|
||||
n_w, got, ideal, tol);
|
||||
errors = errors + 1;
|
||||
end
|
||||
n_chk = n_chk + 1;
|
||||
$fdisplay(fd, "%0d %0d", p0, got);
|
||||
end
|
||||
end else if (got !== left) begin
|
||||
$display("ERROR: right %0d != left %0d", got, left);
|
||||
errors = errors + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
initial begin
|
||||
fd = $fopen(OUT_FILE, "w");
|
||||
$display("tb_dac_stream: GAIN=%0d GAIN_MAX=%0d GAIN_STEP=%0d WAVE=%0d AMP=%0f",
|
||||
GAIN, GAIN_MAX, GAIN_STEP, WAVE, AMP);
|
||||
wait (n_chk >= N_WORDS);
|
||||
$fclose(fd);
|
||||
$display("checked %0d words, inputs %0d, max deviation from ideal %0d LSB",
|
||||
n_chk, n_hist, max_dev);
|
||||
if (errors == 0)
|
||||
$display("TEST PASSED");
|
||||
else
|
||||
$display("TEST FAILED: errors = %0d", errors);
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,183 @@
|
||||
----------------------------------------------------------------------------------
|
||||
-- Module Name: tb_gain_button
|
||||
-- Project Name: DigMic_prj
|
||||
-- Description:
|
||||
-- Тест кнопки усиления ЦАП. Такт уменьшен до 100 кГц: показ длится секунды,
|
||||
-- а в миллисекундах модуль считает сам, так что поведение то же.
|
||||
--
|
||||
-- Проверяется:
|
||||
-- импульс короче времени дребезга -- не нажатие;
|
||||
-- дребезг на обоих фронтах даёт ровно один шаг, отпускание -- ни одного;
|
||||
-- ступени идут по кругу 1..GAIN_MAX -> 0;
|
||||
-- долгое удержание -- один шаг, без автоповтора;
|
||||
-- показ: N коротких вспышек, для 0 одна длинная, потом светодиод свободен;
|
||||
-- нажатие во время показа начинает показ заново с новым значением.
|
||||
----------------------------------------------------------------------------------
|
||||
library ieee;
|
||||
use ieee.std_logic_1164.all;
|
||||
use ieee.numeric_std.all;
|
||||
use std.env.all;
|
||||
|
||||
entity tb_gain_button is
|
||||
end entity tb_gain_button;
|
||||
|
||||
architecture sim of tb_gain_button is
|
||||
|
||||
constant CLK_HZ : integer := 100_000;
|
||||
constant CLK_T : time := 1 sec / CLK_HZ;
|
||||
constant GMAX : integer := 3;
|
||||
|
||||
signal clk : std_logic := '0';
|
||||
signal run : boolean := true;
|
||||
signal btn : std_logic := '0';
|
||||
signal gain : unsigned(3 downto 0);
|
||||
signal show : std_logic;
|
||||
signal led : std_logic;
|
||||
|
||||
begin
|
||||
|
||||
clk <= not clk after CLK_T / 2 when run else '0';
|
||||
|
||||
dut : entity work.gain_button(rtl)
|
||||
generic map(
|
||||
SYS_CLK_HZ => CLK_HZ,
|
||||
GAIN_MAX => GMAX,
|
||||
BTN_ACTIVE => '1',
|
||||
DEBOUNCE_MS => 20
|
||||
)
|
||||
port map
|
||||
(
|
||||
clk => clk,
|
||||
btn => btn,
|
||||
gain => gain,
|
||||
show => show,
|
||||
led => led
|
||||
);
|
||||
|
||||
stim_proc : process
|
||||
variable errors : integer := 0;
|
||||
variable want : integer := 0;
|
||||
|
||||
-- Фронт кнопки с дребезгом: четыре отскока по 1-2 мс
|
||||
procedure bounce(level : std_logic) is
|
||||
begin
|
||||
for i in 1 to 4 loop
|
||||
btn <= level;
|
||||
wait for 2 ms;
|
||||
btn <= not level;
|
||||
wait for 1 ms;
|
||||
end loop;
|
||||
btn <= level;
|
||||
end procedure;
|
||||
|
||||
procedure press is
|
||||
begin
|
||||
bounce('1');
|
||||
wait for 100 ms;
|
||||
bounce('0');
|
||||
wait for 50 ms;
|
||||
end procedure;
|
||||
|
||||
procedure check_gain(expect : integer; what : string) is
|
||||
begin
|
||||
if to_integer(gain) /= expect then
|
||||
report what & ": gain " & integer'image(to_integer(gain)) &
|
||||
", want " & integer'image(expect) severity error;
|
||||
errors := errors + 1;
|
||||
end if;
|
||||
end procedure;
|
||||
|
||||
-- Вспышки до конца показа: для 0 одна длинная, иначе expect коротких
|
||||
procedure check_flashes(expect : integer) is
|
||||
variable n : integer := 0;
|
||||
variable t0 : time;
|
||||
variable w : time;
|
||||
variable wn : time;
|
||||
begin
|
||||
if expect = 0 then
|
||||
wn := 1000 ms;
|
||||
else
|
||||
wn := 150 ms;
|
||||
end if;
|
||||
if show /= '1' then
|
||||
report "no display after press" severity error;
|
||||
errors := errors + 1;
|
||||
return;
|
||||
end if;
|
||||
loop
|
||||
wait until led = '1' or show = '0';
|
||||
exit when show = '0';
|
||||
t0 := now;
|
||||
wait until led = '0';
|
||||
w := now - t0;
|
||||
n := n + 1;
|
||||
if w < wn - 3 ms or w > wn + 3 ms then
|
||||
report "flash " & integer'image(n) & " lasts " & time'image(w) &
|
||||
", want " & time'image(wn) severity error;
|
||||
errors := errors + 1;
|
||||
end if;
|
||||
end loop;
|
||||
if (expect = 0 and n /= 1) or (expect > 0 and n /= expect) then
|
||||
report "flashes " & integer'image(n) & " for gain " &
|
||||
integer'image(expect) severity error;
|
||||
errors := errors + 1;
|
||||
else
|
||||
report "gain " & integer'image(expect) & ": " & integer'image(n) &
|
||||
" flash(es) of " & time'image(wn);
|
||||
end if;
|
||||
end procedure;
|
||||
|
||||
begin
|
||||
wait for 50 ms;
|
||||
check_gain(0, "after power-up");
|
||||
assert show = '0' report "display active without a press" severity error;
|
||||
|
||||
-- Короткий импульс -- помеха, не нажатие
|
||||
btn <= '1';
|
||||
wait for 5 ms;
|
||||
btn <= '0';
|
||||
wait for 100 ms;
|
||||
check_gain(0, "after 5 ms glitch");
|
||||
if show /= '0' then
|
||||
report "glitch started a display" severity error;
|
||||
errors := errors + 1;
|
||||
end if;
|
||||
|
||||
-- По кругу: 1, 2, 3, 0, 1
|
||||
for k in 1 to GMAX + 2 loop
|
||||
press;
|
||||
want := (want + 1) mod (GMAX + 1);
|
||||
check_gain(want, "press " & integer'image(k));
|
||||
check_flashes(want);
|
||||
end loop;
|
||||
|
||||
-- Долгое удержание -- один шаг
|
||||
bounce('1');
|
||||
wait for 2 sec;
|
||||
bounce('0');
|
||||
wait for 50 ms;
|
||||
want := (want + 1) mod (GMAX + 1);
|
||||
check_gain(want, "long hold");
|
||||
if show = '1' then
|
||||
wait until show = '0';
|
||||
end if;
|
||||
|
||||
-- Нажатие посреди показа: показ заново, с новым значением
|
||||
press;
|
||||
want := (want + 1) mod (GMAX + 1);
|
||||
wait for 700 ms;
|
||||
press;
|
||||
want := (want + 1) mod (GMAX + 1);
|
||||
check_gain(want, "press during display");
|
||||
check_flashes(want);
|
||||
|
||||
if errors = 0 then
|
||||
report "TEST PASSED";
|
||||
else
|
||||
report "TEST FAILED: errors = " & integer'image(errors) severity error;
|
||||
end if;
|
||||
run <= false;
|
||||
stop;
|
||||
end process stim_proc;
|
||||
|
||||
end architecture sim;
|
||||
+37
-10
@@ -14,6 +14,12 @@
|
||||
-- Тракт настраивается около 10 мс (запаздывание ФНЧ, заполнение FIFO),
|
||||
-- поэтому в каждом режиме кадры читаются до первого сошедшегося, но не
|
||||
-- больше MAX_TRY.
|
||||
--
|
||||
-- Микрофон подключён как на железе, через линию LVDS: задержка в обе
|
||||
-- стороны и удержание уровня, пока микрофон отпускает линию. Задержка
|
||||
-- подобрана так, чтобы смена данных приходилась на ту же фазу MIC_CLK, что
|
||||
-- измерена на плате (между тактами 1 и 2 из 16), -- тогда проверяется ровно
|
||||
-- та точка выборки, что стоит в Top.
|
||||
----------------------------------------------------------------------------------
|
||||
library ieee;
|
||||
use ieee.std_logic_1164.all;
|
||||
@@ -38,12 +44,21 @@ architecture sim of tb_top is
|
||||
|
||||
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 mic_sel : std_logic;
|
||||
signal uart_tx : std_logic;
|
||||
signal led : std_logic;
|
||||
|
||||
@@ -104,12 +119,12 @@ begin
|
||||
)
|
||||
port map
|
||||
(
|
||||
clk => clk,
|
||||
MIC_DAT => mic_dat,
|
||||
MIC_CLK => mic_clk,
|
||||
MIC_SEL => mic_sel,
|
||||
UART_TX => uart_tx,
|
||||
LED => led
|
||||
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)
|
||||
@@ -121,11 +136,24 @@ begin
|
||||
)
|
||||
port map
|
||||
(
|
||||
mic_clk => mic_clk,
|
||||
mic_clk => mic_clk_far,
|
||||
mode => mode,
|
||||
mic_dat => mic_dat
|
||||
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);
|
||||
@@ -144,7 +172,6 @@ begin
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user