具有多个延迟的 VHDL 状态机 - 最佳方法?
VHDL state machine with several delays - best approach?
自从我能够理解有限状态机的基础知识以来,这个问题一直困扰着我。假设我有四个状态 s0 - s3,其中
FSM 将在 's0' 上电后自动启动。在一些定义的延迟之后,FSM 应进入 's1' - 其他状态也是如此。
不同状态之间的延迟是不一样的。
例如:
上电 -> 's0' -> 100 毫秒 -> 's1' -> 50 微秒 -> 's2' -> 360 微秒 -> 's3' -> 's3'
在像 C 这样的过程语言中,我只需调用一个延迟例程,其中一个参数是所需的延迟,然后就可以完成了。
如何优雅地实现这种 FSM?
最好的,
克里斯
您可以结合使用时钟分频器和计数器。找出您设备上的时钟速度是多少。您提到的所有延迟都可以分解为 10us,因此我将使用时钟分频器来达到该速度。假设您设备的原始时钟速度为 50MHz。您需要查明需要多少个周期才能计数到 10us。下面的计算是这样的:
# of cycles = 10ms * 50MHz = 5000 cycles
所以你需要一个计数到 5000 的计数器。一个粗略的例子如下:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity new_clk is
Port (
clk_in : in STD_LOGIC; -- your 50MHZ device clock
reset : in STD_LOGIC;
clk_out: out STD_LOGIC -- your new clock with a 10us period
);
end clk200Hz;
architecture Behavioral of new_clk is
signal temporal: STD_LOGIC;
signal counter : integer range 0 to 4999 := 0;
begin
clk_div: process (reset, clk_in) begin
if (reset = '1') then
temporal <= '0';
counter <= 0;
elsif rising_edge(clk_in) then
if (counter = 4999) then
temporal <= NOT(temporal);
counter <= 0;
else
counter <= counter + 1;
end if;
end if;
end process;
clk_out <= temporal;
end Behavioral;
注意计数器如何从 0 变为 4999。信号 clk_out 现在的周期为 10us。您现在可以使用它来生成延迟。
例如,对于您的 360us 延迟,计算 clk_out 信号的 36 个周期。该代码与上面的代码大致相似,但这次您正在计数 clk_out 并且您的计数器只会从 0 到 35。
(稍后我可以添加更多内容,但这应该可以帮助您入门。)
我的模式:一个延迟计数器,每个状态转换都可以根据需要进行编程,即在每个新延迟开始时。
虽然某些工具(特别是 Synplicity)在准确计算时间方面存在问题,但它们都是可综合的,除非您的时钟周期是整数纳秒。有关此错误的更多信息,请参阅 this Q&A。如果您 运行 遇到这种情况,幻数(32000 而不是 Synplicity 在该问题中计算出的 32258)可能是最简单的解决方法。
将其包裹在 entity/architecture 中作为(简单的)练习。
-- first, some declarations for readability instead of magic numbers
constant clock_period : time := 10 ns;
--WARNING : Synplicity has a bug : by default it rounds to nanoseconds!
constant longest_delay : time := 100 ms;
subtype delay_type is natural range 0 to longest_delay / clock_period;
constant reset_delay : delay_type := 100 ms / clock_period - 1;
constant s1_delay : delay_type := 50 us / clock_period - 1;
constant s2_delay : delay_type := 360 us / clock_period - 1;
-- NB take care to avoid off-by-1 error!
type state_type is (s0, s1, s2, s3);
-- now the state machine declarations:
signal state : state_type;
signal delay : delay_type;
-- now the state machine itself:
process(clock, reset) is
begin
if reset = '1' then
state <= s0;
delay <= reset_delay;
elsif rising_edge(clock) then
-- default actions such as default outputs first
-- operate the delay counter
if delay > 0 then
delay <= delay - 1;
end if;
-- state machine proper
case state is
when s0 =>
-- do nothing while delay counts down
if delay = 0 then
--start 50us delay when entering S1
delay <= s1_delay;
state <= s1;
end if;
when s1 =>
if delay = 0 then
delay <= s2_delay;
state <= s2;
end if;
when s2 =>
if delay = 0 then
state <= s3;
end if;
when others =>
null;
end case;
end if;
end process;
查看 "Finite State Machines in Hardware: Theory and Design (with VHDL and SystemVerilog)" MIT Press,2013 年的第 8-9 章,了解涵盖任何案例和许多完整示例的详细讨论。
Brian Drummond 精彩回答的小颂歌:谢谢 Brian! :)
主要区别在于删除了延迟长度的固定上限:现在仅受'delay'信号类型长度的限制——通常可以根据需要设置长度。
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;
ENTITY Test123 IS
PORT (
clk_in1 : IN std_logic := '0';
rst1, en1 : IN std_logic;
);
END ENTITY Test123;
ARCHITECTURE Test123_Arch OF Test123 IS
-- first, some declarations for readability instead of magic numbers
CONSTANT clock_period : TIME := 20 ns; -- 50 MHz
--WARNING : Synplicity has a bug : by default it rounds to nanoseconds!
CONSTANT reset_delay : TIME := 100 ms - clock_period;
CONSTANT s1_delay : TIME := 50 us - clock_period;
CONSTANT s2_delay : TIME := 360 us - clock_period;
-- NB take care to avoid off-by-1 error!
-- now the state machine declarations:
TYPE state_type IS (s0, s1, s2, s3);
SIGNAL state : state_type;
--
--signal delay : unsigned(47 downto 0) := (others => '0'); -- a 48-Bit 'unsigned' Type, Along a 50-MHz Clock, Evaluates To an Upper-Limit of ~90,071,992.5474 Seconds.
SIGNAL delay : NATURAL := 0; -- a 'natural' Type, Along a 50-MHz Clock, Evaluates To an Upper-Limit of ~85.8993459 Seconds.
--
FUNCTION time_to_cycles(time_value : TIME; clk_period : TIME) RETURN NATURAL IS
BEGIN
-- RETURN TO_UNSIGNED((time_value / clk_period), 48); -- Return a 48-Bit 'unsigned'
RETURN (time_value / clk_period); -- Return a 32-Bit 'natural'
END time_to_cycles;
--
BEGIN
-- now the state machine itself:
sm0 : PROCESS (clk_in1, rst1)
BEGIN
IF (rst1 = '1') THEN
state <= s0;
delay <= time_to_cycles(reset_delay, clock_period);
ELSIF rising_edge(clk_in1) THEN
-- default actions such as default outputs first
-- operate the delay counter
IF (delay > 0) THEN
delay <= delay - 1;
END IF;
-- state machine proper
CASE state IS
WHEN s0 =>
-- do nothing while delay counts down
IF (delay = 0) THEN
--start 50us delay when entering S1
delay <= time_to_cycles(s1_delay, clock_period);
state <= s1;
END IF;
WHEN s1 =>
IF (delay = 0) THEN
delay <= time_to_cycles(s2_delay, clock_period);
state <= s2;
END IF;
WHEN s2 =>
IF (delay = 0) THEN
state <= s3;
END IF;
WHEN OTHERS =>
NULL;
END CASE;
END IF;
END PROCESS;
END ARCHITECTURE Test123_Arch;
自从我能够理解有限状态机的基础知识以来,这个问题一直困扰着我。假设我有四个状态 s0 - s3,其中 FSM 将在 's0' 上电后自动启动。在一些定义的延迟之后,FSM 应进入 's1' - 其他状态也是如此。 不同状态之间的延迟是不一样的。
例如:
上电 -> 's0' -> 100 毫秒 -> 's1' -> 50 微秒 -> 's2' -> 360 微秒 -> 's3' -> 's3'
在像 C 这样的过程语言中,我只需调用一个延迟例程,其中一个参数是所需的延迟,然后就可以完成了。
如何优雅地实现这种 FSM?
最好的, 克里斯
您可以结合使用时钟分频器和计数器。找出您设备上的时钟速度是多少。您提到的所有延迟都可以分解为 10us,因此我将使用时钟分频器来达到该速度。假设您设备的原始时钟速度为 50MHz。您需要查明需要多少个周期才能计数到 10us。下面的计算是这样的:
# of cycles = 10ms * 50MHz = 5000 cycles
所以你需要一个计数到 5000 的计数器。一个粗略的例子如下:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity new_clk is
Port (
clk_in : in STD_LOGIC; -- your 50MHZ device clock
reset : in STD_LOGIC;
clk_out: out STD_LOGIC -- your new clock with a 10us period
);
end clk200Hz;
architecture Behavioral of new_clk is
signal temporal: STD_LOGIC;
signal counter : integer range 0 to 4999 := 0;
begin
clk_div: process (reset, clk_in) begin
if (reset = '1') then
temporal <= '0';
counter <= 0;
elsif rising_edge(clk_in) then
if (counter = 4999) then
temporal <= NOT(temporal);
counter <= 0;
else
counter <= counter + 1;
end if;
end if;
end process;
clk_out <= temporal;
end Behavioral;
注意计数器如何从 0 变为 4999。信号 clk_out 现在的周期为 10us。您现在可以使用它来生成延迟。
例如,对于您的 360us 延迟,计算 clk_out 信号的 36 个周期。该代码与上面的代码大致相似,但这次您正在计数 clk_out 并且您的计数器只会从 0 到 35。
(稍后我可以添加更多内容,但这应该可以帮助您入门。)
我的模式:一个延迟计数器,每个状态转换都可以根据需要进行编程,即在每个新延迟开始时。
虽然某些工具(特别是 Synplicity)在准确计算时间方面存在问题,但它们都是可综合的,除非您的时钟周期是整数纳秒。有关此错误的更多信息,请参阅 this Q&A。如果您 运行 遇到这种情况,幻数(32000 而不是 Synplicity 在该问题中计算出的 32258)可能是最简单的解决方法。
将其包裹在 entity/architecture 中作为(简单的)练习。
-- first, some declarations for readability instead of magic numbers
constant clock_period : time := 10 ns;
--WARNING : Synplicity has a bug : by default it rounds to nanoseconds!
constant longest_delay : time := 100 ms;
subtype delay_type is natural range 0 to longest_delay / clock_period;
constant reset_delay : delay_type := 100 ms / clock_period - 1;
constant s1_delay : delay_type := 50 us / clock_period - 1;
constant s2_delay : delay_type := 360 us / clock_period - 1;
-- NB take care to avoid off-by-1 error!
type state_type is (s0, s1, s2, s3);
-- now the state machine declarations:
signal state : state_type;
signal delay : delay_type;
-- now the state machine itself:
process(clock, reset) is
begin
if reset = '1' then
state <= s0;
delay <= reset_delay;
elsif rising_edge(clock) then
-- default actions such as default outputs first
-- operate the delay counter
if delay > 0 then
delay <= delay - 1;
end if;
-- state machine proper
case state is
when s0 =>
-- do nothing while delay counts down
if delay = 0 then
--start 50us delay when entering S1
delay <= s1_delay;
state <= s1;
end if;
when s1 =>
if delay = 0 then
delay <= s2_delay;
state <= s2;
end if;
when s2 =>
if delay = 0 then
state <= s3;
end if;
when others =>
null;
end case;
end if;
end process;
查看 "Finite State Machines in Hardware: Theory and Design (with VHDL and SystemVerilog)" MIT Press,2013 年的第 8-9 章,了解涵盖任何案例和许多完整示例的详细讨论。
Brian Drummond 精彩回答的小颂歌:谢谢 Brian! :)
主要区别在于删除了延迟长度的固定上限:现在仅受'delay'信号类型长度的限制——通常可以根据需要设置长度。
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;
ENTITY Test123 IS
PORT (
clk_in1 : IN std_logic := '0';
rst1, en1 : IN std_logic;
);
END ENTITY Test123;
ARCHITECTURE Test123_Arch OF Test123 IS
-- first, some declarations for readability instead of magic numbers
CONSTANT clock_period : TIME := 20 ns; -- 50 MHz
--WARNING : Synplicity has a bug : by default it rounds to nanoseconds!
CONSTANT reset_delay : TIME := 100 ms - clock_period;
CONSTANT s1_delay : TIME := 50 us - clock_period;
CONSTANT s2_delay : TIME := 360 us - clock_period;
-- NB take care to avoid off-by-1 error!
-- now the state machine declarations:
TYPE state_type IS (s0, s1, s2, s3);
SIGNAL state : state_type;
--
--signal delay : unsigned(47 downto 0) := (others => '0'); -- a 48-Bit 'unsigned' Type, Along a 50-MHz Clock, Evaluates To an Upper-Limit of ~90,071,992.5474 Seconds.
SIGNAL delay : NATURAL := 0; -- a 'natural' Type, Along a 50-MHz Clock, Evaluates To an Upper-Limit of ~85.8993459 Seconds.
--
FUNCTION time_to_cycles(time_value : TIME; clk_period : TIME) RETURN NATURAL IS
BEGIN
-- RETURN TO_UNSIGNED((time_value / clk_period), 48); -- Return a 48-Bit 'unsigned'
RETURN (time_value / clk_period); -- Return a 32-Bit 'natural'
END time_to_cycles;
--
BEGIN
-- now the state machine itself:
sm0 : PROCESS (clk_in1, rst1)
BEGIN
IF (rst1 = '1') THEN
state <= s0;
delay <= time_to_cycles(reset_delay, clock_period);
ELSIF rising_edge(clk_in1) THEN
-- default actions such as default outputs first
-- operate the delay counter
IF (delay > 0) THEN
delay <= delay - 1;
END IF;
-- state machine proper
CASE state IS
WHEN s0 =>
-- do nothing while delay counts down
IF (delay = 0) THEN
--start 50us delay when entering S1
delay <= time_to_cycles(s1_delay, clock_period);
state <= s1;
END IF;
WHEN s1 =>
IF (delay = 0) THEN
delay <= time_to_cycles(s2_delay, clock_period);
state <= s2;
END IF;
WHEN s2 =>
IF (delay = 0) THEN
state <= s3;
END IF;
WHEN OTHERS =>
NULL;
END CASE;
END IF;
END PROCESS;
END ARCHITECTURE Test123_Arch;