该项目是关于在FPGA板“spartan 6 lx9”上向MicroBlaze项目添加用户自定义外设核心 . 使用ISE Design Suite 14.6和EDK .
我的问题是在编写VHDL代码方面经验不足 . 我仍然在信号上获得1位无意的锁存:“data_bits”和“latest_value”从<0>直到<15>,即使我已经使用推荐的编码方式进行信号分配 . 我已经设置了默认值,但没有成功...在case语句的每个分支中分配信号不是一个选项,因为我想保留值,特别是“data_bits”,因为这个向量是从几个时钟周期构建的 . 我试图解决这个问题好几天了 .
我的问题是:
-
如何在这种有限状态机设计中修复锁存问题? --Answered
-
我想获得有关我的状态机设计,造型等方面的反馈. --Answered, but there is new code
-
任何关于在一个状态下停留几个时钟周期的设计建议,使用计数器还是有更好的技术? --Still expecting some advice
Initial Source Code:
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并添加有关我的问题的更多信息,以便让其他有类似问题的人更容易理解!
Block Diagram of system:
http://i.stack.imgur.com/odCwR.png
Note: 现在我只想在没有MicroBlaze和AXI互连模块的情况下模拟和验证独立的adc_core本身 .
FSM Diagram: http://i.stack.imgur.com/5qFdN.png
Single process source code:
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 回答
您的代码存在许多问题 . 为了说明其中的一些,我试图在图中描绘你的有限状态机 . 下面的1和2,基于您提供的VHDL代码 .
首先也是最重要的是,设计应从顶层框图开始,显示电路端口(如图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月 .
我的解决方案是始终使用时钟流程来处理所有事情 . 不需要为状态寄存器设置单独的时钟进程,也不需要为状态转换设置单独的进程 . 这是几年前需要的风格 . 在我看来,你最好把所有东西都放在一个时钟进程中然后你就不能得到锁存器 .
如果必须使用两个进程,则获取VHDL 2008编译器并使用
process(all)
确保所有信号都正确地位于灵敏度列表中,然后小心确保分配给的每个信号都获得通过这个过程的每条逻辑路径 . 实现这一目标的最简单方法通常是在流程开始时为它们分配所有'default'值 .如果未在所有可能的路径上分配信号,则会获得锁存器,因为它会变为有状态 .
为避免此问题,请确保始终为信号指定值(一种方法是在过程顶部指定“默认”值) .
"Retaining a value"表示状态,而不是纯粹的组合逻辑 . 在这种情况下,它不应该在您的输出过程中 . 它应该在您的状态更新过程中 .
在组合过程中(如
FSM_Output
),您不应该读取信号并写入相同的信号 . 对于latest_value
和data_bits
来说,这正是这里发生的事情 .创建新信号latest_value_r和data_bits_r并复制时钟进程中的值,或者坚持单个时钟进程而不进行单独的组合过程 .
你想要data_bits和latest_value的硬件是什么?如果要在几个时钟周期内构建向量,则需要一个存储设备 . 您的选择是:锁存器(电平敏感存储器)和触发器(边缘敏感存储器) . 如果你不想要锁存器,那么你必须编写触发器代码 .
要编写触发器代码,请使用“if clk ='1'和clk'event then”,就像在TimerReg进程中一样 . 您也可以使用“if rising_edge(Clk)then” - 我更喜欢这种可读性,但工具并不关心 .
我认为你出错的地方在你的计划过程中 . 代码只是设计捕获 . 重要的是,您需要使用方框图进行规划,并了解设计需要触发器的位置以及需要组合逻辑的位置 . 做到这一点,其余的只是应用编码模板 . 因此,在开始编码之前,请确保您已理解这一点 .
无论您是使用时钟进程进行编码还是使用时钟和组合逻辑进程的混合,都无关紧要 . 我认为你在编码中做的最重要的事情就是让它具有可读性 . 如果你收集意见,你会发现它们各不相同,@ Martin和@Brian更喜欢一个时钟进程 . 我更喜欢2进程状态机 - 触发器和组合(当前状态到下一状态和输出解码) . 你使用了一个3进程状态机 - 对我来说就像绘制一个气泡图来显示状态转换,另一个用于显示输出转换 . 然而,在一天结束时,他们都捕获了相同的意图 . 只要有人在您离开后很长时间内阅读您的代码就很清楚,那应该没问题 .