I'm attempting to create an I2C Bus, however I've stumbled into a very awkward problem - during the mapping part of implementation I get the warning that MapLib:701 - Signal SDA connected to top level port SDA has been removed.
After digging back through, I found this was caused by a previous warning in the I2C Master itself: Xst:1293 - FF/Latch <sda_internal> has a constant value of 1 in block <IIC_MASTER>. This FF/Latch will be trimmed during the optimization process.
On looking through, it seems that the entire state machine has been optimized away - why is this and how can I stop it? In the logic simulation below, it works as expected and I have no idea exactly what I've done that makes it think that the entire state machine is redundant!.
I've put the image of what I expect to see below, and then the two code blocks describing the I2C Master block and the top level block. Any help at all is massively appreciated!
Top Level
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;
ENTITY I2CBus IS
PORT(
SYSCLK_N : IN STD_LOGIC; --system 200MHz differential clock
SYSCLK_P : IN STD_LOGIC;
BTN : IN STD_LOGIC; -- to manually change reset
LED : OUT STD_LOGIC; --to observe reset value
SCL : OUT STD_LOGIC;
SDA : INOUT STD_LOGIC
);
END I2CBus;
ARCHITECTURE behavior OF I2CBus IS
-------------------DECLARE MASTER & SLAVE COMPONENTS------------------------
COMPONENT IIC_MASTER
PORT(SCL : IN STD_LOGIC;
SCL2X : IN STD_LOGIC;
RESET_N : IN STD_LOGIC;
ENA : IN STD_LOGIC;
ADR : IN STD_LOGIC_VECTOR(6 DOWNTO 0);
REG : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
RW : IN STD_LOGIC;
DAT_WR : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
BUSY : OUT STD_LOGIC;
SDA : INOUT STD_LOGIC;
ACK_ERR : BUFFER STD_LOGIC);
END COMPONENT IIC_MASTER;
COMPONENT DCM
PORT(
SYSCLK_P : IN STD_LOGIC; -- CLOCK IN PORTS 200MHZ DIFFERENTIAL
SYSCLK_N : IN STD_LOGIC;
-- CLOCK OUT PORTS
SYSCLK : OUT STD_LOGIC
);
END COMPONENT;
COMPONENT CLK_DIVIDER
GENERIC(INPUT_FREQ : INTEGER;
OUT1_FREQ : INTEGER;
OUT2_FREQ : INTEGER);
PORT(SYSCLK : IN STD_LOGIC;
RESET_N : IN STD_LOGIC;
OUT1 : OUT STD_LOGIC;
OUT2 : OUT STD_LOGIC);
END COMPONENT CLK_DIVIDER;
-------------------I2C MASTER ONLY SIGNALS-----------------
--Inputs
signal reset_n : std_logic; --active high
--Outputs
signal busy : std_logic;
signal ack_err : std_logic;
-----------------------------------------------------------
signal SCL_internal : std_logic;
signal SCL2X_internal : std_logic;
signal sysclk : std_logic;
BEGIN
--------------------I2C_master Instantiation-------------------
master : IIC_Master
port map(
SCL => SCL_internal, --map constant data to send over I2C for now
SCL2X => SCL2X_internal,
RESET_N => RESET_N,
ENA => '1', --hold enable on
ADR => "1011001", --keep address constant
REG => x"AA", --keep target register constant
RW => '0', --always read
DAT_WR => x"77", --keep data to send constant
BUSY => BUSY,
SDA => SDA,
ACK_ERR => ACK_ERR
);
DCM_CLK : DCM
port map(
SYSCLK_P => SYSCLK_P,
SYSCLK_N => SYSCLK_N,
SYSCLK => sysclk --generate 200 MHz clock from system clocks
);
Clk_Div : Clk_Divider --Divide 200MHz clock down to frequencies that can be
generic map( --seen on an oscilloscope
INPUT_FREQ => 200000000, --200 MHz input
OUT1_FREQ => 10, --10 Hz output
OUT2_FREQ => 20 --20 Hz output
)
port map(
SYSCLK => sysclk, --system clock
RESET_N => reset_n, --reset
OUT1 => scl_internal,
OUT2 => scl2x_internal
);
----------------Mappings---------------------------
reset_n <= not BTN; --reset low when button pressed
LED <= not reset_n; --light LED when reset
SCL <= 'Z' when scl_internal = '1' else scl_internal;
end behavior;
I2C Master Block
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity IIC_MASTER IS
PORT(SCL : IN STD_LOGIC; --SCL clock
SCL2X : IN STD_LOGIC; --SCL x2 rate clock
RESET_N : IN STD_LOGIC; --ACTIVE LOW
ENA : IN STD_LOGIC; --ENABLE ACTIVE HIGH
ADR : IN STD_LOGIC_VECTOR(6 DOWNTO 0); --TARGET ADDRESS
REG : IN STD_LOGIC_VECTOR(7 DOWNTO 0); --TARGET REGISTER
RW : IN STD_LOGIC; --READ LOW, WRITE HIGH
DAT_WR : IN STD_LOGIC_VECTOR(7 DOWNTO 0); --DATA TO WRITE TO SLAVE
BUSY : OUT STD_LOGIC; --HIGH WHEN BUSY
SDA : INOUT STD_LOGIC; --SERIAL DATA ON BUS
ACK_ERR : BUFFER STD_LOGIC); --FLAG IF WRONG ACK FROM SLAVE
END IIC_MASTER;
architecture BEHAVIORAL of IIC_MASTER is
type state is (begn, ready, start, command, cmd_ack, reg_cmd, reg_ack, wr, rd, data_ack, stop);
signal i2cstate : state;
signal sda_internal : std_logic := '1'; --internal SDA
signal scl_internal : std_logic;
signal scl2x_internal : std_logic;
signal addr_rw : std_logic_vector(7 downto 0); --latched address (6-0) + rw (7)
signal reg_tx : std_logic_vector(7 downto 0); --latched slave register
signal data_tx : std_logic_vector(7 downto 0); -- data to write to slave
signal bit_cnt : integer range 0 to 7 := 7; --number of bits sent in transaction
begin
------state machine logic & writing to SDA on data clk rising edge-----
state_machine : process(scl2x_internal)
begin
if falling_edge(scl2x_internal) then
if reset_n = '0' then --when reset is low, set to ready i2cstate <= ready; --NOT IN TEMPLATE
busy <= '1'; --set to busy
sda_internal <= '1'; --disable sda to Z
ACK_ERR <= '0'; --clear error/ack flag
bit_cnt <= 7; --reset bit count
i2cstate <= ready;
else
if scl_internal = '0' then --in middle of clock low
case i2cstate is
when begn => i2cstate <= ready; --unconditional transition
when ready => if ENA = '1' then
BUSY <= '1'; --set busy
addr_rw <= ADR & RW; --get address & rw (concatenate)
reg_tx <= REG; --get target register
data_tx <= DAT_WR; --Collect data to write from port
i2cstate <= start; --update state
else
BUSY <= '0'; --set not busy
i2cstate <= ready; --remain ready
end if;
when start =>
BUSY <= '1'; --ensure busy remains on
bit_cnt <= 7; --reset bit counter for bytes
ACK_ERR <= '0';
sda_internal <= addr_rw(bit_cnt); --put first command bit on bus
i2cstate <= command; -- also an unconditional transition
when command =>
if bit_cnt = 0 then
sda_internal <= '1'; --set high for acknowledge
bit_cnt <= 7; --reset bit counter for bytes
i2cstate <= cmd_ack;
else
bit_cnt <= bit_cnt - 1; --decrement bit count
sda_internal <= addr_rw(bit_cnt - 1); --send next address bit on bus
i2cstate <= command; --stay in this state until command is sent
end if;
when cmd_ack =>
sda_internal <= reg_tx(bit_cnt); --write first register bit
i2cstate <= reg_cmd; --go to register sending state
when reg_cmd =>
if bit_cnt = 0 then --register transmitted
sda_internal <= '1'; --release internal sda for acknowledgement
bit_cnt <= 7; --reset bit count
i2cstate <= reg_ack; --go to reg ack
else
bit_cnt <= bit_cnt - 1; --decrement
sda_internal <= reg_tx(bit_cnt - 1); --write next reg bit to bus
i2cstate <= reg_cmd; --keep writing
end if;
when reg_ack =>
if addr_rw(0) = '0' then -- if read/write is high, read
sda_internal <= data_tx(bit_cnt); --write first data bit
i2cstate <= wr; -- go to write state
else --else if low, write
sda_internal <= '1'; --release internal sda for reading
i2cstate <= rd; --go to read state
end if;
when wr =>
BUSY <= '1'; --ensure busy flag still high
if bit_cnt = 0 then --byte transmitted
sda_internal <= '1'; --release internal sda for acknowledgement
bit_cnt <= 7; --reset bit count
BUSY <= '0'; -- data all written, so lower busy to notify others
i2cstate <= data_ack; --go to slav ack
else
bit_cnt <= bit_cnt - 1; --decrement
sda_internal <= data_tx(bit_cnt - 1); --write next bit to bus
i2cstate <= wr; --keep writing
end if;
when rd => null;
when data_ack => --acknowledge write
if ENA = '1' then --continue transaction
BUSY <= '0'; -- accept continue by keeping busy low
addr_rw <= ADR & RW; --fetch next address & command
data_tx <= DAT_WR; --fetch next data to write
reg_tx <= REG; --fetch next register
if addr_rw = ADR & RW and reg_tx = REG then --if the same location
sda_internal <= DAT_WR(bit_cnt); --write first bit of data
i2cstate <= wr; --go to write state
else --continue next transaction with a new read/write or slave
i2cstate <= start; --continue from start
end if;
else
i2cstate <= stop; --transaction done, go to end
sda_internal <= '0';
end if;
when stop =>
BUSY <= '0'; --set not busy
i2cstate <= ready;
end case;
elsif scl_internal = '1' then --in middle of clock high
case i2cstate is
when start => sda_internal <= '0';
when stop => sda_internal <= '1';
when cmd_ack | reg_ack | data_ack =>
if (SDA /= '0' or ACK_ERR = '1') then
ACK_ERR <= '1'; --no acknowledge or prev. error
end if;
when others => null;
end case;
end if;
end if;
end if;
end process;
scl_internal <= '1' when SCL = 'Z' else SCL;
scl2x_internal <= '1' when SCL2X = 'Z' else SCL2X;
SDA <= 'Z' when sda_internal = '1' else '0';
end BEHAVIORAL;
scl_internal <= '1' when SCL = 'Z' else SCL;
scl2x_internal <= '1' when SCL2X = 'Z' else SCL2X;
You cannot test for equality to 'Z'
and synth should warn about these. Simulation can, but there is no hardware block that works that way for synthesis to use.
I2C signals use a WIRED-OR convention, you drive 'Z' but you must test for '0'
or '1'
Specifically, logic high can be represented by either 'H'
or '1'
, the former being a weak form of '1'
. This means that testing for '1'
may synthesise to the right thing, but simulation will no longer work...
So what's happening is that synthesis is seeing invalid logic for SCL, which allows it to delete the lot.
Solutions:
First, in your testbench, drive 'H'
on SCL and SDA permanently. Because it's weak, (like a pullup), '0'
can override it.
Secondly, either explicitly test for '0'
:
scl_internal <= 0 when SCL = '0' else '1';
scl2x_internal <= '0' when SCL2X = '0' else '1';
or explicitly test for 'H'
as well as '1'
scl_internal <= '1' when SCL = 'H' or SCL = '1' else SCL;
scl2x_internal <= '1' when SCL2X = 'H' or SCL2X = '1' else SCL2X;
or use the "to_01xz" function to collapse 'H'
and '1'
into the same value
scl_internal <= '1' when to_01xz(SCL) = '1' else SCL;
scl2x_internal <= '1' when to_01xz(SCL2X) = '1' else SCL2X;