Search code examples
vhdlstate-machinexilinxsynthesisxilinx-ise

Synthesised Synthesis/Implementation


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!

enter image description here

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;

Solution

  • 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;