Sunday, March 6, 2011

The TWI/I2c master/slave composite...

Now that we have hardware master and slave modules, I want to combine them into a single composite module.  The reason is that from time to time my system needs to be able to communicate with attached slave devices but the rest of the time I would like to be able to communicate with my system over remotely...
Since most of the lines are the same in both the master or slave modules, we only need a few additional lines to make a composite.  The familiar data_in/data_out with send_byte/send_ byte_ack or data_out_enable/data_out_ack pairs are shared but open_read and open_write and read_responseare bought out and well as req_read and req_write even though they only apply to the master or slave modules internally (it's easier to understand with them names separately, but they could be multiplexed if needed).  The slave or master lines determine whether the mast or slave module is active.

  // connect based on whether we run the master or slave control logic
   assign data_out              = (master) ? data_out_master : data_out_slave;   // outside
   assign data_out_ack          = (master) ? data_out_ack_master : data_out_ack_slave;   // outside
   assign data_byte             = (master) ? data_byte_master : data_byte_slave;   // bind to byte_xmt
   assign send_byte_ack         = (master) ? send_byte_master_ack : send_byte_slave_ack; // outside
   assign got_ack               = (master) ? got_ack_master : got_ack_slave; // outside
   assign got_nack              = (master) ? got_nack_master : got_nack_slave; // outside
  
Using continuous assignment based on the level of the master input allows us to multiplex shared lines to multiple modules where only one module is active at once.


   tf_component_twi_master twi_master (
                                       .clock(clock), // outside
                                       .reset(reset | !master | slave), // outside
                                       .data_out(data_out_master), // assign logic
                                       .data_out_enable(data_out_enable), // outside
                                       .data_out_ack(data_out_ack_master), // assign logic
                                       .scl_stretch(stretch_master), // bind to twi_core
                                       .arbitration_lost(arbitration_lost), // bind to twi_core
                                       .softreset(softreset_master),  // bind to byte_rec, byte_xmt, address_interlock, twi_base
                                       .scl_enable(scl_enable),  // bind to twi_core
                                       .xmt_start(xmt_start),  // bind to twi_core
                                       .xmt_stop(xmt_stop),  // bind to twi_core
                                       .open_read(open_read),  // outside
                                       .open_write(open_write),  // outside
                                       .addressed(busopen), // outside
                       .read_response(read_response), // outside
                                       .detect_data0(detect_data0),  // bind to twi_core
                                       .detect_data1(detect_data1),  // bind to twi_core
                                       .byte_restart(byte_restart_master), // bind to byte_rec
                                       .data_in(data_in), // outside
                                       .got_ack(got_ack_master), // assign logic
                                       .got_nack(got_nack_master), // assign logic
                                       .set_address(set_address), // outside
                                       .send_byte(send_byte),  // outside
                                       .send_ack(send_byte_master_ack), // assign logic
                                       .data(data),  // bind to byte_rec
                                       .data_valid(data_valid),  // bind to byte_rec
                                       .data_byte(data_byte_master), // assign logic
                                       .xmt_dataack(sendack_master), // bind to twi_base
                                       .xmt_datanack(sendnack_master), // bind to twi_base
                                       .xmt_ack(xmt_ack),  // bind to twi_base
                                       .xmt_byte(xmt_byte_master), // bind to byte_xmt
                                       .xmt_fin(xmt_fin) // bind to byte_xmt
                                       );
  
   tf_component_twi_slave twi_slave (
                                     .clock(clock), // outside
                                     .reset(reset | !slave | master), // outside
                                     .address(address), // bind to address_interlock
                                     .data_out(data_out_slave), // assign logic
                                     .data_out_enable(data_out_enable), // outside
                                     .data_out_ack(data_out_ack_slave), // assign logic
                                     .data_in(data_in), // outside
                                     .send_byte(send_byte), // outside
                                     .data_byte(data_byte_slave), // bind to byte_xmt
                     .read_response(read_response), // outside
                                     .xmt_dataack(sendack_slave), // bind to twi core
                                     .xmt_datanack(sendnack_slave), // bind to twi_base
                                     .arbitration_lost(arbitration_lost),  // assign logic
                                     .softreset(softreset_slave), // bind to byte_rec, byte_xmt, address_interlock, twi_base
                                     .stretch(stretch_slave), // assign_logic
                                     .got_ack(got_ack_slave), // assign logic
                                     .got_nack(got_nack_slave), // assign logic
                                     .req_read(req_read), // bind to address_interlock
                                     .req_write(req_write), // bind to address_interlock
                                     .detect_data0(detect_data0), // bind to twi_base
                                     .detect_data1(detect_data1), // bind to twi_base
                                     .byte_restart(byte_restart_slave), // bind to byte_rec
                                     .set_address(set_address), // outside
                                     .send_ack(send_byte_slave_ack), // assign logic
                                     .data(data), // bind to byte_rec
                                     .data_valid(data_valid), // bind to byte_rec
                                     .xmt_ack(xmt_ack), // bind to twi_base
                                     .xmt_byte(xmt_byte_slave), // bind to byte_xmt
                                     .xmt_fin(xmt_fin) // bind to byte_xmt
                                     );
  
   tf_component_twi_byte_rec byte_rec (
                                       .clock(clock), // outside
                                       .reset(reset | softreset_master | softreset_slave | byte_restart_master | byte_restart_slave | byte_restart_addressed), // outside
                                       .data(data), // bind to address_interlock
                                       .data_valid(data_valid),  // bind to address_interlock, twi_slave
                                       .detect_start(detect_start), // bind to twi_base
                                       .detect_stop(detect_stop), // bind to twi_base
                                       .detect_data0(detect_data0), // bind to twi_base
                                       .detect_data1(detect_data1), // bind to twi_base
                                       .continuous(req_write | open_read) // allows byte forming without seeing start
                                       );
  
   tf_component_twi_byte_xmt byte_xmt (
                                       .clock(clock), // outside
                                       .reset(reset | softreset_master | softreset_slave), // outside
                                       .data_in(data_byte), // bind to twi_slave
                                       .xmt_byte(xmt_byte_slave | xmt_byte_master), // bind to twi_slave
                                       .xmt_fin(xmt_fin), // bind to twi_slave
                                       .xmt_data0(xmt_data0), // bind to twi_base
                                       .xmt_data1(xmt_data1), // bind to twi_base
                                       .xmt_ack(xmt_ack) // bind to twi_base
                                       );
  
   tf_component_twi_address_interlock address_interlock (
                                                         .clock(clock), // outside
                                                         .reset(reset | softreset_master | softreset_slave | master),  // outside
                                                         .address(address), // bind to twi_slave
                                                         .req_write(req_write), // bind to twi_slave, outside
                                                         .req_read(req_read), // bind to twi_slave, outside
                                                         .data(data), // bind to byte_rec
                                                         .data_valid(data_valid), // bind to byte_rec
                                                         .detect_start(detect_start), // bind to twi_base
                                                         .detect_stop(detect_stop), // bind to twi_base
                                                         .xmt_ack(xmt_ack), // bind to twi_base
                                                         .xmt_addressed_ack(sendack_addressed), // bind to twi_base
                                                         .clear(byte_restart_addressed) // bind to byte_rec
                                                         );
  
   tf_component_twi_base twi_base (
                                   .sda(sda), // outside
                                   .scl(scl), // outside
                                   .clock(clock), // outside
                                   .reset(reset | softreset_master | softreset_slave), // outside
                                   .scl_enable(scl_enable),  // bind to twi_master
                                   .stretch(stretch_master | stretch_slave), // bind to twi_slave
                                   .detect_start(detect_start), // bind to byte_rec, address_interlock
                                   .detect_stop(detect_stop), // bind to byte_rec, address_interlock
                                   .detect_data0(detect_data0), // bind to byte_rec, twi_slave, twi_master
                                   .detect_data1(detect_data1), // bind to byte_rec, twi_slave, twi_master
                                   .xmt_start(xmt_start), // bind to twi_master
                                   .xmt_stop(xmt_stop),   // bind to twi_master
                                   .xmt_data0(xmt_data0 | sendack_master | sendack_slave | sendack_addressed), // bind to xmt_byte, twi_slave, twi_master, address interlock
                                   .xmt_data1(xmt_data1 | sendnack_master | sendnack_slave), // bind to xmt_byte
                                   .xmt_ack(xmt_ack), // bind to xmt_byte
                                   .arbitration_lost(arbitration_lost), // bind to twi_slave, twi_master
                                   .delay_16bits(delay_16bits)
                                   );
  

In other cases we connect lines (like clock, reset, sda, scl, the xmt and rec lines) to many modules continuously.  When multiple modules need to access the same resource (like transmitting an ack or data0 or a nack or data1) we connect them with or gates to the input of the relevant module.
What we end up with is a layout where most of the modules are highly interconnected to each other and few are connected to the outside inputs and outputs.

The small amount of glue logic (in this case or gates) creates minimal increases in the signal time (which here doesn't matter anyway, but in some designs it does). The whole verilog file is here.

In many ways it is more practical to build at least the master and slave components in hardware and then use a processor to handle the upper logic control levels (fewer interrupts need to be handled than accessing the core directly) but it's an implementation specific decision (sometimes there is more processing capacity than hardware capacity available).   It is however entirely possible to implement the upper logic levels in hardware as well (something I do for relatively simple controllers where I don't want a processor at all, or I don't want to interrupt the processor for some reason).

In the big picture it's probably better for most people to use a vendor supplied IP solution for a TWI/I2C controller, but that's not a good way to learn how things work or the different ways of designing solutions.  I wrote these modules based off the I2C spec and adjusted some things after experimenting with different devices as an academic exercise and decided to make them available under the gpl.

Hopefully this will be of some use to you.  I have tried to use a number of different design mechanisms as examples of approaches that can be used to solve problems, but I did not design this I2C/TWI implementation to be particularly small or efficient (If one wanted to, the entire solution can be done in a single module with extensive resuse/multiple use of registers and signal lines but it would be much much harder to understand and would not be easy to extract modules for use directly or as templates for other purposes.  A highly compact/signal dense core with the byte rec/xmt/addr modules connected to a soft core on an embedded processor is probably what most people would choose).

The other points to make are that the size could be reduced by decreasing the delay from 16 bits to 8 bits (for 10kHz and about you aren't going to use the upper 8 bits anyway).  Also, if your implementation is going to be on a single clip or a well controlled board then you may want to remove the filter (especially if you want to increase the speed significantly).

A good place to get the spec for I2c is: http://www.i2c-bus.org/references/

Because I currently have no need for 10 nit addressing I have only included 7 bit addressing (it would be very easy to add 10-bit addressing if needed).  I also did not implement general calls (again, I have no need for that yet but it would also be easy to add that if needed).