有限状态机(VHDL)+反馈中的无意锁存器

时间:2013-12-28 14:55:10

标签: vhdl state-machine

该项目是关于在FPGA板“spartan 6 lx9”上向MicroBlaze项目添加用户自定义外设核心。使用ISE Design Suite 14.6和EDK。

我的问题是在编写VHDL代码方面经验不足。我仍然在信号上获得1位无意的锁存:来自< 0>的“data_bits”和“latest_value”。直到< 15>,即使我已经使用推荐的编码样式进行信号分配。我已经设置了默认值,但没有成功...在case语句的每个分支中分配信号不是一个选项,因为我想保留值,尤其是“data_bits”,因为这个向量是从几个时钟周期构建的。我试图解决这个问题好几天了。

我的问题是:

  1. 如何在这种有限状态机设计中修复锁存问题?的 - 回答
  2. 我想获得有关我的状态机设计,样式等的反馈。 - 已回答,但有新代码
  3. 任何设计建议,使用计数器或更好的技术,在一个状态下停留几个时钟周期? - 仍然期待一些建议
  4. 初始源代码:

    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    
    entity adc_16bit is
    port(
            clk                 : in std_logic; 
            rst                 : in std_logic;         
            data_reg_out        : out std_logic_vector(31 downto 0);
            control_reg         : in std_logic_vector(31 downto 0);
    
            SDO                 : in std_logic;
            SCK                 : out std_logic;
            CONV                    : out std_logic
    );
       end adc_16bit;
    
        architecture Behavioral of adc_16bit is
    type adc_states is (idle, conversation, clocking_low, clocking_high, receiving_bit, update_data);
    signal State, NextState : adc_states;
    
    signal data_bits                : std_logic_vector(15 downto 0) := (others => '0');
    signal latest_value         : std_logic_vector(15 downto 0) := (others => '0');
    
    signal conv_cnt                 : integer range 0 to 501 := 0;
    signal clk_cnt                  : integer range 0 to 14 := 0;
    signal bit_cnt                  : integer range 0 to 17 := 0;
    
        begin
    
    ----------------------------------------------------------------------------------------
    -- State Machine Register
    ----------------------------------------------------------------------------------------
    StateReg:
    process(clk, rst)
    begin
        if(clk'event and clk = '1') then
            if(rst = '0') then
                State <= idle;
            else
                State <= NextState;
            end if;
        end if;
    end process StateReg;
    
    ----------------------------------------------------------------------------------------
    -- Signals Register
    ----------------------------------------------------------------------------------------
    TimerReg:
    process(clk, rst)
    begin
        if(clk'event and clk = '1') then
            --!default
            conv_cnt <= conv_cnt;
            clk_cnt <= clk_cnt;
            bit_cnt <= bit_cnt;
    
            --latest_value <= latest_value;
            --data_bits <= data_bits;
    
            case State is
    
                when idle =>
                    conv_cnt <= 0;
                    clk_cnt <= 0;
                    bit_cnt <= 0;
    
                when conversation =>
                    if(conv_cnt = 501) then
                        conv_cnt <= 0;
                    else
                        conv_cnt <= conv_cnt + 1;
                    end if;
    
                when clocking_low =>
                    if(clk_cnt = 14) then
                        clk_cnt <= 0;
                    else
                        clk_cnt <= clk_cnt + 1;
                    end if;
    
                when clocking_high =>
                    if(clk_cnt = 14) then
                        clk_cnt <= 0;
                    else
                        clk_cnt <= clk_cnt + 1;
                    end if;
    
                when receiving_bit =>
                    if(bit_cnt = 16) then
                        bit_cnt <= 0;
                    else
                        bit_cnt <= bit_cnt + 1;
                    end if;
    
                when update_data =>
    
            end case;
        end if;
    end process TimerReg;
    
    ----------------------------------------------------------------------------------------
    -- FSM Logic
    ----------------------------------------------------------------------------------------
    FSM_Proc:
    process(State, control_reg, conv_cnt, clk_cnt, bit_cnt )
    begin
        case State is
    
            when idle =>
                if(control_reg(0) = '1') then
                    NextState <= conversation;
                else
                    NextState <= idle;
                end if;
    
            when conversation => 
                if(conv_cnt = 500) then
                    NextState <= clocking_low;
                else
                    NextState <= conversation;
                end if;
    
            when clocking_low =>
                if(clk_cnt = 13) then
                    NextState <= clocking_high;
                else
                    NextState <= clocking_low;
                end if;
    
            when clocking_high =>
                if(clk_cnt = 13) then
                    NextState <= receiving_bit;
                else
                    NextState <= clocking_high;
                end if;
    
            when receiving_bit =>
                if(bit_cnt = 15) then
                    NextState <= update_data;
                else
                    NextState <= clocking_low;
                end if;
    
            when update_data =>
                if(control_reg(0) = '1') then
                    NextState <= conversation;
                else
                    NextState <= idle;
                end if;
    
        end case;
    end process FSM_Proc;
    
    ----------------------------------------------------------------------------------------
    -- FSM Output
    ----------------------------------------------------------------------------------------
    FSM_Output:
    process(NextState, latest_value, data_bits, bit_cnt, SDO )
    begin
        --!default
        CONV <= '0';
        SCK <= '0';
        data_reg_out(31 downto 16) <= (others => '0');
        data_reg_out(15 downto 0) <= latest_value;
    
        --data_bits <= data_bits;
        --latest_value <= latest_value;
    
    
        case NextState is
            when idle =>
                latest_value <= (others => '0');
                data_bits <= (others => '0');
    
            when conversation =>
                CONV <= '1';
    
            when clocking_low => 
                SCK <= '0';
    
            when clocking_high => 
                SCK <= '1';
    
            when receiving_bit => 
                SCK <= '1';
                --data_bits <= data_bits;
                data_bits(bit_cnt) <= SDO;
    
            when update_data => 
                latest_value <= data_bits;
    
            when others =>
                --latest_value <= latest_value;
                --data_bits <= data_bits;
    
        end case;
    end process FSM_Output;
    
    
        end Behavioral;
    

    修改

    感谢您的所有回复!我决定在单个进程中重写我的FSM,并添加有关我的问题的更多信息,以便让那些有类似问题的人更容易理解!

    系统框图:
    http://i.stack.imgur.com/odCwR.png

    注意:现在我只想在没有MicroBlaze和AXI互连模块的情况下模拟和验证独立的adc_core本身。

    FSM图: http://i.stack.imgur.com/5qFdN.png

    单个流程源代码:

    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    
    entity adc_chip_driver is
    port(
            clk                 : in std_logic; 
            rst                 : in std_logic;         
            data_reg_out        : out std_logic_vector(31 downto 0);
            control_reg         : in std_logic_vector(31 downto 0);
    
            SDO                 : in std_logic;
            SCK                 : out std_logic;
            CONV                    : out std_logic
    );
    end adc_chip_driver;
    
    architecture Behavioral of adc_chip_driver is
    type states is (idle, conversation, clocking_low, clocking_high, receiving_bit, update_data);
    
    signal state : states;
    
    signal data_bits                : std_logic_vector(0 to 15) := (others => '0');
    signal latest_value         : std_logic_vector(15 downto 0) := (others => '0');
    
    signal conv_cnt                 : integer range 0 to 500 := 0;
    signal clk_cnt                  : integer range 0 to 13 := 0;
    signal bit_cnt                  : integer range 0 to 15 := 0;
    
    begin
    process(clk, rst, control_reg)
    begin
    
        if(rst = '0') then
            state <= idle;
            data_bits <= (others => '0');
            latest_value <= (others => '0');
            data_reg_out <= (others => '0');
    
        elsif(clk'event and clk = '1') then
            --!Default Values
            data_reg_out(31 downto 16) <= (others => '0');      --unused bits of register
            data_reg_out(15 downto 0) <= latest_value;          --data_reg_out is always tided to latast_value;
            latest_value <= latest_value;                               --latest_value is being updated only once       
            data_bits <= data_bits;                                     --has to retain value
            conv_cnt <= conv_cnt;
            clk_cnt <= clk_cnt;
            bit_cnt <= bit_cnt;
    
            case state is
                when idle =>
                    --signals
                    conv_cnt <= 0;
                    clk_cnt <= 0;
                    bit_cnt <= 0;
                    --outputs
                    SCK <= '0';
                    CONV <= '0';
                    --logic
                    if(control_reg(0) = '1') then
                        state <= conversation;
                    else
                        state <= idle;
                    end if;
    
                when conversation =>
                    --output
                    SCK <= '0';
                    CONV <= '1';
                    --logic
                    if(conv_cnt = 500) then
                        state <= clocking_low;
                        conv_cnt <= 0;
                    else
                        state <= conversation;
                        conv_cnt <= conv_cnt + 1;
                    end if;
    
                when clocking_low =>    
                    --ouput
                    SCK <= '0';
                    CONV <= '0';
                    --logic
                    if(clk_cnt = 13) then
                        clk_cnt <= 0;
                        state <= clocking_high;
                    else
                        clk_cnt <= clk_cnt + 1;
                        state <= clocking_low;
                    end if;
    
    
                when clocking_high =>           
                    --ouput
                    SCK <= '1';
                    CONV <= '0';
                    --logic
                    if(clk_cnt = 13) then
                        clk_cnt <= 0;
                        state <= receiving_bit;
                    else
                        clk_cnt <= clk_cnt + 1;
                        state <= clocking_high;
                    end if;
    
                when receiving_bit =>
                    --signal
                    data_bits(bit_cnt) <= SDO;
                    --ouput
                    SCK <= '1';
                    CONV <= '0';
                    --logic
                    if(bit_cnt = 15) then
                        bit_cnt <= 0;
                        state <= update_data;
                    else
                        bit_cnt <= bit_cnt + 1;
                        state <= clocking_low;
                    end if;
    
                when update_data =>
                    --signal
                    latest_value(15 downto 0) <= data_bits(0 to 15);
                    --ouput
                    SCK <= '0';
                    CONV <= '0';
                    --logic
                    if(control_reg(0) = '1') then
                        state <= conversation;
                    else
                        state <= idle;
                    end if;
    
            end case;
        end if;
    end process;
    end Behavioral;
    

    也许我可以收到一些关于单一流程设计的新反馈? 另外,对于特定FSM状态下的计数器使用情况,我仍然没有答案。我注意到通常在第二个周期中“clocking_low”和“clocking_high”计数器实际上从1开始而不是0,我知道在这种情况下它不是问题,但我可以很容易地想象它在哪里可能很重要。我在考虑将重置设置计数器设置为'-1'之后,但也许有更好的解决方案?

5 个答案:

答案 0 :(得分:2)

您的代码存在许多问题。为了说明其中的一些,我试图在图中描绘你的有限状态机。下面的1和2,基于您提供的VHDL代码。

enter image description here

首先也是最重要的是,设计应从顶级框图开始,显示电路端口(如图1所示),然后是详细的状态转换图(如图2所示 - 此处不完整)。回想一下,例如,电路输出(data_reg_out, SCK CONV - 图1)是FSM应该产生的信号,因此它是必不可少的这些值在所有状态中指定(在图2中的状态圆圈内显示)。一旦修复并完成了图2的图表,编写相应的VHDL代码应该相对简单(除了计时器 - 见下面的评论)。

其他问题可以直接在代码中看到。关于这四个过程的一些评论如下。

存储FSM状态的第一个进程(StateReg)没问题。

第二个进程(TimerReg)也被注册(在clk’event下),这是构建计时器所必需的。但是,处理定时器是任何定时FSM中最棘手的部分之一,因为你必须设计一个正确的策略来停止/运行定时器并将其归零。为此,我建议您查看下面的参考1,它从硬件角度处理所有可能的FSM实现,​​包括对定时FSM的广泛研究。

第三个进程(FSM_Proc)定义下一个状态。它没有注册,这是应该的。但是,为了检查它,有必要首先完成图2的状态转换图。

最后一个进程(FSM_Output)定义机器输出。它没有注册,这应该是一般的。但是,尽管有默认值,但所有州的输出列表都不相同。注意,例如,状态空闲中存在latest_value和data_bits,它们不会出现在所有状态中,从而导致锁存器的推断。此外,此过程基于 NextState 而不是 PresentState ,这可能会降低电路的最大速度。

我希望这些评论可以激励你从一开始就重新启动。

1 V. A. Pedroni,硬件中的有限状态机:理论与设计(使用VHDL和SystemVerilog),麻省理工学院出版社,2013年12月。

答案 1 :(得分:1)

如果未在所有可能路径上分配信号,则会获得锁存器,因为它会变为有状态。

要避免此问题,请确保始终为信号指定值(一种方法是在过程顶部指定“默认”值。)

  

因为我希望保留值,特别是“data_bits”,因为这个向量是从几个时钟周期构建的。

“保留值”意味着状态,而不是纯粹的组合逻辑。在这种情况下,它不应该在您的输出过程中。它应该在你的状态更新过程中。

答案 2 :(得分:1)

我的解决方案是始终使用时钟进程处理所有事情。不需要为状态寄存器设置单独的时钟进程,也不需要为状态转换设置单独的进程。这是几年前需要的风格。在我看来,你最好将所有内容都放在一个时钟进程中然后你就不能得到锁存器。

如果必须使用两个进程,那么获取VHDL 2008编译器并使用process(all)确保所有信号都在敏感列表中正确,然后仔细确保您分配的每个信号都为每个信号分配通过这个过程的逻辑路径。实现这一目标的最简单方法通常是在流程开始时为它们分配所有“默认”值。

答案 3 :(得分:0)

在组合过程中(如FSM_Output),您应该从不读取信号并写入相同的信号。对于latest_valuedata_bits来说,这正是这里发生的事情。

创建新信号latest_value_r和data_bits_r并复制时钟进程中的值,或者在没有单独组合过程的情况下坚持单个时钟进程。

答案 4 :(得分:0)

您想要data_bits和latest_value的硬件是什么?如果要在几个时钟周期内构建向量,则需要一个存储设备。您的选择是:锁存器(电平敏感存储器)和触发器(边缘敏感存储器)。如果你不想要锁存器,那么你必须编写触发器代码。

要编写触发器代码,请使用“if clk ='1'和clk'event then”,就像在TimerReg进程中一样。您也可以使用“if rising_edge(Clk)then” - 我更喜欢这种可读性,但工具无论如何都不在乎。

我认为你出错的地方在你的计划过程中。代码只是设计捕获。重要的是,您需要使用方框图进行规划,并了解设计需要触发器的位置以及需要组合逻辑的位置。这是正确的,其余的只是应用编码模板。因此,在开始编码之前,请确保您已了解这一点。

无论您是使用时钟进程进行编码还是使用时钟和组合逻辑进程的混合,都无关紧要。我认为你在编码中做的最重要的事情就是让它具有可读性。如果你收集意见,你会发现它们各不相同,@ Martin和@Brian更喜欢一个时钟进程。我更喜欢2进程状态机 - 触发器和组合(当前状态到下一状态和输出解码)。你使用了一个3进程状态机 - 对我来说就像绘制一个气泡图来显示状态转换,另一个用于显示输出转换。然而,在一天结束时,他们都捕获了相同的意图。只要有人在您离开后很久就阅读您的代码就很清楚了,那应该没问题。