Tuesday, March 1, 2011

Extracting a byte off the twi/i2c bus...

Well the base module we made is capable of transmitting and receiving individual signals (start, stop, data0 and data1) and generating a clock of arbitrary frequency over an open collector bus (twi/i2c logic).  One could use software on a processor to handle the addressing and master or slave behavior logic, but for me it seems easier if the hardware could also handle the jobs of forming bytes from the signals we see on the bus, determining when we are addressed, sending bytes out on the bus, ack transfers and so on... (it would be nice to have a modules that will transmit/receive data as bytes and act as either a master or a slave).  The main reason this is useful to do in hardware it that fewer interrupts are needed on the processor to get work done and the timing can be much more tightly controlled.

The first thing we need to do is design a module to form byte sized packets from the data0 and data1 signals we see on the bus.  Once we have 8 signals (to form a byte) we can signal that the data is valid.

For outputs we have 8 bits of data and a data_valid flag that is high once a byte is received.  As inputs we are using the system clock, and the 4 signals (start, stop, data0 and data1) that come from the bit detector module - the reset line will put the module into a known state on demand (we use this type of element often), but the continuous line is something new...

If we detect a start signal, the 8 next signals will form a byte (that will represent the address and the read/write direction from the master) and if we detect a stop then no more bits will come that should be logically formed into a byte.  We can use these two signals to either form a byte from the first 8 bits after a start signal, or to abort forming a byte if for whatever reason a stop signal is sent.  What happens though if we are addressed and need to receive a byte from the master (or we are the master and need to receive from an addressed slave)?  In these cases we need to form bytes without the leading start signal and so we add the continuous input to enable the byte forming logic.  This is easily done with a state machine...

   case (state)
           (`TF_COMPONENT_TWI_BYTE_REC_IDLE): begin
              // we only care about a start signal here
              data_valid <= 1'b0; // clear flag
              if (detect_start || continuous) begin
                 // if we get a start signal then prepare to store the next bit
                 state    <= `TF_COMPONENT_TWI_BYTE_REC_INIT;           
              end
           end
            (`TF_COMPONENT_TWI_BYTE_REC_ADD): begin
               // we have to care about start, stop, data0 and data1 signals here
             if (detect_data0 || detect_data1) begin
                data[position] <= detect_data1; // covers both triggers
                if (position == 3'b000) begin
                   // we have all the bits so signal valid
                   state <= `TF_COMPONENT_TWI_BYTE_REC_FIN;    
                end
                else begin
                   // decrease the position
                   position <= position - 1;
                end
             end
             else if (detect_start) begin
                // if we get a start signal then reset the state
                state    <= `TF_COMPONENT_TWI_BYTE_REC_INIT;            
             end
             else if (detect_stop) begin
                // if we get a stop signal then reset the state
                state    <= `TF_COMPONENT_TWI_BYTE_REC_IDLE;
             end              
            end
            (`TF_COMPONENT_TWI_BYTE_REC_FIN): begin 
               if (detect_start) begin
                  // if we get a start signal then prepare to store the next bit
                  data_valid <= 1'b0; // signal data is no longer valid
                  state    <= `TF_COMPONENT_TWI_BYTE_REC_INIT;           
               end                                     
               else begin                                
                  data_valid <= 1'b1; // signal data is valid
               end
            end
           (`TF_COMPONENT_TWI_BYTE_REC_INIT): begin
              // reset the position and prepare to grab the next bit
              position <= 3'b111;              
              state    <= `TF_COMPONENT_TWI_BYTE_REC_ADD;              
           end
What should jump out rather quickly is that once a byte has been received and data_valid is high, there is no apparent way to receive another byte... (usually I like to use flag pairs - enable/ack - to reset behaviors) - In this case we are going to use the reset line to put the module back into a known state where a new byte can be received.  While this module isn't all that complicated, sometimes keeping track of how to get back into a known state isn't simple or would significantly increase the complexity of the design - in these cases it's easier to use the reset line rather than enable/ack pairs.  The one time you can't do this is when some information needs to be kept and not reset.

The whole verilog file for this module is here.  Next we will make an address interlock...