Bidirectional Bus Modelling

M

M. Norton

This one has me stumped on how to implement the proper behavior, so
I'm going to check with the conventional wisdom of USENET ;-).

Here is the situation. We have an FPGA talking to a DAC on another
board via I2C. For purposes most likely involving voltage level and
protection, the data line is buffered in the following fashion.

The DAC only has one data line, SDA.
The FPGA has two data lines, SDO, SDI.

SDI is always a buffered version of SDA. That part is generally easy.

SDO is wired to the negative enable of a tri-stateable buffer. The
input to the buffer is ground. There is a moderately low strength
pull-up on the output (1kohm). I have zero idea of the capacitance on
this line. They've done this trick before with no issues and the data
rate is fairly low and they seem to be happy with the results. I'm
just verifying the communications behavior with the DAC as best I
can.

Anyhow, so when the FPGA is controlling the bus, it modulates SDO.
When SDO = 0, the SDA bus is strongly pulled to ground. When SDO = 1,
the bus is weakly pulled to 1. When the driver on the DAC side is
active, it can pull the bus around, and otherwise tristated.

To start, I'm not going to try to model rise-fall times. I can add
that in later if it needs it (and honestly I don't know that anyone
ever measured the capacitance of the bidirectional line)

So far I have (with declarations sketched in)

entity tb
port (...
sdo : in std_logic;
sdi : out std_logic;
... );
end tb;

architecture foobar of tb is

signal sda : std_logic;

component dac_model
port (...
sda : inout std_logic;
... );
end component;

begin

DAC_BUFFER_MODEL : process (sdo, sda)
begin
if (sdo = '0') then
sda <= '0';
else
sda <= 'H';
end if;
--
-- The pin back to the FPGA should always be whatever is on sda
--
sdi <= sda;
end process;

my_dac : dac_model
port map (
sda => sda,
...);

So... is this going to do what I want it to do? Looks like I assign
sda a weak high if sdo is not low. I can make sure that the dac_model
treats sda properly (either 1, 0, or Z). I just hope that H will
resolve to 1 when it matters. I've spent most of my time in RTL and
have never, ever used the other values of std_logic since they don't
exist inside an FPGA.

Since there's no proper tristate command, I'm not able to guess when
the FPGA wants to tristate the bus and when it just wants to signal a
'1'. However I know the interior of the FPGA is also not written to
worry about 'H's versus '1's so at some point it has to be resolved
into very discrete levels.

Writing this, I just had the thought that I need to do this in my
process. So instead of the sdi <= sda assignment, perhaps something
here that resolves everything into real values would be good. However
I'm not sure how to go about this other than a brute force case
statement like:

case sda is
when '1' => sdi <= '1';
when '0' => sdi <= '0';
when 'H' => sdi <= '1';
when ...
Etc. You get the picture. Surely there's a better way to do this.

Any ideas out there? Anyone seen this setup before? I'd draw a
picture, but Google Groups doesn't do fixed space text input so I only
have the faintest idea of how it'd look. Hopefully I described it
well enough.

Best regards,
Mark Norton
 
K

kennheinrich

Writing this, I just had the thought that I need to do this in my
process.  So instead of the sdi <= sda assignment, perhaps something
here that resolves everything into real values would be good.  However
I'm not sure how to go about this other than a brute force case
statement like:

    case sda is
        when '1' => sdi <= '1';
        when '0' => sdi <= '0';
        when 'H' => sdi <= '1';
        when ...
Etc. You get the picture.  Surely there's a better way to do this.

