VHDL 状态机测试平台 - 在板上工作但不在仿真中
VHDL state machine testbench - works when on board but not on simulation
我有在板上运行的 VHDL 实现,它检测到序列 01110 并将为 2 个时钟计数生成一个标志。它也检测重叠序列,其中 011101110 会升旗两次。
我已经使用电路板上的逻辑分析器检查了我的实现,并且非常有信心它可以正常工作。我以 10 kHz 的频率输入 0111 的重复序列,在板上,它有一个 100 MHz 的时钟,我用预分频器将其缩放到 10 kHz。
我的问题是,在尝试使用模拟重新创建类似场景时,我没有得到预期的任何输出
来自电路板逻辑分析仪的图像
图片来自测试台
测试平台代码
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity test_FSM_prac4 is
-- Port ( );
end test_FSM_prac4;
architecture Behavioral of test_FSM_prac4 is
component FSM_prac4 is
port (
inputSignal : in STD_LOGIC;
pushButton : in STD_LOGIC;
clk100mhz : in STD_LOGIC;
logic_analyzer : out STD_LOGIC_VECTOR (7 downto 0);
LEDs: out STD_LOGIC
); end component;
signal inputSignal : std_logic := '0';
signal pushButton: std_logic := '0';
signal clk100mhz: std_logic := '0';
signal logic_analyzer: std_logic_vector(7 downto 0);
signal LEDs : std_logic;
begin
uut : FSM_prac4 port map(
inputSignal => inputSignal,
pushButton => pushButton,
clk100mhz => clk100mhz,
logic_analyzer => logic_analyzer,
LEDs => LEDs
);
--generate clock 100mhz
clock_tic: process begin
loop
clk100mhz <= '0';
wait for 5ns;
clk100mhz <= '1';
wait for 5ns;
end loop;
end process;
input_changes: process begin
loop
inputSignal <= '0';
wait for 100us;
inputSignal <= '1';
wait for 100us;
inputSignal <= '1';
wait for 100us;
inputSignal <= '1';
wait for 100us;
end loop;
end process;
end Behavioral;
显示逻辑分析仪的映射
logic_analyzer(0) <= masterReset;
logic_analyzer(1) <= newClock -- 10Khz Clock;
logic_analyzer(2) <= outputZ;
--FSM States
logic_analyzer(3) <= '1' when y = A ELSE '0';
logic_analyzer(4) <= '1' when y = B ELSE '0';
logic_analyzer(5) <= '1' when y = C ELSE '0';
logic_analyzer(6) <= '1' when y = D ELSE '0';
logic_analyzer(7) <= '1' when y = E ELSE '0';
如果有人能指出我在测试台上做错了什么,以及如何复制以获得与第一张图像相似的结果,因为它显示在模拟中,它始终保持在状态 A 而新时钟不是切换意味着 clk100mhz
不知何故没有连接,但我不明白为什么。
非常感谢任何帮助,谢谢大家
编辑:
我写了一个简单的程序来测试我的标量时钟
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity scaler_clk is
Port (
pushButton : in std_logic;
indicator : out std_logic;
clk100mhz : in STD_LOGIC;
clk10khz: out STD_LOGIC
);
end scaler_clk;
architecture Behavioral of scaler_clk is
signal clockScalers : std_logic_vector (12 downto 0):= (others => '0') ;
signal prescaler: std_logic_vector(12 downto 0) := "1001110001000";
signal newClock: std_logic := '0';
signal masterReset : std_logic;
begin
clk10khz <= newClock;
masterReset <= pushButton;
process (clk100mhz,masterReset) begin
if(masterReset <= '1') then <--- error occurs here
clockScalers <= "0000000000000";
newClock <= '0';
indicator <= '1';
elsif (clk100mhz'event and clk100mhz = '1')then
indicator <= '0';
clockScalers <= clockScalers + 1;
if(clockScalers > prescaler) then
newClock <= not newClock;
clockScalers <= (others => '0');
end if;
end if;
end process;
end Behavioral;
测试平台代码
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity test_scaler_clk is
-- Port ( );
end test_scaler_clk;
architecture Behavioral of test_scaler_clk is
component scaler_clk Port (
pushButton : in std_logic;
indicator : out std_logic;
--input clock
clk100mhz : in STD_LOGIC;
clk10khz: out STD_LOGIC
);end component;
signal clk100mhz: std_logic := '0';
signal clk10khz : std_logic;
signal pushButton: std_logic;
signal indicator : std_logic;
begin
uut: scaler_clk port map(
pushButton => pushButton,
indicator => indicator,
clk100mhz => clk100mhz,
clk10khz => clk10khz
);
pushButton <= '0';
clock_tic: process begin
loop
clk100mhz <= '0';
wait for 5ns;
clk100mhz <= '1';
wait for 5ns;
end loop;
end process;
end Behavioral;
即使我将 pushButton 设置为“0”,它仍然会触发 masterReset
,任何人都知道为什么,这就是 10 kHz 时钟不工作的原因
您可以(应该)改进您的代码中的几件事。正如 Brian 已经解释的那样,在 scaler_clk
的 Behavioral
架构中,您应该:
if(masterReset = '1') then
而不是:
if(masterReset <= '1') then
现在,让我们从最可能导致您的初始问题的原因开始:未绑定的组件。您的测试平台将设计实例化以作为组件进行验证。 VHDL 组件是一种 原型 实际实体。原型足以编译,因为编译器可以执行所有必要的语法和类型检查。但它们不足以模拟,因为模拟器还需要原型背后的实现。一些工具对未绑定的组件有一个默认的绑定策略:如果它们找到一个具有相同名称的实体,并且如果它只有一个架构,它们就会使用那个。您的模拟器显然不使用这种策略(至少不是默认情况下,可能有一个选项但它被禁用)。请注意,我知道的大多数模拟器在发现未绑定的组件时都会发出警告。您可能错过了这些警告。
无论如何,您的组件实例是未绑定的(它们没有关联 entity/architecture)并且模拟器将它们视为黑盒。它们的输出不受驱动,除了您声明的初始值 (1)。
如何解决这个问题?两个选项:
使用配置指定每个组件实例应使用哪个 entity/architecture 对:
for all: scaler_clk use entity work.scaler_clk(Behavioral);
使用实体实例化而不是组件:
uut: entity work.scaler_clk(Behavioral) port map...
现在,让我们来看看您的代码中可以改进的其他一些方面:
您使用的是非标准包,通常甚至不兼容:IEEE.STD_LOGIC_ARITH
和 IEEE.STD_LOGIC_UNSIGNED
。因为它们不是标准的,所以它们甚至不应该出现在标准 IEEE
库中。您应该改用 IEEE.NUMERIC_STD
,并且只使用那个。它声明了 SIGNED
和 UNSIGNED
类型(与 STD_LOGIC_VECTOR
的声明相同)并重载了它们的算术运算符。
您的测试平台生成 100MHz 时钟:
clock_tic: process begin
loop
clk100mhz <= '0';
wait for 5ns;
clk100mhz <= '1';
wait for 5ns;
end loop;
end process;
无限循环没用:一个进程已经是无限循环了:
clock_tic: process
begin
clk100mhz <= '0';
wait for 5ns;
clk100mhz <= '1';
wait for 5ns;
end process clock_tic;
也会这样做。对您的 input_changes
流程也有同样的评论。
您的 input_changes
进程使用 wait for <duration>
语句。这不是一个好主意,因为与时钟相比,您不知道 inputSignal
信号何时切换。是在 clk100mhz
的上升沿之前、之后还是恰好同时?如果正好在同一时间,会发生什么?当然,您可以谨慎选择 <durations>
以避免此类歧义,但它很容易出错。您应该只在时钟生成过程中使用 wait for <duration>
。其他地方,最好和时钟同步:
input_changes: process
begin
inputSignal <= '0';
for i in 1 to 10000 loop
wait until rising_edge(clk100mhz);
end loop;
inputSignal <= '1';
for i in 1 to 10000 loop
wait until rising_edge(clk100mhz);
end loop;
inputSignal <= '1';
for i in 1 to 10000 loop
wait until rising_edge(clk100mhz);
end loop;
inputSignal <= '1';
for i in 1 to 10000 loop
wait until rising_edge(clk100mhz);
end loop;
end process input_changes;
这保证了inputSignal
在时钟上升沿之后立即改变。你可以用更优雅的方式重写它(并且可能更容易维护):
input_changes: process
constant values: std_logic_vector(0 to 3) := "0111";
begin
for i in values'range loop
inputSignal <= values(i);
for i in 1 to 10000 loop
wait until rising_edge(clk100mhz);
end loop;
end loop;
end process input_changes;
您正在使用已解析的类型(STD_LOGIC
和 STD_LOGIC_VECTOR
)。这些类型允许多重驱动,即具有由多个设备(VHDL 进程)驱动的硬件线(VHDL 信号)。通常你不想要这个。通常你甚至想像避免瘟疫一样避免这种情况,因为它会导致短路。在大多数情况下,使用未解析的类型(STD_ULOGIC
和 STD_ULOGIC_VECTOR
)更为明智,因为编译器 and/or 如果您在设计中不小心造成短路,模拟器将引发错误。
最后一件事:如果正如其名称所示,您打算将 clk10khz
信号用作真正的时钟,您应该重新考虑这个决定。这是您使用自定义逻辑生成的信号。时钟具有非常具体的电气和时序限制,常规信号无法真正满足这些限制。在将 clk10khz
用作时钟之前,您必须处理时钟偏差、时钟缓冲……并非不可能,但很棘手。如果您确实将它用作时钟,您的合成器可能会发出您也错过的严重警告(看看计时报告)。此外,这在您的情况下可能没有用:可以改用 clk100mhz
生成的启用信号,从而避免所有这些问题。而不是:
process (clk100mhz,masterReset) begin
if(masterReset = '1') then
clockScalers <= "0000000000000";
newClock <= '0';
indicator <= '1';
elsif (clk100mhz'event and clk100mhz = '1')then
indicator <= '0';
clockScalers <= clockScalers + 1;
if(clockScalers > prescaler) then
newClock <= not newClock;
clockScalers <= (others => '0');
end if;
end if;
end process;
使用:
signal tick10khz: std_ulogic;
...
process(clk100mhz, masterReset) begin
if masterReset = '1') then
clockScalers <= "0000000000000";
tick10khz <= '0';
elsif rising_edge(clk100mhz) then
clockScalers <= clockScalers + 1;
tick10khz <= '0'
if(clockScalers > prescaler) then
tick10khz <= '1';
clockScalers <= (others => '0');
end if;
end if;
end process;
然后,而不是:
process(clk10khz)
begin
if rising_edge(clk10khz) then
register <= register_input;
end if;
end process;
使用:
process(clk100mhz)
begin
if rising_edge(clk100mhz) then
if tick10khz = '1' then
register <= register_input;
end if;
end if;
end process;
结果相同,但只有一个 100MHz 时钟,这避免了时钟偏移、时钟缓冲和时钟域交叉问题。
(1) 这说明了为什么用初始值声明变量和信号通常不是一个好主意:它隐藏了潜在的问题。如果没有这个,您的信号将停留在 'U'
(未初始化),这可能有助于理解问题的根源。
我有在板上运行的 VHDL 实现,它检测到序列 01110 并将为 2 个时钟计数生成一个标志。它也检测重叠序列,其中 011101110 会升旗两次。
我已经使用电路板上的逻辑分析器检查了我的实现,并且非常有信心它可以正常工作。我以 10 kHz 的频率输入 0111 的重复序列,在板上,它有一个 100 MHz 的时钟,我用预分频器将其缩放到 10 kHz。
我的问题是,在尝试使用模拟重新创建类似场景时,我没有得到预期的任何输出
来自电路板逻辑分析仪的图像
图片来自测试台
测试平台代码
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity test_FSM_prac4 is
-- Port ( );
end test_FSM_prac4;
architecture Behavioral of test_FSM_prac4 is
component FSM_prac4 is
port (
inputSignal : in STD_LOGIC;
pushButton : in STD_LOGIC;
clk100mhz : in STD_LOGIC;
logic_analyzer : out STD_LOGIC_VECTOR (7 downto 0);
LEDs: out STD_LOGIC
); end component;
signal inputSignal : std_logic := '0';
signal pushButton: std_logic := '0';
signal clk100mhz: std_logic := '0';
signal logic_analyzer: std_logic_vector(7 downto 0);
signal LEDs : std_logic;
begin
uut : FSM_prac4 port map(
inputSignal => inputSignal,
pushButton => pushButton,
clk100mhz => clk100mhz,
logic_analyzer => logic_analyzer,
LEDs => LEDs
);
--generate clock 100mhz
clock_tic: process begin
loop
clk100mhz <= '0';
wait for 5ns;
clk100mhz <= '1';
wait for 5ns;
end loop;
end process;
input_changes: process begin
loop
inputSignal <= '0';
wait for 100us;
inputSignal <= '1';
wait for 100us;
inputSignal <= '1';
wait for 100us;
inputSignal <= '1';
wait for 100us;
end loop;
end process;
end Behavioral;
显示逻辑分析仪的映射
logic_analyzer(0) <= masterReset;
logic_analyzer(1) <= newClock -- 10Khz Clock;
logic_analyzer(2) <= outputZ;
--FSM States
logic_analyzer(3) <= '1' when y = A ELSE '0';
logic_analyzer(4) <= '1' when y = B ELSE '0';
logic_analyzer(5) <= '1' when y = C ELSE '0';
logic_analyzer(6) <= '1' when y = D ELSE '0';
logic_analyzer(7) <= '1' when y = E ELSE '0';
如果有人能指出我在测试台上做错了什么,以及如何复制以获得与第一张图像相似的结果,因为它显示在模拟中,它始终保持在状态 A 而新时钟不是切换意味着 clk100mhz
不知何故没有连接,但我不明白为什么。
非常感谢任何帮助,谢谢大家
编辑:
我写了一个简单的程序来测试我的标量时钟
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity scaler_clk is
Port (
pushButton : in std_logic;
indicator : out std_logic;
clk100mhz : in STD_LOGIC;
clk10khz: out STD_LOGIC
);
end scaler_clk;
architecture Behavioral of scaler_clk is
signal clockScalers : std_logic_vector (12 downto 0):= (others => '0') ;
signal prescaler: std_logic_vector(12 downto 0) := "1001110001000";
signal newClock: std_logic := '0';
signal masterReset : std_logic;
begin
clk10khz <= newClock;
masterReset <= pushButton;
process (clk100mhz,masterReset) begin
if(masterReset <= '1') then <--- error occurs here
clockScalers <= "0000000000000";
newClock <= '0';
indicator <= '1';
elsif (clk100mhz'event and clk100mhz = '1')then
indicator <= '0';
clockScalers <= clockScalers + 1;
if(clockScalers > prescaler) then
newClock <= not newClock;
clockScalers <= (others => '0');
end if;
end if;
end process;
end Behavioral;
测试平台代码
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity test_scaler_clk is
-- Port ( );
end test_scaler_clk;
architecture Behavioral of test_scaler_clk is
component scaler_clk Port (
pushButton : in std_logic;
indicator : out std_logic;
--input clock
clk100mhz : in STD_LOGIC;
clk10khz: out STD_LOGIC
);end component;
signal clk100mhz: std_logic := '0';
signal clk10khz : std_logic;
signal pushButton: std_logic;
signal indicator : std_logic;
begin
uut: scaler_clk port map(
pushButton => pushButton,
indicator => indicator,
clk100mhz => clk100mhz,
clk10khz => clk10khz
);
pushButton <= '0';
clock_tic: process begin
loop
clk100mhz <= '0';
wait for 5ns;
clk100mhz <= '1';
wait for 5ns;
end loop;
end process;
end Behavioral;
即使我将 pushButton 设置为“0”,它仍然会触发 masterReset
,任何人都知道为什么,这就是 10 kHz 时钟不工作的原因
您可以(应该)改进您的代码中的几件事。正如 Brian 已经解释的那样,在 scaler_clk
的 Behavioral
架构中,您应该:
if(masterReset = '1') then
而不是:
if(masterReset <= '1') then
现在,让我们从最可能导致您的初始问题的原因开始:未绑定的组件。您的测试平台将设计实例化以作为组件进行验证。 VHDL 组件是一种 原型 实际实体。原型足以编译,因为编译器可以执行所有必要的语法和类型检查。但它们不足以模拟,因为模拟器还需要原型背后的实现。一些工具对未绑定的组件有一个默认的绑定策略:如果它们找到一个具有相同名称的实体,并且如果它只有一个架构,它们就会使用那个。您的模拟器显然不使用这种策略(至少不是默认情况下,可能有一个选项但它被禁用)。请注意,我知道的大多数模拟器在发现未绑定的组件时都会发出警告。您可能错过了这些警告。
无论如何,您的组件实例是未绑定的(它们没有关联 entity/architecture)并且模拟器将它们视为黑盒。它们的输出不受驱动,除了您声明的初始值 (1)。
如何解决这个问题?两个选项:
使用配置指定每个组件实例应使用哪个 entity/architecture 对:
for all: scaler_clk use entity work.scaler_clk(Behavioral);
使用实体实例化而不是组件:
uut: entity work.scaler_clk(Behavioral) port map...
现在,让我们来看看您的代码中可以改进的其他一些方面:
您使用的是非标准包,通常甚至不兼容:
IEEE.STD_LOGIC_ARITH
和IEEE.STD_LOGIC_UNSIGNED
。因为它们不是标准的,所以它们甚至不应该出现在标准IEEE
库中。您应该改用IEEE.NUMERIC_STD
,并且只使用那个。它声明了SIGNED
和UNSIGNED
类型(与STD_LOGIC_VECTOR
的声明相同)并重载了它们的算术运算符。您的测试平台生成 100MHz 时钟:
clock_tic: process begin loop clk100mhz <= '0'; wait for 5ns; clk100mhz <= '1'; wait for 5ns; end loop; end process;
无限循环没用:一个进程已经是无限循环了:
clock_tic: process begin clk100mhz <= '0'; wait for 5ns; clk100mhz <= '1'; wait for 5ns; end process clock_tic;
也会这样做。对您的
input_changes
流程也有同样的评论。您的
input_changes
进程使用wait for <duration>
语句。这不是一个好主意,因为与时钟相比,您不知道inputSignal
信号何时切换。是在clk100mhz
的上升沿之前、之后还是恰好同时?如果正好在同一时间,会发生什么?当然,您可以谨慎选择<durations>
以避免此类歧义,但它很容易出错。您应该只在时钟生成过程中使用wait for <duration>
。其他地方,最好和时钟同步:input_changes: process begin inputSignal <= '0'; for i in 1 to 10000 loop wait until rising_edge(clk100mhz); end loop; inputSignal <= '1'; for i in 1 to 10000 loop wait until rising_edge(clk100mhz); end loop; inputSignal <= '1'; for i in 1 to 10000 loop wait until rising_edge(clk100mhz); end loop; inputSignal <= '1'; for i in 1 to 10000 loop wait until rising_edge(clk100mhz); end loop; end process input_changes;
这保证了
inputSignal
在时钟上升沿之后立即改变。你可以用更优雅的方式重写它(并且可能更容易维护):input_changes: process constant values: std_logic_vector(0 to 3) := "0111"; begin for i in values'range loop inputSignal <= values(i); for i in 1 to 10000 loop wait until rising_edge(clk100mhz); end loop; end loop; end process input_changes;
您正在使用已解析的类型(
STD_LOGIC
和STD_LOGIC_VECTOR
)。这些类型允许多重驱动,即具有由多个设备(VHDL 进程)驱动的硬件线(VHDL 信号)。通常你不想要这个。通常你甚至想像避免瘟疫一样避免这种情况,因为它会导致短路。在大多数情况下,使用未解析的类型(STD_ULOGIC
和STD_ULOGIC_VECTOR
)更为明智,因为编译器 and/or 如果您在设计中不小心造成短路,模拟器将引发错误。最后一件事:如果正如其名称所示,您打算将
clk10khz
信号用作真正的时钟,您应该重新考虑这个决定。这是您使用自定义逻辑生成的信号。时钟具有非常具体的电气和时序限制,常规信号无法真正满足这些限制。在将clk10khz
用作时钟之前,您必须处理时钟偏差、时钟缓冲……并非不可能,但很棘手。如果您确实将它用作时钟,您的合成器可能会发出您也错过的严重警告(看看计时报告)。此外,这在您的情况下可能没有用:可以改用clk100mhz
生成的启用信号,从而避免所有这些问题。而不是:process (clk100mhz,masterReset) begin if(masterReset = '1') then clockScalers <= "0000000000000"; newClock <= '0'; indicator <= '1'; elsif (clk100mhz'event and clk100mhz = '1')then indicator <= '0'; clockScalers <= clockScalers + 1; if(clockScalers > prescaler) then newClock <= not newClock; clockScalers <= (others => '0'); end if; end if; end process;
使用:
signal tick10khz: std_ulogic; ... process(clk100mhz, masterReset) begin if masterReset = '1') then clockScalers <= "0000000000000"; tick10khz <= '0'; elsif rising_edge(clk100mhz) then clockScalers <= clockScalers + 1; tick10khz <= '0' if(clockScalers > prescaler) then tick10khz <= '1'; clockScalers <= (others => '0'); end if; end if; end process;
然后,而不是:
process(clk10khz) begin if rising_edge(clk10khz) then register <= register_input; end if; end process;
使用:
process(clk100mhz) begin if rising_edge(clk100mhz) then if tick10khz = '1' then register <= register_input; end if; end if; end process;
结果相同,但只有一个 100MHz 时钟,这避免了时钟偏移、时钟缓冲和时钟域交叉问题。
(1) 这说明了为什么用初始值声明变量和信号通常不是一个好主意:它隐藏了潜在的问题。如果没有这个,您的信号将停留在 'U'
(未初始化),这可能有助于理解问题的根源。