Thursday, March 3, 2011

Transmitting a byte on a TWI/I2C bus...

Designing a module to send a byte over the bus should be almost intuitive now.  We will build the module with the familiar system clock, a reset line to put he module into a known internal state, 8 lines as data_in for the byte we want to send and a signal line (xmt_byte) to trigger the process.  Once the byte has been sent we use the output xmt_fin (I like to use fin instead of ack for this) as an output to indicate completion - this will be dropped low again when xmt_byte goes low and then a new byte can be sent.  To interface with the bit transmitter we use xmt_data0,xmt_data1 outputs and the xmt_ack input to know when the bit was transmitted.

  case (state)
           (`TF_COMPONENT_TWI_BYTE_XMT_IDLE): begin
              //capture the data_in when we get a flag to transmit
              xmt_data0  <= 1'b0;  // make ceratin we are not signalling   
              xmt_data1  <= 1'b0;  // make ceratin we are not signalling  
              if (xmt_byte && !xmt_fin) begin
                 data  <= data_in;  
                 state <= `TF_COMPONENT_TWI_BYTE_XMT_INIT;           
            (`TF_COMPONENT_TWI_BYTE_XMT_SEND): begin
               // walk through the bits and advance on an ack
               if (!xmt_ack && !xmt_data0 && !xmt_data1) begin
                  // signal the next bit
                  xmt_data0 <= !data[position];
                  xmt_data1 <= data[position];                                         
               else if (xmt_ack && (xmt_data0 || xmt_data1)) begin           
                  xmt_data0  <= 1'b0;  // make ceratin we are not signalling   
                  xmt_data1  <= 1'b0;  // make ceratin we are not signalling
                  // bit was transmitted
                  if (position == 3'b000) begin
                     // we have sent all the bits so signal valid
                     state <= `TF_COMPONENT_TWI_BYTE_XMT_FIN;    
                  else begin
                     // decrease the position
                     position <= position - 1;
            (`TF_COMPONENT_TWI_BYTE_XMT_FIN): begin
               xmt_fin <= 1'b1; // signal transmit finished
               state   <= `TF_COMPONENT_TWI_BYTE_XMT_IDLE;         
            (`TF_COMPONENT_TWI_BYTE_XMT_INIT): begin
               // reset the position and prepare to send the first
               position <= 3'b111;              
               state    <= `TF_COMPONENT_TWI_BYTE_XMT_SEND;
         endcase // case (state)

The state machine is straight forward and counts through the 8 bits, toggling the appropriate transmitter signals.  The signal pairs (variants of enable/ack) on either side of the module work independently.   An important thing to note is that the first thing this module does is make a copy of the data to internal registers rather than using the data lines... This takes more resources than is strictly needed here, but there are reasons for doing this - if the data lines from an external source change during the process then unpredictable data can be sent over the bus (this is not good).  In this case the xmt_fin flag is only set high when the sending is complete; but if we wanted to, an "in_process" flag could be set once the data is copied that would allow the external module supplying the data input lines to do something else with those lines without affecting the byte we want to send (this we will do elsewhere - here it's not a problem because of the overall communication logic).  The other reason to buffer the data is that the timing is easier to control where a buffer exists between modules (not a problem here, but when many modules are going to do different things with the same data lines it can become a difficult problem).  The whole verilog file is here.  Now that we have the components needed, next we will build master and slave modules (significantly more complicated than what we have built so far).