VHDL实现ADC串口采集FSM中check_count状态时长异常求助
VHDL 24位ADC串口采集FSM状态异常问题排查
我是VHDL新手,要实现ADC的24位串口数据采集功能:该串口包含24位串行数据线路、新数据就绪指示DRDY信号,以及上升沿推送数据的串行时钟SCLK,系统持续运行。需求是准确采集24位采样数据,转换为并行总线输出,同时给后续模块提供“数据有效”信号。因为系统时钟是串口时钟的4倍,我用有限状态机(FSM)实现,状态包括init、wait_drdy、wait_sclk、inc_count、check_count,通过计数器cnt判断是否完成24位采集。
但行为仿真中发现check_count状态的持续时间远超过预期的1个系统时钟周期,导致错过后续SCLK脉冲,无法采集下一位数据。我尝试把计数器cnt改成FSM进程内部的整数变量,问题依旧,求指点原因。
FSM及信号图

VHDL代码
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity serial_ads1675 is Port ( clk : in STD_LOGIC; reset : in STD_LOGIC; sclk : in std_logic; sdata : in std_logic; drdy : in std_logic; pdata : out std_logic_vector(23 downto 0); pdready : out std_logic ); end serial_ads1675; architecture Behavioral of serial_ads1675 is -- Internal declarations signal ipdata : std_logic_vector (23 downto 0); signal ipdready : std_logic; signal tmp1, tmp2, tmp3, tmp4 : std_logic; signal rise_drdy, rise_sclk : std_logic; signal cnt : unsigned (4 downto 0); type state is (init, wait_drdy, wait_sclk, inc_count, check_count); signal actual_state, next_state : state; begin -- Concurrent statements pdata <= ipdata; pdready <= ipdready; rise_drdy <= '1' when ((tmp1 = '1') and (tmp2 = '0')) else '0'; rise_sclk <= '1' when ((tmp3 = '1') and (tmp4 = '0')) else '0'; -- Process for edge detection process (clk, reset) begin if(reset = '0') then tmp1 <= '0'; tmp2 <= '0'; tmp3 <= '0'; tmp4 <= '0'; elsif (falling_edge(clk)) then tmp1 <= drdy; tmp2 <= tmp1; tmp3 <= sclk; tmp4 <= tmp3; end if; end process; -- State register process process (reset, clk) begin if (reset = '0') then actual_state <= init; elsif (rising_edge(clk)) then actual_state <= next_state; end if; end process; -- Next State affectation process process (rise_sclk, rise_drdy) begin case actual_state is when init => next_state <= wait_drdy; ipdata <= (others => '0'); ipdready <= '0'; cnt <= (others => '0'); when wait_drdy => if (rise_drdy = '0') then next_state <= actual_state; else next_state <= wait_sclk; end if; cnt <= (others => '0'); when wait_sclk => if (rise_sclk = '0') then next_state <= actual_state; else next_state <= inc_count; end if; ipdready <= '0'; when inc_count => next_state <= check_count; cnt <= cnt + 1; ipdready <= '0'; ipdata(23 downto 1) <= ipdata(22 downto 0); ipdata(0) <= sdata; when check_count => case cnt is when "11000" => next_state <= wait_drdy; ipdready <= '1'; when others => next_state <= wait_sclk; ipdready <= '0'; end case; when others => next_state <= init; end case; end process; end Behavioral;
行为仿真波形图

问题原因分析
- 敏感列表不完整:下一状态进程的敏感列表仅包含
rise_sclk和rise_drdy,但进程中使用了actual_state和cnt信号。这两个信号的变化无法触发进程更新,导致check_count状态下,只有当rise_sclk或rise_drdy变化时才会执行状态判断,而多数时间进程不会响应,状态长时间停滞。 - 组合逻辑驱动寄存器违规:在组合逻辑进程中直接驱动
cnt、ipdata、ipdready等寄存器类型信号,会引发竞争冒险,且不符合同步设计规范。这类信号必须在时钟边沿触发的时序进程中更新。 - 计数器与状态不同步:
cnt在组合逻辑中更新,没有与系统时钟同步,导致计数器值变化滞后于状态转换,进一步加剧状态判断的延迟。
修正方案
- 补全敏感列表:将下一状态进程的敏感列表改为
actual_state, rise_sclk, rise_drdy, cnt,确保所有影响状态转换的信号变化都能触发进程。 - 分离组合与时序逻辑:仅在组合逻辑中计算
next_state,将cnt、ipdata、ipdready的更新移到时钟触发的时序进程中,保证同步操作。 - 同步计数器更新:将
cnt的增量操作放到时序进程内,与状态转换同步,确保每个系统时钟周期仅更新一次。
修正后的核心代码片段:
-- 组合逻辑:仅计算next_state process (actual_state, rise_sclk, rise_drdy, cnt) begin next_state <= actual_state; -- 默认保持当前状态 case actual_state is when init => next_state <= wait_drdy; when wait_drdy => if rise_drdy = '1' then next_state <= wait_sclk; end if; when wait_sclk => if rise_sclk = '1' then next_state <= inc_count; end if; when inc_count => next_state <= check_count; when check_count => if cnt = "11000" then next_state <= wait_drdy; else next_state <= wait_sclk; end if; when others => next_state <= init; end case; end process; -- 时序逻辑:更新所有寄存器 process (clk, reset) begin if reset = '0' then actual_state <= init; ipdata <= (others => '0'); ipdready <= '0'; cnt <= (others => '0'); elsif rising_edge(clk) then actual_state <= next_state; case actual_state is when init => ipdata <= (others => '0'); ipdready <= '0'; cnt <= (others => '0'); when wait_drdy => if rise_drdy = '1' then cnt <= (others => '0'); end if; when inc_count => cnt <= cnt + 1; ipdata <= ipdata(22 downto 0) & sdata; -- 左移加载新数据 ipdready <= '0'; when check_count => ipdready <= '1' when cnt = "11000" else '0'; when others => null; end case; end if; end process;
内容的提问来源于stack exchange,提问作者Mostrillo
相关产品推荐
相关产品推荐

