Search code examples
vhdlbidirectional

Bidirectional to bidirectional in VHDL


So I've heard that passing through a bidirectional data port without knowing the protocol (or without having a control line) cannot be done. (See tie two inout together vhdl)

However, I'd REALLY like to do this and I'd really like to not have to know the protocol. SO...

I would like to pass the DATA line from a SIM smart card (from a phone). Currently everything works except the DATA line, which is bidirectional.

I have yet to confirm this but my scoping hints that the line is pulled high and either side is allowed to pull it down as needed. I would like to take advantage of the line being pulled high externally.

I would like to try two options as laid out in the commented code below:

architecture Behavioral of SIM_Select_Test_A is

begin

process(MOD_CLK, MOD_RST)
begin
    SIM_RST     <= MOD_RST;
    SIM_CLK <= MOD_CLK;
end process;

-- OPTION ONE1
--  process(MOD_CLK, MOD_DATA, SIM_DATA)
--  begin
--      IF MOD_DATA = '0' THEN
--          SIM_DATA <= '0';
--      ELSE
--          IF SIM_DATA = '0' THEN
--              MOD_DATA <= '0';
--          ELSE
--              MOD_DATA <= MOD_DATA;
--              SIM_DATA <= SIM_DATA;
--          END IF;
--      END IF;
--  end process;

-- OPTION 2
--  process(MOD_CLK, MOD_DATA, SIM_DATA)
--  begin
--      IF MOD_DATA = '0' THEN
--          SIM_DATA <= '0';
--      ELSE
--          IF SIM_DATA = '0' THEN
--              MOD_DATA <= '0';
--          ELSE
--              MOD_DATA <= 'Z';
--              SIM_DATA <= 'Z';
--          END IF;
--      END IF;
--  end process;

end Behavioral;

Can someone please confirm that if I am driving SIM_DATA low that I will not fall into the second ELSE and therefore drive MOD_DATA low (ie get into some circular logic)

Any comments if I should pursue this avenue or do I absolutely need to know the protocol. If so I guess I'll start researching that :(

Thanks in advance, Kurt

EDIT: Adding my Entity Declaration:

entity SIM_Select_Test_A is
Port ( SIM_VCC : OUT STD_LOGIC;
       SIM_DATA : inout  STD_LOGIC;
       SIM_RST : out  STD_LOGIC;
       SIM_CLK : out  STD_LOGIC;
          MOD_VCC : in STD_LOGIC;
       MOD_DATA : inout  STD_LOGIC;
       MOD_RST : in  STD_LOGIC;
       MOD_CLK : in  STD_LOGIC);
    attribute bufg : string;
    attribute bufg of MOD_CLK : signal is "CLK";
    attribute bufg of MOD_DATA : signal is "OE";
end SIM_Select_Test_A;

EDIT 2: Wow, thanks for that detailed reply. Yes, I can see what you're saying. I guess what I was hoping is that I could add some smarts to the code to the effect of being aware when the CPLD was controlling a wire and prioritizing wire1. So your comments made me formalize my thoughts and here's what I got. Apologies for the pseudocode but I hope it makes things clearer and it always works so well when I compile it in my head :)

IF (wire1 = '0' AND flag = '0') THEN
    wire2 <= '0' <--here wire1 gets priority and wire2 is controlled based on wire1.  My hope is that when it is at this point in the code then it does NOT fall into the else statement.
