VHDL 代码在综合后表现异常 (I2C)

VHDL code behaves abnormally after synthesis (I2C)

首先,由于公司披露协议,我不允许 post 我的代码,因此我将描述行为的症状,希望这就足够了。

我正在使用 Scott Larson on eewiki 提供的 I2C master IP,我在页面上提供的示例中以相同的方式使用它。不同的是,我有几个例子中描述的程序

WHEN get_data =>                               --state for conducting this transaction
busy_prev <= i2c_busy;                       --capture the value of the previous i2c busy signal
IF(busy_prev = '0' AND i2c_busy = '1') THEN  --i2c busy just went high
    busy_cnt := busy_cnt + 1;                  --counts the times busy has gone from low to high during transaction
END IF;

CASE busy_cnt IS                             --busy_cnt keeps track of which command we are on
    WHEN 0 =>                                  --no command latched in yet
        i2c_ena <= '1';                            --initiate the transaction
        i2c_addr <= slave_addr;                    --set the address of the slave
        i2c_rw <= '0';                             --command 1 is a write
        i2c_data_wr <= data_to_write;              --data to be written

    WHEN 1 =>                                  --1st busy high: command 1 latched, okay to issue command 2
        i2c_rw <= '1';                             --command 2 is a read (addr stays the same)

    WHEN 2 =>                                  --2nd busy high: command 2 latched, okay to issue command 3
        i2c_rw <= '0';                             --command 3 is a write
        i2c_data_wr <= new_data_to_write;          --data to be written
        IF(i2c_busy = '0') THEN                    --indicates data read in command 2 is ready
            data(15 DOWNTO 8) <= i2c_data_rd;       --retrieve data from command 2
        END IF;

    WHEN 3 =>                                  --3rd busy high: command 3 latched, okay to issue command 4
        i2c_rw <= '1';                             --command 4 is read (addr stays the same)

    WHEN 4 =>                                  --4th busy high: command 4 latched, ready to stop
        i2c_ena <= '0';                            --deassert enable to stop transaction after command 4
        IF(i2c_busy = '0') THEN                    --indicates data read in command 4 is ready
            data(7 DOWNTO 0) <= i2c_data_rd;         --retrieve data from command 4
            busy_cnt := 0;                           --reset busy_cnt for next transaction
            state <= home;                           --transaction complete, go to next state in design
        END IF;

    WHEN OTHERS => NULL;

END CASE;

例如最后如果 get_data 而不是去 home 我去初始化情况重置 busy_cnti2c_ena 和其他信号然后到另一种写法类似 get_data.

的情况

现状

问题

正如我之前提到的,代码分为几种类似于上面示例中的get_data的情况,并且在执行时在其中一种情况下陷入死循环。当我在另一个案例中更改某些东西时,它会在它行为不端并且陷入无限循环之前卡住另一个案例。即使在一种情况下更改与算法无关的简单信号(用于调试的 LED 输出)也可能导致之前的情况出现异常。

无限循环行为也很奇怪,并显示在案例结束时未设置状态。

请注意,我没有在示例中使用相同的方式来更新状态,而是使用单独的进程来更新状态 (curr_state <= next_state;),而 next_state 是更新的进程。

我的猜测 我认为问题可能是因为我必须在每种情况下设置每个输出。但即使在我设置了所有设置之后,行为也是相似的。

为了完成:开发环境是Lattice Diamond,FPGA是MachXO2。

发布的代码片段存在严重问题,如果根据评论它不再是时钟进程的一部分:

Me: Is the posted code still part of a clocked process?

mmahdich: @Martin In my code it's not, the clocked process only updates the curr_state <= next_state

如果我将 code from the question 嵌入到以下测试架构中,那么综合编译器 XST(来自 ISE 14.7)会报告有关信号 busy_cntbusy_prevdata。 OP 没有观察到闩锁警告的原因可能是由于未公开代码部分的进一步优化或干扰。 (我手头没有格子钻石。)

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

entity i2c_test is
  port (
    clk               : in  std_logic;
    i2c_busy          : in  std_logic;
    slave_addr        : in  std_logic;
    data_to_write     : in  std_logic_vector(7 downto 0);
    new_data_to_write : in  std_logic_vector(7 downto 0);
    i2c_data_rd       : in  std_logic_vector(7 downto 0);
    i2c_ena           : out std_logic;
    i2c_addr          : out std_logic;
    i2c_rw            : out std_logic;
    i2c_data_wr       : out std_logic_vector(7 downto 0);
    data              : out std_logic_vector(15 downto 0));
end i2c_test;

architecture rtl of i2c_test is
  type   state_t is (get_data, home);
  signal curr_state : state_t := home;
  signal state      : state_t;          -- next state is named "state" in OP code
  signal busy_prev  : std_logic;
begin  -- rtl
  process (clk)
  begin  -- process
    if rising_edge(clk) then
      curr_state <= state;
    end if;
  end process;

  process(curr_state, busy_prev, i2c_busy, slave_addr, data_to_write, new_data_to_write, i2c_data_rd)
    variable busy_cnt : integer range 0 to 4 := 0;
  begin
    state <= curr_state;              -- next state is named "state" in OP code
    i2c_ena <= '0';
    i2c_addr <= '-';
    i2c_rw  <= '-';
    i2c_data_wr <= (others => '-');
    -- no default assignments for busy_prev and data here, because the usage
    -- below indicates that a register was intended

    case curr_state is
      when home => state <= get_data;

    ---------------------------------------------------------------
    -- Add code from question here
    ---------------------------------------------------------------

    
    end case;
  end process;
end rtl;

首先,下一个状态的信号似乎被命名为state

然后,XST 为信号 busy_prevdata 找到锁存器。我没有在组合过程中为这些信号添加默认分配,因为 OP 代码中的以下分配表明需要一个寄存器:

busy_prev <= i2c_busy;              --capture the value of the previous i2c busy signal
data(15 DOWNTO 8) <= i2c_data_rd;   --retrieve data from command 2
data(7 DOWNTO 0) <= i2c_data_rd;    --retrieve data from command 4

最后,如果没有时钟进程,就不能(轻易地)实现这个等待计数器:

WHEN get_data =>                               --state for conducting this transaction
  IF(busy_prev = '0' AND i2c_busy = '1') THEN  --i2c busy just went high
    busy_cnt := busy_cnt + 1;                  --counts the times busy has gone from low to high during transaction
  END IF;

编辑 综合以上内容需要 busy_cnt 的触发器,它由过程灵敏度列表中列出的所有信号触发。当然,只有当条件 (curr_state = get_data and busy_prev = '0' and i2c_busy = '1') 为真时,才会将新状态加载到触发器中。例如,XST 为此合成了一个锁存器,当条件为真时启用。但是随后,busy_cnt 在启用锁存器期间形成组合循环。此综合行为与 VHDL 描述不符。

解决方案是在时钟进程中实现寄存器 busy_prevdatabusy_cnt