Friday, March 4, 2011

Building an I2C/TWI slave...

An I2C/TWI slave really just acts as a pump for data that connects the bus to whatever is connecting to the slave.  Here we will use byte wide transfers each with an enable/ack control pair to communicate to the slave.  The only additional logic has to do with the type of behavior (read/write) and a method for the slave to wait for a response from the master.
While there are quite a few lines connecting to this module, it's actually quite easy to communicate with... The clock and reset lines are typical, SDA and SCL connect to the bus.  The address can be set  with the set_address/send_byte_ack pair over data_in to give the slave it's identity and when it is addressed over the bus either req_read or req_write will be high.  If req_read is high then data can be pumped onto the bus with the send_byte/send_byte_ack pair over data_in (the master response will appear on got_ack/got_nack).  If req_write is high then data can be pumped off the bus with the data_out_enable/data_out_ack pair.


   if (byte_restart) begin
            byte_restart <= 1'b0; // clear flag - ok to read bytes   
         end
         // always clean up the data_out_ack flag when data_out_enable is cleared.
         if (!data_out_enable && data_out_ack) begin
            data_out_ack <= 1'b0;          
         end
         case(rec_state)
           (`TF_TWI_SLAVE_REC_MODE_IDLE): begin
              xmt_dataack   <= 1'b0; // clear flag always here           
              if (!req_write) begin
                 // Don't interfere when the focus is not releveant
                 xmt_dataack  <= 1'b0; // clear flag
                 xmt_datanack <= 1'b0; // clear flag
              end
              else if (req_write && (data_out_enable == data_out_ack)) begin
                 // attention is on us but we are not ready to read
                 byte_restart  <= data_valid; // if needed reset byte receiver - cleared above
              end
              else if (req_write && data_out_enable && !data_out_ack) begin
                 // we are not full and we want to read
                 byte_restart  <= 1'b1; // reset byte receiver - cleared above
                 rec_state     <= `TF_TWI_SLAVE_REC_MODE_READ;          
              end
           end
           (`TF_TWI_SLAVE_REC_MODE_READ): begin
              if (!req_write) begin
                 // if we lose the request line then bail
                 xmt_dataack   <= 1'b0; // clear flag 
                 xmt_datanack  <= 1'b0; // clear flag 
                 rec_state     <= `TF_TWI_SLAVE_REC_MODE_IDLE;
              end
              if (req_write && !data_valid) begin
                 // ready to receive
                 xmt_dataack   <= 1'b0; // clear flag
                 xmt_datanack  <= 1'b0; // clear flag
              end
              else if (req_write && data_valid && !xmt_dataack && !xmt_ack) begin
                 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           
                 rec_state     <= `TF_TWI_SLAVE_REC_MODE_ACK;           
              end              
           end
           (`TF_TWI_SLAVE_REC_MODE_ACK): begin
              // whether we get an flag that the ack was sent or we lose the req_write, clean up
              if (!req_write) begin         
                 xmt_dataack   <= 1'b0; // clear flag
                 xmt_datanack  <= 1'b0; // clear flag     
                 data_out_ack  <= 1'b0; // clear flag
                 rec_state     <= `TF_TWI_SLAVE_REC_MODE_IDLE;          
              end
              else if (req_write && data_out_enable && (xmt_dataack | xmt_datanack) && xmt_ack) begin
                 // now the ack was sent, signal that we have received a byte back outside the module
                 xmt_dataack   <= 1'b0; //clear flag           
                 xmt_datanack  <= 1'b0; // clear flag               
                 data_out_ack  <= 1'b1; // signal we sent the data out of the module
                 byte_restart  <= 1'b1; // reset byte detector since we sent an ack
                 rec_state     <= `TF_TWI_SLAVE_REC_MODE_IDLE;           
              end            
           end
         endcase
      end // if (!reset)
   end
     
   // Here we pump data from outside the module over the bus...
   always @(posedge clock) begin
      if (reset) begin
         send_ack   <= 1'b0;
         xmt_byte   <= 1'b0;            
         got_ack    <= 1'b0;
         got_nack   <= 1'b0;
         send_state <= `TF_COMPONENT_TWI_SLAVE_SEND_IDLE;       
      end
      else if (!reset) begin
         // 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
         case (send_state)
           (`TF_COMPONENT_TWI_SLAVE_SEND_IDLE): begin   
              if (!send_ack && set_address && !send_byte) begin
                 address    <= data_in[6:0];    
                 send_ack   <= 1'b1; // cleared above
              end
              if (!req_read) begin
                 // Don't interfere when the focus is not releveant
                 xmt_byte  <= 1'b0; // make ceratain this is clear 
              end
              else if (req_read && (send_byte == send_ack)) begin
                 // attention is on us, but we have nothing to send now
                 xmt_byte  <= 1'b0; // make certain this is clear            
              end
              else if (req_read && send_byte && !send_ack && !xmt_byte && !xmt_fin) begin
                 // we want to send a byte out
                 data_byte  <= data_in;
                 got_ack    <= 1'b0; // make certain this is clear here - master response
                 got_nack   <= 1'b0; // make certain this is clear here - master respnose
                 xmt_byte   <= 1'b1; // flag to send byte on bus             
                 send_state <= `TF_COMPONENT_TWI_SLAVE_SEND_BYTE;
              end 
           end
           (`TF_COMPONENT_TWI_SLAVE_SEND_BYTE): begin
              if (!req_read) begin
                 // if we lose the request then bail
                 send_state <= `TF_COMPONENT_TWI_SLAVE_SEND_IDLE;
              end
              else if (req_read && xmt_fin) begin
                 // once the data has been sent on the bus, wait for an ack/nack
                 xmt_byte   <= 1'b0;
                 send_state <= `TF_COMPONENT_TWI_SLAVE_SEND_WAIT;
              end             
           end
           (`TF_COMPONENT_TWI_SLAVE_SEND_WAIT): begin
              // we need to check for ack or nack
              got_ack    <= detect_data0;             
              got_nack   <= detect_data1;
              if ((got_ack || got_nack) && req_read) begin 
                 // either way, once we get a response from the master return to idle              
                 send_ack   <= 1'b1; // set flag       
                 send_state <= `TF_COMPONENT_TWI_SLAVE_SEND_IDLE;
              end            
              else if (!req_read) begin                       
                 // if we lose the request then bail
                 send_state <= `TF_COMPONENT_TWI_SLAVE_SEND_IDLE;
              end
           end
         endcase // case (send_state)   
Since there are two distinct behaviors (sending/recieving) I could have written this as two separate modules but I put them together because it makes sense to me to do it that way.  Internally I could have designed it with 1 functional part (between begin/end) rather than two (1 for each major behavior), but it make more sense to me to build these pieces separately for understandability - care must be taken when you do this to insure that they don't interfere with each other for external signals (that's easy here with req_write/req_read defining exclusive functions).  This could have been made smaller by using elements across functions, but I would rather have a design that's easier to understand and update/maintain in the future.  The whole verilog file is here.  Next we will build a master (which is only a little more complicated).