【问题标题】:Trouble creating finite state machine创建有限状态机的麻烦
【发布时间】:2023-03-13 17:07:01
【问题描述】:

我正在为我接到的任务创建状态机和 vhdl 代码。它涉及使用FPGA来控制其功能的机器。它有一个 100 MHZ 时钟,占空比为 50%。用户按下机器上的一个按钮,一个 LED 就会打开。一旦发生这种情况,机器就会开始在数据线上寻找数据序列(前导码)。一旦检测到前导码,伺服系统顺时针旋转到 90 度位置,保持向上 10 秒,之后整个系统复位。舵机的周期为 20ms,起始位置的占空比为 1.0ms,垂直位置的占空比为 1.5ms。前导序列为 1-0-1-0-0-0-0-1-0-1-0-0-0-0-0-0-1,位周期为 0.5 微秒。

我已经有好几年没有用 FPGA 或 VHDL 做任何事情了,所以目前这对我来说有点困难。我目前正在使用状态机,但由于状态的数量,我遇到了一些困难。我的状态机中有 20 个状态。 S0 是初始状态,S1 是 LED 亮的状态,S2-S18 代表我正在检测前导码的状态,S19 是伺服旋转的状态,然后状态机回到初始状态。我不确定我是否正确地解决了这个问题,但如果有任何帮助或建议,我们将不胜感激。

【问题讨论】:

  • 到目前为止,您的推理看起来不错。不过我只是想知道S19。舵机从初始位置达到90度需要多长时间?然后多久才能回到复位状态的初始位置? S19 应该分成 3 个状态吗?即S19:从初始位置移动到90度; S20:等待10秒; S21:回到初始位置。值得考虑的是,一些设计工具具有状态机向导,因此您可以绘制图表,它会为您生成 VHDL。
  • 嗯。绝对值得考虑蒂姆。我认为你提出了一些有效的观点。我认为将伺服状态分为3个状态可能更容易。我不太确定,时间方面,从开始到垂直或从垂直到开始需要多长时间。我给出的只是各个伺服位置的伺服周期和占空比。我希望也许可以从该信息中找出各个时间。
  • 我认为没有足够的信息来计算伺服的惯性、滞后和响应时间。其他需要考虑的是定时时钟:100 MHz; 20 毫秒; 0.5微秒; 10 秒。这些是由FPGA外部的硬件提供的吗?如果您的设计工具和目标 FPGA 允许,请考虑使用 PLL。或者使用时钟分频器或计数器。
  • 您的笔记本注释“状态机挑战”从何而来?为什么使用单片 FSM 作为解决方案,有比担心branches for rejected preamble matches 更简单的解决方案?例如,可以使用移位寄存器来匹配前导码。它需要与单热状态机实现相同的硬件复杂性。您的两个问题都不包含具体的编程问题。
  • 这是给我的一个问题/任务,其中一个要求是创建一个具有相应 vhdl 代码的状态机。不过,我确信有更简单的方法可以解决这个问题。

标签: vhdl fsm digital-logic


【解决方案1】:

有限状态机 - 无移位寄存器

这就是我得到的。我不明白你从 S18 到 S19 的过渡。在我看来,前导码在 S18 中时已被检测到,因此不需要额外的检测转换。我还将您的 S19 分为 3 个状态,即 S18、S19 和 S20。

有限状态机 - 带移位寄存器

这是用于检测前导序列的移位寄存器的状态图。 S1等待移位寄存器检测前导序列。

FSM的VHDL实现

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity StateMachineChallenge is
    generic
    (
        NUM_STATES: natural := 5;
        NUM_OUTPUTS: natural := 6
    );
    port
    (
        clock: in std_logic;
        reset: in std_logic;
        button: in std_logic;
        found_preamble: in std_logic;
        timer_a_timeout: in std_logic;
        timer_b_timeout: in std_logic;
        timer_c_timeout: in std_logic;
        state: out natural range 0 to NUM_STATES := 0;
        outputs: out std_logic_vector(NUM_OUTPUTS - 1 downto 0) := (others => '0')
    );
end entity;