ELSE  <-- IF (wire1 is NOT low OR there is a flag) THEN check if wire2 is low (which in my head seems slightly different than elsif
    IF (wire2 = '0') THEN
       wire1 <= '0';
       flag  <= '1'; <-- I think this is where there might be a problem.  I am trying to use the flag to tell the outer IF that the CPLD holding wire1 low and to ignore it
    ELSE <--neither are being held low externally
       wire1 <= 'HIZ';
       wire2 <= 'HIZ';
       flag  <= '0';
    END IF
END IF;

I will see if I can simulate that today but appreciate any comments you might have.


Solution

  • This answer applies, if one can make at least some assumptions on the data transfer of the intercepted bi-directional bus. If one cannot make any assumptions, then my other answer is still true.

    The interception would look like this: IC1 <--wire1--> FPGA <--wire2--> IC2.

    A solution is possible, if one can make at least these assumptions:

    • The bus is idle between data transfer IC1-->IC2 and IC2-->IC1.

    • The FPGA can be driven by a clock (from an external oscillator) which is at least 10 times faster than the signaling rate of the bus.

    The solution requires a finite state machine, where the states keep track which side (IC1 oder IC2) actually pulled-down the line. If one IC pulls down his wire, the FPGA pulls down the other one. If the IC releases the wire and the wire is pulled-up by the resistor, than the FPGA releases the other wire too. But now, we have to wait until the other wire is pulled-up by its own resistor before checking that wire for data transmission in the other direction.

    Here is the code:

    library ieee;
    use ieee.std_logic_1164.all;
    
    entity intercept_open_drain_bus2 is
      port (
        clock : in std_logic;
        wire1 : inout std_logic;
        wire2 : inout std_logic);
    end entity intercept_open_drain_bus2;
    
    architecture rtl of intercept_open_drain_bus2 is
      type state_t is (IDLE, PULLDOWN1, WAIT1, PULLDOWN2, WAIT2);
      signal state : state_t := IDLE;
    begin
    
      -- Moore outputs from finite state machine
      wire1 <= '0' when state = PULLDOWN1 else 'Z';
      wire2 <= '0' when state = PULLDOWN2 else 'Z';
    
      -- finite state machine
      process(clock)
      begin
        if rising_edge(clock) then
          case state is
            when IDLE =>
              if wire2 = '0' then
                state <= PULLDOWN1;
              elsif wire1 = '0' then
                state <= PULLDOWN2;
              end if;
    
            when PULLDOWN1 =>
              if wire2 /= '0' then -- 'H' or '1'
                state <= WAIT1;
              end if;
    
            when WAIT1 => -- wait until wire1 is pulled-up by the resistor
              if wire1 /= '0' then
                state <= IDLE;
              end if;
    
            when PULLDOWN2 =>
              if wire1 /= '0' then -- 'H' or '1'
                state <= WAIT2;
              end if;
    
            when WAIT2 => -- wait until wire2 is pulled-up by the resistor
              if wire2 /= '0' then
                state <= IDLE;
              end if;
          end case;
        end if;
      end process;
    end architecture rtl;
    

    And this is the testbench:

    library ieee;
    use ieee.std_logic_1164.all;
    
    entity intercept_open_drain_bus2_tb is
    end entity intercept_open_drain_bus2_tb;
    
    architecture sim of intercept_open_drain_bus2_tb is
      signal clock   : std_logic := '1';
      signal wire1   : std_logic;
      signal wire2   : std_logic;
      signal STOPPED : boolean   := false;
    
    begin
      DUT: entity work.intercept_open_drain_bus2
        port map (
          clock => clock,
          wire1 => wire1,
          wire2 => wire2);
    
      -- 100 MHz FPGA clock from external oscillator
      clock <= not clock after 5 ns when not STOPPED;
    
      -- external PULLUP resistor
      wire1 <= 'H';
      wire2 <= 'H';
    
      WaveGen_Proc: process
      begin
        -- both far-end ICs have their outputs disabled
        wire1 <= 'Z';
        wire2 <= 'Z';
        wait until rising_edge(clock);
        assert (wire1 = 'H') and (wire2 = 'H') report "initial pullup failed." severity error;
    
        -- IC 1 pulls down wire 1 for at least 10 FPGA clock cycles
        wire1 <= '0';
        wait until rising_edge(clock);
        -- wire2 is changing here in the real world
        for i in 2 to 10 loop
          wait until rising_edge(clock);
          assert (wire2 = '0') report "passing from wire1 failed." severity error;
        end loop;
    
        -- IC 1 disables its output again for at least 10 FPGA clock cycles
        wire1 <= 'Z';
        wait until rising_edge(clock);
        -- wire2 is changing here in the real world
        for i in 2 to 10 loop
          wait until rising_edge(clock);
          assert (wire1 = 'H') and (wire2 = 'H') report "pullup after transfer failed." severity error;
        end loop;
    
        -- IC 2 pulls down wire 1 for at least 10 FPGA clock cycles
        wire2 <= '0';
        wait until rising_edge(clock);
        -- wire1 is changing here in the real world
        for i in 2 to 10 loop
          wait until rising_edge(clock);
          assert (wire1 = '0') report "passing from wire2 failed." severity error;
        end loop;
    
        -- IC 2 disables its output again for at least 10 FPGA clock cycles
        wire2 <= 'Z';
        wait until rising_edge(clock);
        -- wire1 is changing here in the real world
        for i in 2 to 10 loop
          wait until rising_edge(clock);
          assert (wire2 = 'H') and (wire1 = 'H') report "pullup after transfer failed." severity error;
        end loop;
    
        STOPPED <= true;
        wait;
      end process WaveGen_Proc;
    end architecture sim;
    

    Now, the simulator output looks good:

    waveform from simulating the testbench