From 2672f8b8a8aecc2d640fee9e6b6674c9d5d87545 Mon Sep 17 00:00:00 2001 From: gromov_ia <1keyten1@gmail.com> Date: Fri, 2 Oct 2026 13:52:23 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BD=D0=B4:=20=D0=A6=D0=90?= =?UTF-8?q?=D0=9F=20PCM5102A,=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=83=D1=81=D0=B8=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F,=20=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=B8=D1=8F=20LVDS,=20UART=20=D1=87=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B7=20BL616?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - вторая ветвь тракта: отсчёты ФНЧ -> пересчёт в 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. --- DigMic.gprj | 22 +- impl/DigMic_process_config.json | 93 ++++++++ src/DigMic.cst | 24 +- src/Top.vhd | 132 ++++++++++- src/dac_stream.v | 179 ++++++++++++++ src/gain_button.vhd | 150 ++++++++++++ src/gowin_pll/gowin_pll.ipc | 147 ++++++++++++ src/gowin_pll/gowin_pll.mod | 44 ++++ src/gowin_pll/gowin_pll.vhd | 102 ++++++++ src/gowin_pll/gowin_pll_mod.vhd | 271 ++++++++++++++++++++++ src/i2s_transmitter.v | 134 +++++++++++ src/pdm_rx.vhd | 17 +- src/pll_init.v | 397 ++++++++++++++++++++++++++++++++ tb/tb_dac_stream.v | 279 ++++++++++++++++++++++ tb/tb_gain_button.vhd | 183 +++++++++++++++ tb/tb_top.vhd | 47 +++- 16 files changed, 2181 insertions(+), 40 deletions(-) create mode 100644 impl/DigMic_process_config.json create mode 100644 src/dac_stream.v create mode 100644 src/gain_button.vhd create mode 100644 src/gowin_pll/gowin_pll.ipc create mode 100644 src/gowin_pll/gowin_pll.mod create mode 100644 src/gowin_pll/gowin_pll.vhd create mode 100644 src/gowin_pll/gowin_pll_mod.vhd create mode 100644 src/i2s_transmitter.v create mode 100644 src/pll_init.v create mode 100644 tb/tb_dac_stream.v create mode 100644 tb/tb_gain_button.vhd diff --git a/DigMic.gprj b/DigMic.gprj index 723b692..8394c02 100644 --- a/DigMic.gprj +++ b/DigMic.gprj @@ -3,17 +3,23 @@ 5 - gw5a25b-003 + gw5a25a-002 - - - - - - - + + + + + + + + + + + + + diff --git a/impl/DigMic_process_config.json b/impl/DigMic_process_config.json new file mode 100644 index 0000000..48b5b9e --- /dev/null +++ b/impl/DigMic_process_config.json @@ -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 +} \ No newline at end of file diff --git a/src/DigMic.cst b/src/DigMic.cst index 14533cd..3db65f6 100644 --- a/src/DigMic.cst +++ b/src/DigMic.cst @@ -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; diff --git a/src/Top.vhd b/src/Top.vhd index 333f1bf..4a1f3fb 100644 --- a/src/Top.vhd +++ b/src/Top.vhd @@ -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( diff --git a/src/dac_stream.v b/src/dac_stream.v new file mode 100644 index 0000000..2b7a368 --- /dev/null +++ b/src/dac_stream.v @@ -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 diff --git a/src/gain_button.vhd b/src/gain_button.vhd new file mode 100644 index 0000000..e778ca6 --- /dev/null +++ b/src/gain_button.vhd @@ -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; diff --git a/src/gowin_pll/gowin_pll.ipc b/src/gowin_pll/gowin_pll.ipc new file mode 100644 index 0000000..9109dca --- /dev/null +++ b/src/gowin_pll/gowin_pll.ipc @@ -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 diff --git a/src/gowin_pll/gowin_pll.mod b/src/gowin_pll/gowin_pll.mod new file mode 100644 index 0000000..3d2efe1 --- /dev/null +++ b/src/gowin_pll/gowin_pll.mod @@ -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 \ No newline at end of file diff --git a/src/gowin_pll/gowin_pll.vhd b/src/gowin_pll/gowin_pll.vhd new file mode 100644 index 0000000..cfa3c56 --- /dev/null +++ b/src/gowin_pll/gowin_pll.vhd @@ -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 diff --git a/src/gowin_pll/gowin_pll_mod.vhd b/src/gowin_pll/gowin_pll_mod.vhd new file mode 100644 index 0000000..e0d5db6 --- /dev/null +++ b/src/gowin_pll/gowin_pll_mod.vhd @@ -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 diff --git a/src/i2s_transmitter.v b/src/i2s_transmitter.v new file mode 100644 index 0000000..7ecc0dc --- /dev/null +++ b/src/i2s_transmitter.v @@ -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 diff --git a/src/pdm_rx.vhd b/src/pdm_rx.vhd index a215f16..8770387 100644 --- a/src/pdm_rx.vhd +++ b/src/pdm_rx.vhd @@ -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; diff --git a/src/pll_init.v b/src/pll_init.v new file mode 100644 index 0000000..26af0ea --- /dev/null +++ b/src/pll_init.v @@ -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= 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 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 diff --git a/tb/tb_gain_button.vhd b/tb/tb_gain_button.vhd new file mode 100644 index 0000000..630c3cd --- /dev/null +++ b/tb/tb_gain_button.vhd @@ -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; diff --git a/tb/tb_top.vhd b/tb/tb_top.vhd index 0c19ce6..be876c9 100644 --- a/tb/tb_top.vhd +++ b/tb/tb_top.vhd @@ -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