Saturday, March 5, 2011

Building an I2C/TWI master...

Building a master is just a bit more complicated than building a slave.  The master has to control the serial clock (SCL) which the slave doesn't have to worry about, but otherwise much of the communication is the same.  Pumping data onto the bus is done over data_in with send_byte/send_byte_ack and receiving is done over data_out with data_out_enable/data_out_ack pairs.  The slave target address can be set over data_in with the set_address/send_ack pair.  To begin a communication with a slave pull either open_read or open_write high - when the slave acks back addressed will go high.  When reading from the slave the response the master will send back to the byte the slave has sent is determined by read_response.

   // we always want to reset the response ack/block when no signals to process
         if (send_ack && !send_byte && !set_address) begin 
            // reset flag  
            send_ack     <= 1'b0; // clear flag
         end
         // resetting the byte receiver is always a single cycle clear flag
         if (byte_restart) begin
            byte_restart <= 1'b0; // clear flag - ok to read bytes   
         end
        
         case (state)
           (`TF_COMPONENT_TWI_MASTER_STATE_IDLE): begin
              scl_enable   <= 1'b0; // turn off scl generator - only turned on by start below
              scl_stretch  <= 1'b0; // do not hold scl generator
              addressed    <= 1'b0; // here we are not addressed
              got_ack      <= 1'b0; // make certain this is clear here - slave resopnse
              got_nack     <= 1'b0; // make certain this is clear here - slave resopnse
              xmt_start    <= 1'b0; // make certain this is clear here - send start
              xmt_stop     <= 1'b0; // make certain this is clear here - send stop
              xmt_dataack  <= 1'b0; // make certain this is clear here - send ack (0)
              xmt_datanack <= 1'b0; // make certain this is clear here - send nack (1)
              data_out_ack <= 1'b0; // make certain this is clear here - rec data is not valid
              xmt_byte     <= 1'b0; // make certain this is clear here - send byte (addr or data)
              if (set_address && !send_ack) begin
                 // set the address and flag back
                 address  <= data_in[6:0];
                 send_ack <= 1'b1; // cleared above when set_address is dropped
              end           
              else if (!send_ack && (open_write || open_read)) begin
                 // try to open the bus   
                 state <= `TF_COMPONENT_TWI_MASTER_STATE_START;         
              end
           end // case: (`TF_COMPONENT_TWI_MASTER_STATE_IDLE)     
           (`TF_COMPONENT_TWI_MASTER_STATE_START): begin 
              // open the bus
              scl_enable  <= 1'b1; // turn on scl
              scl_stretch <= 1'b0; // do not hold scl generator
              if (!xmt_ack && !xmt_start) begin  
                 xmt_start <= 1'b1; // send the start signal               
              end
              else if (xmt_ack && xmt_start) begin
                 xmt_start <= 1'b0; // clear flag once start has been sent
                 if (open_read || open_write) begin
                    state     <= `TF_COMPONENT_TWI_MASTER_STATE_ADDRESS;
                 end        
              end // if (xmt_ack && xmt_start)       
           end // case: (`TF_COMPONENT_TWI_MASTER_STATE_START)    
           (`TF_COMPONENT_TWI_MASTER_STATE_ADDRESS): begin
              // try to open a connection to the slave
              if ((open_read || open_write) && !xmt_byte && !xmt_fin) begin
                 // send the address and set the read/write bit
                 scl_stretch <= 1'b0; // start scl generator
                 data_byte   <= {address, open_read};
                 xmt_byte    <= 1'b1;             
              end
              else if ((open_read || open_write) && xmt_byte && xmt_fin) begin
                 // after the byte was sent, clear flag
                 xmt_byte    <= 1'b0;           
                 scl_stretch <= 1'b1; // stretch
                 got_ack     <= 1'b0;
                 got_nack    <= 1'b0;
                 state       <= `TF_COMPONENT_TWI_MASTER_STATE_AORN;
              end            
              else if (!open_read && !open_write) begin
                 // bail if we lost the line that sent us here
                 state <= `TF_COMPONENT_TWI_MASTER_STATE_CLOSE;
              end      
           end
           (`TF_COMPONENT_TWI_MASTER_STATE_AORN): begin            
              // wait until we see either an ack or a nack from the slave
                 if (open_read && got_ack) begin
                    state        <= `TF_COMPONENT_TWI_MASTER_STATE_READ;   
                    scl_stretch  <= 1'b1; // stretch
                    addressed    <= 1'b1; // signal we have addressed (busopen)
                    byte_restart <= 1'b1; // reset byte detector
                    got_ack      <= 1'b0; // clear flag  
                    got_nack     <= 1'b0; // clear flag
                 end            
                 else if (open_write && got_ack) begin
                    state        <= `TF_COMPONENT_TWI_MASTER_STATE_WRITE;   
                    scl_stretch  <= 1'b1; // stretch
                    addressed    <= 1'b1; // signal we have addressed (busopen)
                    byte_restart <= 1'b1; // reset byte detector
                    got_ack      <= 1'b0; // clear flag
                    got_nack     <= 1'b0; // clear flag
                 end
                 else if (got_nack) begin
                    state        <= `TF_COMPONENT_TWI_MASTER_STATE_CLOSE;
                    got_ack      <= 1'b0; // clear flag
                    got_nack     <= 1'b0; // clear flag
                 end
              else if (!open_read && !open_write) begin
                 // bail if we lost the line that sent us here
                 state       <= `TF_COMPONENT_TWI_MASTER_STATE_CLOSE;
                 scl_stretch <= 1'b0; // run the clock
                 got_ack     <= 1'b0;             
                 got_nack    <= 1'b0;
              end    
              else if ((open_read || open_write) && !got_ack && !got_nack) begin
                 scl_stretch <= 1'b0; // run the clock
                 got_ack     <= detect_data0;             
                 got_nack    <= detect_data1;
              end
           end // case: (`TF_COMPONENT_TWI_MASTER_STATE_AORN)     
           (`TF_COMPONENT_TWI_MASTER_STATE_WRITE): begin
              // Here we are sending data to the slave. 
              // xmt_byte/xmt_fin pair for the bus
              // send_byte/send_ack pair for outside the module
              if (!open_write && !open_read) begin
                 // If we lose the signal, close the bus
                 send_ack    <= 1'b0; // clear flag  
                 xmt_byte    <= 1'b0; // clear flag 
                 scl_stretch <= 1'b0; // start scl generator
                 state       <= `TF_COMPONENT_TWI_MASTER_STATE_CLOSE;
              end
              else if (!open_write && open_read) begin
                 // if we flip modes restart without closing
                 send_ack    <= 1'b0; // clear flag  
                 xmt_byte    <= 1'b0; // clear flag 
                 scl_stretch <= 1'b0; // start scl generator
                 state       <= `TF_COMPONENT_TWI_MASTER_STATE_START;
              end  
              else if (open_write && (send_byte == send_ack)) begin
                 // the bus is open for writing but we have nothing to send yet
                 // either no intent or already sent
                 scl_stretch <= 1'b1; // hold scl generator
              end
              else if (open_write && send_byte && !send_ack && !xmt_byte && !xmt_fin) begin
                 // intend to send, transmitter available
                 data_byte   <= data_in;
                 xmt_byte    <= 1'b1; // trigger send of byte data
                 scl_stretch <= 1'b0; // start scl generator
              end
              else if (open_write && send_byte && !send_ack && xmt_byte && xmt_fin) begin
                 // sent byte on bus so inform outside we sent it
                 send_ack    <= 1'b1; // signal we processed - cleared above
                 xmt_byte    <= 1'b0; // clear flag        
                 scl_stretch <= 1'b1; // stretch a bit   
                 state       <= `TF_COMPONENT_TWI_MASTER_STATE_AORN;
              end                          
           end // case: (`TF_COMPONENT_TWI_MASTER_STATE_WRITE)    
           (`TF_COMPONENT_TWI_MASTER_STATE_READ): begin
              // Here we are pulling data from the slave.
              // data_out_enable/data_out_ack pair for outside the module
              // xmt_dataack/xmt_ack pair for sending ack on the bus
              // data_valid/byte_restart for byte receiver
              if (!open_write && !open_read) begin
                 // If we lose the signal, close the bus
                 xmt_dataack  <= 1'b0; // clear   
                 data_out_ack <= 1'b0; // clear                          
                 state        <= `TF_COMPONENT_TWI_MASTER_STATE_CLOSE;
              end
              else if (open_write && !open_read) begin
                 // if we flip modes restart without closing
                 xmt_dataack  <= 1'b0; // clear   
                 data_out_ack <= 1'b0; // clear         
                 state        <= `TF_COMPONENT_TWI_MASTER_STATE_START;
              end
              else if (open_read && (data_out_enable == data_out_ack)) begin
                 // we are not ready to receive a byte so hold the clock
                 scl_stretch  <= 1'b1; // start scl generator
              end
              else if (open_read && !data_out_enable && data_out_ack) begin
                 // outside signals data is handled by dropping data_out_enable
                 data_out_ack <= 1'b0; // clear flag            
                 byte_restart <= data_valid; // if needed reset byte receiver - cleared above
              end            
              else if (open_read && data_out_enable && !data_out_ack && !data_valid) begin
                 // we are ready to receive a byte so start the clock
                 scl_stretch  <= 1'b0; // start scl generator 
              end
              else if (open_read && data_out_enable && !data_out_ack && data_valid && !xmt_dataack && !xmt_ack) begin
                 // we have a byte ready and enabled to care about it so keep a copy of it and ack back
                 data_out     <= data; // keep a copy locally          
                 xmt_dataack  <= !read_response; // send an ack on the bus if needed  
                 xmt_datanack <= read_response;  // send a nack on the bus if needed   
              end
              else if (open_read && data_out_enable && !data_out_ack && data_valid && (xmt_dataack | xmt_datanack) && xmt_ack) begin
                 // we sent the ack to the bus so signal out we have data
                 xmt_dataack  <= 1'b0; // clear the signal to send an ack on the bus
                 xmt_datanack <= 1'b0; // clear the signal to send a nack on the bus  
                 data_out_ack <= 1'b1; // signal we have new data to the outside   
              end
           end
           (`TF_COMPONENT_TWI_MASTER_STATE_CLOSE): begin
              xmt_dataack  <= 1'b0; 
              xmt_start    <= 1'b0; 
              data_out_ack <= 1'b0;          
              scl_stretch  <= 1'b0; // start scl generator
              if (!xmt_ack && !xmt_stop) begin
                 xmt_stop  <= 1'b1;                 
              end
              else if (xmt_ack && xmt_stop) begin
                 xmt_stop   <= 1'b0;
                 addressed  <= 1'b0;            
                 state      <= `TF_COMPONENT_TWI_MASTER_STATE_IDLE;             
              end
           end 
         endcase       
The state machine here is done all in one part rather than two like we did with the slave (it's easier to handle the additional logical flow considerations here doing it this way).  The resulting design is vertically much more complicated because of the additional states and the number of signals that can cause the states to change; however the horizontal space is about the same (so the speed is comparable even though the problem is more complex).  The entire verilog file is here.