architecture V1 of StateMachineChallenge is

    constant S0: natural range 0 to NUM_STATES := 0;
    constant S1: natural range 0 to NUM_STATES := 1;
    constant S2: natural range 0 to NUM_STATES := 2;
    constant S3: natural range 0 to NUM_STATES := 3;
    constant S4: natural range 0 to NUM_STATES := 4;

    constant LED_OFF, FIND_PREAMBLE_OFF, TIMER_A_OFF, TIMER_B_OFF, TIMER_C_OFF: std_logic := '0';
    constant LED_ON, FIND_PREAMBLE_ON, TIMER_A_ON, TIMER_B_ON, TIMER_C_ON: std_logic := '1';

    constant SERVO_POSITION_START: std_logic := '0';  -- 1.0 ms
    constant SERVO_POSITION_90DEG: std_logic := '1';  -- 1.5 ms

    type TOutputsTable is array(0 to NUM_STATES - 1) of std_logic_vector(NUM_OUTPUTS - 1 downto 0);
    constant OUTPUTS_TABLE: TOutputsTable :=
    (
        -- LED, Find preamble, Servo position, Timer A run, Timer B run, Timer C run
        (LED_OFF, FIND_PREAMBLE_OFF, SERVO_POSITION_START, TIMER_A_OFF, TIMER_B_OFF, TIMER_C_OFF),
        (LED_ON, FIND_PREAMBLE_ON, SERVO_POSITION_START, TIMER_A_OFF, TIMER_B_OFF, TIMER_C_OFF),
        (LED_ON, FIND_PREAMBLE_OFF, SERVO_POSITION_90DEG, TIMER_A_ON, TIMER_B_OFF, TIMER_C_OFF),
        (LED_ON, FIND_PREAMBLE_OFF, SERVO_POSITION_90DEG, TIMER_A_OFF, TIMER_B_ON, TIMER_C_OFF),
        (LED_ON, FIND_PREAMBLE_OFF, SERVO_POSITION_START, TIMER_A_OFF, TIMER_B_OFF, TIMER_C_ON)
    );
    signal next_state: natural range 0 to NUM_STATES := S0;
    signal next_outputs: std_logic_vector(NUM_OUTPUTS - 1 downto 0) := OUTPUTS_TABLE(S0);

begin
    --
    -- State register and outputs register.
    --
    process(clock, reset)
    begin
        if reset = '1' then
            state <= S0;
            outputs <= OUTPUTS_TABLE(S0);
        elsif rising_edge(clock) then
            state <= next_state;
            outputs <= next_outputs;
        end if;
    end process;

    --
    -- Next state logic
    --
    process(reset, state, button, found_preamble, timer_a_timeout, timer_b_timeout, timer_c_timeout)
    begin
        if reset = '1' then
            next_state <= S0;
        else
            case state is
                when S0 =>  -- Reset
                    if button = '1' then
                        next_state <= S1;
                    else
                        next_state <= S0;
                    end if;
                when S1 =>  -- Looking for preamble
                    if found_preamble then
                        next_state <= S2;
                    else
                        next_state <= S1;
                    end if;
                when S2 =>  -- Moving servo to 90 degrees
                    if timer_a_timeout then
                        next_state <= S3;
                    else
                        next_state <= S2;
                    end if;
                when S3 =>  -- Waiting for 10 seconds
                    if timer_b_timeout then
                        next_state <= S4;
                    else
                        next_state <= S3;
                    end if;
                when S4 =>  -- Moving servo to start position
                    if timer_c_timeout then
                        next_state <= S0;
                    else
                        next_state <= S4;
                    end if;
                when others =>
                    next_state <= S0;
            end case;
        end if;
    end process;

    --
    -- Next outputs logic
    --
    process(reset, next_state)
    begin
        if reset = '1' then
            next_outputs <= OUTPUTS_TABLE(S0);
        else
            next_outputs <= OUTPUTS_TABLE(next_state);
        end if;
    end process;

end architecture;

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity TestBench is
end entity;

