Wednesday, June 18, 2014

Should I remove inout?

When I started to design PSHDL, I had to have ports with direction in and out obviously. As VHDL and Verilog has inout as well, I thought, well, what's the harm? If you want to implement I2C, you definitely need to have that, right?

Well, it turns out that inout is a trouble maker. It works well on the top module, when it is assigned directly to a pin, but when you write a module that decodes the I2C protocol, this might not necessarily end up being the top module. You would want to wire the I2C sub-module up to the actual top module. But how do you do this?

The honest answer is, in PSHDL you currently can't. You can write:

interface I2C {
    inout bit scl, sda;
}

module top {
    I2C i2c;
    inout bit scl, sda;

    i2c.scl=scl;
    scl=i2c.scl;

    i2c.sda=sda
    sda=i2c.sda
}

This code is bad for multiple reasons. The first one is that it is two lines per port-mapping which is rather annoying. The second, and more important reason is: It doesn't work in VHDL. The generated code looks like this:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity top is
    port (
        scl : inout std_logic;
        sda : inout std_logic
    );
end;
architecture pshdlGenerated of top is
    signal \$map_i2c_scl\ : std_logic;
    signal \$map_i2c_sda\ : std_logic;
begin
    i2c : entity work.I2C
        port map (
            scl => \$map_i2c_scl\,
            sda => \$map_i2c_sda\
        );
    process(\$map_i2c_scl\, \$map_i2c_sda\, scl, sda)
    begin
        \$map_i2c_scl\ <= scl;
        scl <= \$map_i2c_scl\;
        \$map_i2c_sda\ <= sda;
        sda <= \$map_i2c_sda\;
    end process;
end;

Unfortunately this creates a multiple driver issue as \$map_i2c_scl\ is written in the port-map as well as in the the process later on. The proper VHDL solution for that is to map the signal directly:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity top is
    port (
        scl : inout std_logic;
        sda : inout std_logic
    );
end;
architecture pshdlGenerated of top is
begin
    i2c : entity work.I2C
        port map (
            scl => scl,
            sda => sda
        );
end;

One of the new features that I plan for v0.2 of the language are exports, which basically could accomplish exactly that. The idea behind exports is that you can say:

module top {
    I2C i2c;
    export i2c.scl;
    export i2c.sda;

    //or if you want to export a whole interface:
    export i2c;
}

This would then generate the desired VHDL code. While this might look like a valid solution for the problem of inouts, there are even more problems with them. For example, what happens when you read or write an exported signal? I tend to say that this should be allowed, but for inout's that would not be possible, and I hate rules that only work sometimes and not other times.

Every signal that is not an input is by default initialized with 0. This little trick prevents the creation of latches, as every signal always has a value assigned to it. But in the case of inout this feature is rather annoying as you sometimes don't want to drive that signal. The solution for that is the @VHDLLatchable annotation, which prevents the creation of the default 0 initialization. But this again feels more like a hack. Overall it can be observed that people are tempted to create things, that don't work well within FPGAs, such as shared busses, with high-z's. These don't really work within FPGAs as there are no tri-state lines. Those are mapped to two uni-directional signals with multiplexers.

Another problem are combinatorical loops. Those are especially easy to create with inouts.

Overall it appears that people can have many unwanted effects with inouts while gaining very little (they can only be used on top-level modules). Maybe the best idea is to simply remove them, and provide tooling to map a pair of in/out signals to one pin. A little annotation might help to designate pins that belong together, like this:

interface I2C {
    @iopin("scl") in bit scl_i;
    @iopin("scl") out bit scl_o;
    @iopin("sda") in bit sda_i;
    @iopin("sda") out bit sda_o;
}

Or maybe even a generic record (as will be part of v0.2) that consists of an in and out variable could be the solution.

interface ioPin {
    in bit sig_i;
    out bit sig_o;
}

interface I2C {
    record ioPin sda;
    record ioPin scl;
}
Please note that every signal can be assigned highZ(), which will turn into a VHDL 'z'. This can be used to tristate every output if necessary.

What do you think?

2 comments:

  1. What about something like this:
    iopin scl in bit scl_i out bit scl_o; #This seperates the pin into in and out
    Then you can route the scl_i and scl_o signals internally.
    For tristates, I would suggest to add a second bit:
    iopin scl in bit scl_i out bit scl_o dir bit scl_d;
    Now you can control the direction of the iopin with scl_d.
    Can we do initialisations on demand?
    iopin scl in bit scl_i out bit scl_o=0 dir bit scl_d=1;

    Another thing I would like to have is routing operators:
    module top {
    iopin scl;
    module i2c_controller;
    module cpu;

    scl <=> i2c_controller.scl; // This does a straight-through assignment from the scl iopin to the i2c_controller

    i2c_controller.sda <=X=> cpu.gpio // This connects the input from the i2c_controller to the output of the cpu and the other way round

    }

    Keep the syntax easy, concise and clear. I would use Annotations for afterthoughts, not for essential things.

    ReplyDelete
    Replies
    1. Hmm, the idea of the connector operator is quite nice, but I think the seperation of scl is a bit over the top. But I will think this through.
      Thanks for the feedback.

      Delete