There's a function in the std_logic_1164 package called To_X01 that
maps the nine std_logic values into (wait for it...) 'X', '0', and
'1'. You can either use it when you assign a new "internal" signal
from an external "dirty" signal. (By "dirty" I mean containing those
values like 'H' that people don't usually test for). Alternately, you
can use the function right in the port mapping depending on port modes
and so on.

- Kenn
 
M

M. Norton

There's a function in the std_logic_1164 package called To_X01 that
maps the nine std_logic values into (wait for it...) 'X', '0', and
'1'.  You can either use it when you assign a new "internal" signal
from an external "dirty" signal. (By "dirty" I mean containing those
values like 'H' that people don't usually test for). Alternately, you
can use the function right in the port mapping depending on port modes
and so on.

Thank you. In light of this, I think the following will work.
dac_sda is driven by the following process and a component port map
(declared inout, but for the instantiating architecture, I believe
it's considered a driver.)

DAC_BIDIR_MODEL : process (dac_sda, I_DAC_SDA)
begin
--
-- The bidirectional port needs to be set and then resolved.
--
if (I_DAC_SDA = '0') then
dac_sda <= '0';
else
dac_sda <= 'H'; -- weak high pullup
end if;
--
-- The pin back to the FPGA should always show the value of
-- the bidirectional pin. Using a resolution function to
-- make sure it's a true logic value.
--
O_DAC_SDA <= to_x01(dac_sda);
end process;

Now, this sort of extends the issue into the DAC model itself and I'm
already running into problems with not knowing how to declare the
behavior of the sda line.

For example, this DAC runs asynchronously on the SCLK and SDA lines.
I've decided that there are 4 main states to the DAC: IDLE, RX_CMD,
TX_DATA, and RX_DATA. For the RX* and TX* states, these are comprised
of the following procedures: rx_byte, tx_byte, rx_ack, tx_ack,
wait_for_stop.

So, I thought it'd be nice to not have any sort of supervisor clock
and perhaps I could make this run asynchronously, but I ran into
trouble almost immediately in the IDLE state. For example, in the
IDLE state, I want to assign sda <= 'Z', making sure the DAC is tri-
stated. However at the same time I'm evaluating the sda signal
because the start of a sequence begins on the falling edge of sda
while sclk is high. So, I had something like this:

case dac_state is
when IDLE =>
sda <= 'Z';
if (falling_edge(sda) and sclk='1') then
dac_state <= RX_CMD;
end if;

And this seems bizarre to me. SDA is declared as an inout port in the
entity. So it has this ambiguous purpose as driver and receiver. So,
I'm assigning it here, and checking for a state that is not what I
assigned it. Will this actually work?

I might have to start a separate topic on this state machine because
the way I thought I'd do it feels like the complete antithesis of RTL
and as such feels really foreign. At the same time, it feels like the
*right* way to do it.

Increasingly puzzled,
Mark Norton
 
K

KJ

     case dac_state is
          when IDLE =>
               sda <= 'Z';
               if (falling_edge(sda) and sclk='1') then
                    dac_state <= RX_CMD;
               end if;

And this seems bizarre to me.  SDA is declared as an inout port in the
entity.  So it has this ambiguous purpose as driver and receiver.  So,
I'm assigning it here, and checking for a state that is not what I
assigned it.  

But you also know...
1. That there are mutliple drivers on 'sda'. The expectation is that
only one driver will be actively driving the signal at any given time,
the rest are expected to tri-state their drivers.
2. Any weak or strong driver will overcome a tri-stated driver in real
life (i.e. actual parts). Therefore, in real life, a falling edge can
occur on 'sda' when you're in the idle state setting 'sda' to a high
impedance state when it gets driven by whatever else is driving 'sda'.

What you seem to be grappling with is that the std_logic_1164 types do
correctly this real life situation just as you would expect from real
life hardware.
1. You can have mutliple drivers on a std_logic type signal.
2. Those multiple drivers can all be attempting to drive the signal to
whatever state they would like it to be.
3. The state that the signal gets put into is simply computed from the
values that all of the drivers are driving it to. So at best, only
one driver will ever get the signal to the state that it is driving it
to, the others will not.
Will this actually work?

Yes.

Kevin Jennings
 
M

M. Norton

What you seem to be grappling with is that the std_logic_1164 types do
correctly this real life situation just as you would expect from real
life hardware.

Yes, thank you. Very late yesterday after pouring repeatedly through
more arcane portions of Ashenden's book (or what had previously seemed
to be more arcane) I came to the zen-like conclusion that I needed to
trust that the signals would work the way they seemed to be designed
to work. If I tried to overwork the solution, I was likely going to
end up breaking something worse.

So, for the odd little tristate buffers, I'm going to leave it at:

--
-- Due to an inter-board interface, the bidirectional
-- data line is buffered into two unidirectional lines.
-- This routine attempts to model the buffer.
--
DAC_BIDIR_MODEL : process (dac_sda, I_DAC_SDA)
begin
--
-- The outbound FPGA pin drives the bidirectional low or
-- tristates.
--
if (I_DAC_SDA = '0') then
dac_sda <= '0'; -- force low
else
dac_sda <= 'H'; -- weak high pullup
end if;
--
-- The pin back to the FPGA should always show the value of
-- the bidirectional pin. Using a resolution function to
-- make sure it's a true logic value.
--
O_DAC_SDA <= to_x01(dac_sda);
end process;

Thank you for the help!

Best regards,
Mark Norton
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,020
Latest member
GenesisGai

Latest Threads

Top