如何将复杂的 FSM 拆分成多个部分?
这里有一些示例行来说明一个主 FSM 和两个函数 FSM 之间的简单调用/返回握手协议
process(Clock)
begin
if (Reset = '1') then
MainFSM_State <= ST_IDLE;
Func1FSM_State <= ST_IDLE;
Func2FSM_State <= ST_IDLE;
else
MainFSM_State <= MainFSM_NextState;
Func1FSM_State <= Func1FSM_NextState;
Func2FSM_State <= Func2FSM_NextState;
end if;
end process;
process(MainFSM_State, Input, ...,
Func1FSM_Output, Func1FSM_Return,
Func2FSM_Output, Func2FSM_Return)
begin
Func1FSM_Call <= '0';
Func2FSM_Call <= '0';
MainFSM_Output <= x"00";
case MainFSM_State is
when ST_IDLE =>
if (Input = '1') then
Func1FSM_Call <= '1';
MainFSM_NextState <= ST_FUNC1_EXECUTING;
end if;
when ST_FUNC1_EXECUTING =>
MainFSM_Output <= Func1FSM_Output;
if (Func1FSM_Return = '1') then
Func2FSM_Call <= '1';
end if;
....
end case;
end process;
您的设计的一些一般提示(基于代码 sn-p)
使用大 FSM 也很容易出错。所以编写大 FSM 的一个目标应该是可读性,这样你就可以更容易地发现错误。
在使用 numeric_std 时不要使用 std_logic_arith、std_logic_unsigned。只使用 numeric_std 就足够了。
-
wait until 不是一个可综合的陈述。使用:
if rising_edge(clk) then
...
end if;
您的外部输入(如 reset_switch)是否已同步和去抖动?像 reset_switch 这样的信号名称意味着这个信号是原始的;)
您可以通过使用 case 语句来提高 if..elsif..else..end if; 语句的可读性(参见第 230 行)
你使用了大量的二进制赋值,比如t_left_most <= "010111"; --23。也许最好用另一种类型(例如受约束的自然)声明 t_left_most,这样您就可以在 FSM 中使用整数文字。为了将这些信号导出到其他模块,信号被转换回 std_logic_vector。 -> 提高可读性
-
有时引入常量是很好的,例如
constant C_TOP_MOST : NATURAL := 0;
-
我建议您从您的 FSM 中提取您的计数器 memReadAddress。大 FSM 应该只生成流控制信号,FSM 不应该包含任何数据流逻辑(计数器、多路复用器……)。因此,将计数器提取到一个单独的进程中,并将控制信号添加到您的大型 FSM。
when T_block_1 =>
ReadAddressCounter_en <= '1';
就我个人而言,我使用 _rst、_en、_us、_inc、_dec 之类的前缀作为计数器控制信号。 _us 代表无符号,因为我的计数器是无符号计数器,如果需要,该值将转换为 std_logic。
FSM“遥控”信号的反例:
process(Clock)
begin
if rising_edge(Clock) then
if ((Reset or MemAddressCounter_rst) = '1') then
MemAddressCounter_us <= (others => '0');
else
if (MemAddressCounter_load = '1') then
MemAddressCounter_us <= to_unsigned(5, MemAddressCounter_us'length);
elsif (MemAddressCounter_inc = '1') then
MemAddressCounter_us <= MemAddressCounter_us + 1;
end if;
end if;
end if;
end process;
MemAddress <= std_logic_vector(MemAddressCounter_us);
-
第 286 行不应该是可综合的(我认为 Xilinx XST 不够聪明,如果是这样,那么 XST 会产生什么是不可预测的......)
dataIn<=dataOut(39 downto conv_integer(T_left_most+1))&T_square&T_square&T_square&dataOut(conv_integer(T_right_most-1) downto 0);
那么你如何修复我的提示列表中的最后一个点呢?
- 简单方法:使用 40:1 多路复用器
- 更高级的方法:尝试使用掩码和位移操作
- 使用另一种数据结构,更适合硬件:)
如果您想对此点进行更详细的讨论,请发表评论,我将尝试扩展我的答案。
扩展答案-位运算解决方案
让我们总结一下第 286 行:
dataIn <= dataOut(39 downto conv_integer(T_left_most+1)) &
T_square & T_square & T_square &
dataOut(conv_integer(T_right_most-1) downto 0);
您的结果由 3 部分组成:
- 数据输出从 MSB 切分到动态标记
- 3 位
- 数据输出从动态标记切分到 0
这种位插入也可以通过按位设置/清除操作来完成:
- 设置:
result <= input or set_mask;
- 清除:
result <= input and not clear_mask;
这可以组合成一个位覆盖操作:
- 覆盖:
result <= (input and not override_mask) or override_value;
- 安全覆盖
result <= (input and not override_mask) or (override_value and override_mask);
如果您不能假设 override_value 仅在 override_mask 范围内设置位,则必须使用后者。
下一步是将 T_square 移动到正确的位置。可以通过桶形移位器或乘法器(DSP 模块)快速移动位。因为您的向量有 40 位,所以桶形移位器比 DSP 模块更快。
64 位桶形移位器有一个 6 位 shift_value 输入。输入的每一位指定该值是否应移动 2 的幂。
entity arith_BarrelShifter is
generic (
BITS : POSITIVE := 32
);
port (
Input : in STD_LOGIC_VECTOR(BITS - 1 downto 0);
ShiftAmount : in STD_LOGIC_VECTOR(log2ceilnz(BITS) - 1 downto 0);
Output : out STD_LOGIC_VECTOR(BITS - 1 downto 0)
);
end;
architecture rtl of arith_BarrelShifter is
constant STAGES : POSITIVE := log2ceilnz(BITS);
subtype T_INTERMEDIATE_RESULT is STD_LOGIC_VECTOR(BITS - 1 downto 0);
type T_INTERMEDIATE_VECTOR is array (NATURAL range <>) of T_INTERMEDIATE_RESULT;
signal IntermediateResults : T_INTERMEDIATE_VECTOR(STAGES downto 0);
begin
IntermediateResults(0) <= Input;
Output <= IntermediateResults(STAGES);
for i in 0 to STAGES - 1 generate
process(IntermediateResults(i), ShiftRotate, LeftRight, ArithmeticLogic)
begin
if (ShiftAmount(i) = '0') then
IntermediateResults(i + 1) <= IntermediateResults(i);
else
IntermediateResults(i + 1) <= IntermediateResults(i)((BITS - i**2 - 1) downto 0) & ((i**2 - 1) downto 0 => '0'); -- SLA, SLL
end if;
end process;
end generate;
end;
所以现在我们可以合并所有步骤:
override_value <= (39 downto 3 => '0') & (2 downto 0 => T_square);
override_mask <= (39 downto 3 => '0') & (2 downto 0 => '1');
shift_value : entity PoC.arith_BarrelShifter
generic map (
BITS => 40
)
port map (
Input => override_value,
ShiftAmount => T_left_most,
Output => shifted_value
);
shift_mask : entity PoC.arith_BarrelShifter
generic map (
BITS => 40
)
port map (
Input => override_mask,
ShiftAmount => T_left_most,
Output => shifted_mask
);
-- clear bits from source / set bits from value
dataIn <= (dataOut and not shifted_mask) or shifted_value;
希望我没有犯错:)