Wednesday, March 2, 2011

Building an I2C/TWI address interlock...

Now that we built a module to receive bytes over the TWI/I2C bus let's begin to do something with it... One the the things a slave device has to do is watch the bus and look for a request from the master.  Since many devices can be connected to the bus at the same time, slaves know when the master wants to communicate with them because each device has a unique address.  These addresses are 7 bits long (the 8th bit of the byte signals whether the master wants to write to the slave (0) or read from the slave (1) and are always the first byte sent from the master following a start signal.
For our module we are going to use as inputs the outputs from the byte receiver, the system clock, a reset line to put the module into a known state and the 7 bit address we want to watch for as well as the start and stop signals from the bit detector.  We will set the outputs req_read or req_write high depending on the 8th bit (r/w bit) if the first seven bits of the first byte received after a start matches the address bits.  If we subsequently detect a stop signal then these will be set low (0) because we should no longer consider ourselves to be logically addressed.

Since a slave needs to ack back to the master when it detects it's own address (so the master knows the slave exists and can communicate) we are adding the xmt_addressed_ack output/xmt_ack pair to use the bit transmitter so send this signal back to the master.  The clear output will be used to reset the byte detector so that data can be received again (remember it needs to be reset after every byte received).

   if (detect_start || detect_stop) begin
            flag              <= detect_start;          
            req_write         <= 1'b0;
            req_read          <= 1'b0;  
            mode              <= 1'b0;   
            xmt_addressed_ack <= 1'b0;               
         end            
        if (data_valid && flag && !xmt_ack) begin
            // only check the first byte after a start
            flag <= 1'b0; // clear after first data_valid flag
            if (data[7:1] == address) begin
               xmt_addressed_ack <= 1'b1;
               mode              <= data[0];
            end
         end 
         if (xmt_addressed_ack && xmt_ack) begin
            req_write         <= !mode;
            req_read          <= mode;
            xmt_addressed_ack <= 1'b0;  
            clear             <= 1'b1; // clear byte rec.                              
         end   
         if (clear) begin
            clear <= 1'b0; // only high for 1 cycle
         end       
While it seems like a rather complex set of behavior it's quite easy to describe in verilog.  The overview of the design here looks a bit more complex that one might expect.  This might be a good time to point out that the number of elements that exist between the left and right sides of a design are something to consider.  Tiny changes in the HDL (verilog or VHDL or others...) can translate into more or fewer elements that the signals have to pass through and this can dramatically alter the amount of time it takes for the input signals to result in output signals.  It's a good idea to pay attention to the way different designs translate so that if you need to make a circuit faster at some time in the future you have an idea of where in the design a change could make a significant difference (this also applies to making a design more space efficient but slower if timing isn't a problem).  Here I don't care because the design is much faster than it needs to be for this purpose and I would rather have the verilog easier to understand.  The whole verilog for this module is here.  Next we will build a byte transmitter and then we can move on to the master and slave designs.