architecture V1 of TestBench is

    constant SYS_CLOCK_FREQ: real := 100000000.0;  -- Hz
    constant SYS_CLOCK_PERIOD: time := 1.0 sec / SYS_CLOCK_FREQ;

    signal halt_sys_clock: boolean := false;
    signal sys_clock: std_logic;
    signal reset: std_logic;

    signal button: std_logic;
    signal found_preamble:  std_logic;
    signal timer_a_timeout: std_logic;
    signal timer_b_timeout: std_logic;
    signal timer_c_timeout: std_logic;
    
    constant NUM_STATES: natural := 5;
    constant NUM_OUTPUTS: natural := 6;
    
    signal state: natural range 0 to NUM_STATES;

    signal outputs: std_logic_vector(NUM_OUTPUTS - 1 downto 0) := (others => '0');

    signal led: std_logic;
    signal find_preamble: std_logic;
    signal servo_position: std_logic;
    signal timer_a_enable: std_logic;
    signal timer_b_enable: std_logic;
    signal timer_c_enable: std_logic;

    component StateMachineChallenge is
        generic
        (
            NUM_STATES: natural := NUM_STATES;
            NUM_OUTPUTS: natural := NUM_OUTPUTS
        );
        port
        (
            clock: in std_logic;
            reset: in std_logic;
            button: in std_logic;
            found_preamble: in std_logic;
            timer_a_timeout: in std_logic;
            timer_b_timeout: in std_logic;
            timer_c_timeout: in std_logic;
            state: out natural range 0 to NUM_STATES := 0;
            outputs: out std_logic_vector(NUM_OUTPUTS - 1 downto 0) := (others => '0')
        );
    end component;
    
begin

    SysClockGenerator: process
    begin
        while not halt_sys_clock loop
            sys_clock <= '1';
            wait for SYS_CLOCK_PERIOD / 2.0;
            sys_clock <= '0';
            wait for SYS_CLOCK_PERIOD / 2.0;
        end loop;
        wait;
    end process SysClockGenerator;
    
    ResetProcess: process
    begin
        reset <= '0';
        wait for 1 ns;
        reset <= '1';
        wait for 10 ns;
        reset <= '0';
        --wait for 21 ms;
        wait for 1 ms;
        halt_sys_clock <= true;
        wait;
    end process ResetProcess;
    
    ButtonPress: process
    begin
        button <= '0';
        wait for 1 us;
        button <= '1';
        wait for 1 us;
        button <= '0';
        wait;
    end process ButtonPress;
    
    FindPreamble: process(sys_clock)
        variable count: natural := 0;
    begin
        if rising_edge(sys_clock) and find_preamble = '1' then
            count := count + 1;
        end if;
        if count = 17 * 50 then
            found_preamble <= '1';
        else
            found_preamble <= '0';
        end if;
    end process FindPreamble;
    
    TimerA: process(sys_clock)
        variable count: natural := 0;
    begin
        if rising_edge(sys_clock) and timer_a_enable = '1' then
            count := count + 1;
        end if;
        if count = 200 then
            timer_a_timeout <= '1';
        else
            timer_a_timeout <= '0';
        end if;
    end process TimerA;
    
    TimerB: process(sys_clock)
        variable count: natural := 0;
    begin
        if rising_edge(sys_clock) and timer_b_enable = '1' then
            count := count + 1;
        end if;
        if count = 1000 then
            timer_b_timeout <= '1';
        else
            timer_b_timeout <= '0';
        end if;
    end process TimerB;
    
    TimerC: process(sys_clock)
        variable count: natural := 0;
    begin
        if rising_edge(sys_clock) and timer_c_enable = '1' then
            count := count + 1;
        end if;
        if count = 200 then
            timer_c_timeout <= '1';
        else
            timer_c_timeout <= '0';
        end if;
    end process TimerC;
    
    DUT: StateMachineChallenge
        generic map
        (
            NUM_STATES => NUM_STATES,
            NUM_OUTPUTS => NUM_OUTPUTS
        )
        port map
        (
            clock => sys_clock,
            reset => reset,
            button => button,
            found_preamble => found_preamble,
            timer_a_timeout => timer_a_timeout,
            timer_b_timeout => timer_b_timeout,
            timer_c_timeout => timer_c_timeout,
            state => state,
            outputs => outputs
        );
    
    (led, find_preamble, servo_position, timer_a_enable, timer_b_enable, timer_c_enable) <= outputs;
        
