Mahdi Chamseddine
Mahdi Chamseddine

Reputation: 387

VHDL code behaves abnormally after synthesis (I2C)

First of all, because of company disclosure agreement I am not allowed to post my code thus I will describe the symptoms of the behavior and hopefully it's enough.

I am using the IP for I2C master provided by Scott Larson on eewiki and I use it the same way in the example provided on the page. The difference is that I have several procedures as described in the example

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;

For example at the end if the get_data instead of going to home I go to an initializing case to reset busy_cnt and i2c_ena and other signals then to another case for writing similar to get_data.

Current Situation

The Problem

As I mentioned before the code is divided into several cases similar to get_data in the example above and when executing it is stuck in an infinite loop in one of them. And when I change something in the case that is stuck another case before it misbehaves and also stuck in an infinite loop. Even changing a simple signal that has nothing to do with the algorithm (LED output for debugging) in one case might cause a case before it to misbehave.

The infinite loop behavior is also strange and shows state is not set at the end of the case.

Note that I am not using the same way in the example to update state rather I use a separate Process for the state update (curr_state <= next_state;) and the next_state is the one updated.

My Speculation I assumed that the problem could be because I have to set every output in every case. But even after setting all I could the behavior was similar.

For the sake of completion: The development environment is Lattice Diamond and the FPGA is MachXO2.

Upvotes: 2

Views: 503

Answers (1)

Martin Zabel
Martin Zabel

Reputation: 3659

The posted code snippet has severe problems, if it's not part of a clocked process anymore according to the comments:

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

If I embed the code from the question into the following test architecture, then the synthesis compiler XST (from ISE 14.7) reports warnings about latches for the signals busy_cnt, busy_prev and data. The reason that the OP observes no latch warnings may result from further optimizations or interference from the undisclosed code parts. (I have no Lattice Diamond at hand.)

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;

First of all, the signal for the next state seems to be named state.

Then, XST founds latches for the signals busy_prev and data. I have not added default assignments for these signals in the combinational process because the following assignments in the OP's code indicate that a register was intended:

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

Finally, one cannot (easily) implement this wait counter without a clocked process:

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;

EDIT Synthesizing the above would require flip-flops for busy_cnt which is triggered by all signals listed in the process sensitivity list. Of course, a new state will be loaded into the flip-flops only if the condition (curr_state = get_data and busy_prev = '0' and i2c_busy = '1') is true. For example, XST synthesizes a latch for this, which is enabled when the condition is true. But then, busy_cnt forms a combinational loop during an enabled latch. This synthesized behaviour does not match the VHDL description.

The solution would be to implement the registers busy_prev, data and busy_cnt in the clocked process.

Upvotes: 1

Related Questions