Monday, February 28, 2011

Finishing up a TWI/I2C interface...

Well so far we've made a few modules (a clock, a tristate controller, a signal transmitter and a signal receiver) so far and with a few more small ones we will have the core of a hardware twi/i2c controller.

One thing we need to so is refine our handling of the alarms from the tristate module (these go high when the intended signal doesn't match the output signal).  the problem is that in practice it takes a non-zero amount of time for levels to change and for us to detect those changes.

A simple way to handle short term differences that could be acceptable is to only send that alarm if they exist for too long. Here we will do that with shift register.  The full verilog for the design is here.

The other thing we need to consider is synchronizing the SCL clock.  This needs to be done when the bus is stretched or a second master has stepped on (transmitted over) our clock.



This is very easy to do by inducing a stretch signal if SCL is stretched low and also resetting the intent module so that it can detect errors again in the future.
if (scl_alarm && (scl == 1'b0)) begin
            // we wanted to drive high and failed for long enough to trigger an
            // alarm... so something else is holding scl low (stretching or other scl generator)
            // We should therefore try to get in our low phase of the signal
            stretch_low <= 1'b1; // get our generator in the low phase of the cycle.
            clear       <= 1'b1; // reset intent monitor          
         end
         if (clear) begin
            clear <= 1'b0; // reset flag
         end

Which comes down to a very simple implementation.   The whole verilog design for the module is here.
The last thing we need to add is a filter for the SDA and SCL lines.  If have perfect signals and great connectors and shielding then it's not needed; however, in practice there can be short glitches in the lines. 


   scl_p <= scl;
         sda_p <= sda;

         if ((scl_p == scl) && (scl_history > 0)) begin
            scl_history <= scl_history - 1;        
         end
         else if ((scl_p == scl) && (scl_history == 0)) begin
            scl_filtered <= scl;           
         end
         else  begin
            scl_history <= 3'b111;         
         end
        
         if ((sda_p == scl) && (sda_history > 0)) begin
            sda_history <= sda_history - 1;        
         end
         else if ((sda_p == scl) && (sda_history == 0)) begin
            sda_filtered <= sda;           
         end
         else  begin
            sda_history <= 3'b111;         
         end    

Here we could use simple shift registers, but instead we will count the number of clocks between changes in state and only let the output change if the input signal has been stable for a sufficient amount of time.  The reason is that using a shift register will make the output immediately sensitive to a change int he input after it is stable and using a counter is an easy way to make it resistant to intermittent fluctuations in all cases.  The design is more complicated than using shift registers.
The whole verilog file for this module is here.




Now that we have all the pieces we can build a base module that combines them together.  This module will detect and transmit signals on the bus and can generate the SCL clock if needed. 

This is rather easy to do by connection the inputs and output from the modules we have to each other and the remaining ones to inputs and outputs of our new module.


assign arbitration_lost = sda_alarm & scl;
     
   tf_component_tristate_pair component_tristate_pair (
                                                       .sda(sda),
                                                       .sda_xmt(sda_xmt),
                                                       .sda_status(sda_status),
                                                       .sda_enable(sda_enable),
                                                       .scl(scl),
                                                       .scl_xmt(clock_variable),
                                                       .scl_status(scl_status),
                                                       .scl_enable(scl_enable),
                                                       .stretch(stretch)
                                                       );
                                                                                                                
   tf_component_scl_variable component_scl_variable (
                                                     .clock(clock),
                                                     .reset(reset | !scl_enable | stretch_low_sync),
                                                     .clock_out(clock_variable),
                                                     .stretch_high(stretch_high_xmt),
                                                     .stretch_low(stretch | stretch_low_sync),
                                                     .delay_16bits(delay_16bits)
                                                     );
     
   tf_component_twi_intent component_twi_intent (
                                                 .clock(clock),
                                                 .reset(reset),
                                                 .clear(clear),
                                                 .scl_status(scl_status),
                                                 .sda_status(sda_status),
                                                 .scl_alarm(scl_sync),
                                                 .sda_alarm(sda_alarm)
                                                 );
  
   tf_component_twi_synchro twi_synchro (
                                           .clock(clock),
                                           .reset(reset),
                                           .scl_alarm(scl_sync),
                                           .clear(clear),
                                           .stretch_low(stretch_low_sync),
                                           .scl(scl_status)
                                           );

   tf_component_twi_rec twi_rec (
                                       .clock(clock),
                                       .reset(reset),
                                       .scl(scl_filtered),
                                       .sda(sda_filtered),
                                       .rec_start(detect_start),
                                       .rec_stop(detect_stop),
                                       .rec_data0(detect_data0),
                                       .rec_data1(detect_data1)
                                       );

   tf_component_twi_xmt twi_xmt (
                                 .clock(clock),
                                 .reset(reset),
                                 .scl(scl_filtered),
                                 .stretch_high(stretch_high_xmt),
                                 .sda_xmt(sda_xmt),
                                 .sda_enable(sda_enable),
                                 .xmt_start(xmt_start),
                                 .xmt_stop(xmt_stop),
                                 .xmt_data0(xmt_data0),
                                 .xmt_data1(xmt_data1),
                                 .xmt_ack(xmt_ack),
                                 .pulse_delay(delay_16bits)
                                 );
                                                                                       
   tf_component_twi_filter twi_filter (
                                       .clock(clock),
                                       .reset(reset),
                                       .sda(sda),
                                       .scl(scl),
                                       .sda_filtered(sda_filtered),
                                       .scl_filtered(scl_filtered)
                                       );

The only new assignment here is to bind the arbitration_lost signal (high when SDA state doesn't match intent - something is talking/transmitting over our data) output to be only true when SCL is high.  In theory this should not be necessary, but in practice sometimes slave devices will ack back to the master earlier than they should (rather than waiting for the signal hold time as they are supposed to).
The whole verilog file for the module is here.  This could easily be controlled with software, but that's sometimes too slow... next we will add modules to handle the logical device design levels of addressing, sending/receiving bytes and master and slave behaviors.