end architecture;

时钟计时

VHDL 生成时钟脉冲

--
-- Clock Strobe
--
-- Generates a slow clock strobe from a fast clock.
--

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity ClockStrobe is
    generic
    (
        max_count: natural
    );
    port
    (
        clock: in std_logic;
        clock_strobe: out std_logic
    );
end;

architecture V1 of ClockStrobe is
begin
    -- Create clock strobe.
    process(clock)
        variable counter: natural range 0 to max_count := 0;
    begin
        if rising_edge(clock) then
            counter := counter + 1;
            if counter = max_count then
                clock_strobe <= '1';
                counter := 0;
            else
                clock_strobe <= '0';
            end if;
        end if;
    end process;

end architecture;

--
-- Test Bench
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity TestBench is
end entity;

architecture V1 of TestBench is

    constant SYS_CLOCK_FREQ: real := 100000000.0;  -- Hz
    constant SYS_CLOCK_PERIOD: time := 1.0 sec / SYS_CLOCK_FREQ;

    constant MAX_COUNT_2MHz: natural := 50;
    constant MAX_COUNT_50Hz: natural := 2000000;

    signal stop_clock: boolean := false;
    signal clock: std_logic;
    signal clock_strobe_2MHz: std_logic;
    signal clock_strobe_50Hz: std_logic;

    component ClockStrobe is
        generic
        (
            max_count: natural
        );
        port
        (
            clock: in std_logic;
            clock_strobe: out std_logic
        );
    end component;
    
begin

    ClockGenerator: process
    begin
        while not stop_clock loop
            clock <= '0';
            wait for SYS_CLOCK_PERIOD / 2.0;
            clock <= '1';
            wait for SYS_CLOCK_PERIOD / 2.0;
        end loop;
        wait;
    end process ClockGenerator;

    ClockStrobe2MHz: ClockStrobe
        generic map
        (
            max_count => MAX_COUNT_2MHz
        )
        port map
        (
            clock => clock,
            clock_strobe => clock_strobe_2MHz
        );

    ClockStrobe50Hz: ClockStrobe
        generic map
        (
            max_count => MAX_COUNT_50Hz
        )
        port map
        (
            clock => clock,
            clock_strobe => clock_strobe_50Hz
        );

    -- Preamble process.
    process(clock)
    begin
        if rising_edge(clock) then
            if clock_strobe_2MHz then
                -- Process the next preamble bit.
            end if;
        end if;
    end process;

    -- Servo process.
    process(clock)
    begin
        if rising_edge(clock) then
            if clock_strobe_50Hz then
                -- Process the servo.
            end if;
        end if;
    end process;

end architecture;

模拟

仅显示 2 MHz 频闪。

【讨论】:

  • 我明白了。只是为了理解,为什么大多数州回到 S2 和 S8 和 S17 回到 S1?那么 S3 呢?
  • 糟糕!我已经更正了 S3 和 S10。他们回到 S1 和 S2 的原因是因为他们破坏了前导序列。 0 回到序列的开头以再次寻找初始的 1。 1 回到 S2,因为从 S1 的转换也是 1(循环返回被视为新序列中的第一个 1)。
  • 感谢蒂姆的帮助。是否需要根据状态机创建真值表和k个映射来辅助创建vhdl代码?
  • 这取决于您的经验。既然你问是否有必要,我想你应该。为了使事情更简单,我会将 S1 到 S17 转换为一个移位寄存器,当前导码与所需的序列匹配时设置一个标志。此外,计时器 A 和计时器 C 可以替换为角位置传感器,这些传感器可用于标记何时到达所需位置。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-22
  • 1970-01-01
  • 1970-01-01
  • 2015-05-10
  • 2020-07-11
  • 1970-01-01
相关资源
最近更新 更多