Tuesday, April 26, 2011

Tf-spider boards arrive...

Yay, I received the tf-spider boards... I used Laen's service (DorkbotPDX) so I got 3 copies of the design.  All 3 looked visually correct so I did some quick electrical tests (resistance between linked pins and unlinked pins) and I couldn't find any pad that wasn't connected as intended or any unintended connections.  The one mistake I made was that I made the holes too small (but since this doesn't matter for the surface mount parts and I have female connectors that will fit it's not a big deal - I think I made them 0.032 instead of 0.040).  The holes were where they should be (not off center) and very consistently aligned across the board.

Not a great image here but what I wanted to look at was the quality of the mask and the copper layer.  I expected the silkscreen to have lower resolution (but it's still useful even for the tiny letters that indicate the component values - this makes it much easier to assemble).  The copper layer is better than I expected with extremely uniform line width and uniform spacing between traces.  This is nice to see since the backplane (which isn't here yet) is going to push the production process much more than this board.

Here you get to see dust and fibers (I was handling the board a lot) but what's interesting is that you can see the way the mask and the silk screen is handled by the vias - no problems at all... Overall the productions quality is much better than I expected.

Here is the Xlinix coolrunner II cpld soldered on the board.  One thing I'm always afraid of is how the pads accept solder (it can vary quite a lot and is a very unhappy thing if the solder doesn't wet correctly).  These boards were very happy with the solder I used (classic 67/33 mixture).  There aren't enough components here to justify breaking out the solder paste so I just hand solved.  (Ignore the residual flux because I didn't clean the board after soldering here... also ignore the fact that my soldering skills are not as perfect as they should be and I put too much solder on some pads).  There were no problems on the surface mount pads and the through the hole components had nice flow even thought the holes were smaller than they should have been.

If you look at the design there are clear block of elements that make it easier to assemble a few components and the test for function (for example the resistor + led combo on the cpldVccio power lines -> assemble then test with a couple of AA batteries).  Then the slightly more complicated ldo/resistor/cap/led combos - do the 3v3 first and after testing do the 1v8 because it uses the 3v3 output as the input source - directly connect the 1v8 enable from the msp430 socket).  Then the filter caps and the cpld and then all the female connectors. 

I'm still working on the msp430 controller code (time is at a premium when the weather is nice) but I'll post it when it's done.   

Friday, April 22, 2011

tf-card-alpha-niblet...

The niblet is a small card designed for the tanukifu alpha backplane (DIN 41612 based with non-standard pinout).  The primary purpose is for adding terminations for the differential pairs and single ended lines (which could be very different depending on the actual board production quality and the other cards attached to the system - not a problem at lower frequencies, but very important at high ones).

The differential pairs are placed on the top copper layer (red lines here) and the single ended lins are placed on the lower copper layer (green lines here).  I know it would be much better to use more than 2 layers, but part of this project is trying to see how things actually work in reality (not just on paper) and 2 layers are much cheaper to have produced (and I really want to see what kind of performance I can get out of two layers).  The copper fills are not shown here.

If we look more closely are the differential pairs here you can see that there are two sets of pads for each pair... This will make it significantly easier to look at fine grain adjustments to the resistance and will project access points for testing.

Looking at the single ended lines you can see that I put two pads (one for binding to ground and one for binding to 3v3).  These may be used with resistors to drain current (limiting the magnitude of reflections), or to pull up/pull down the line, or to set the nominal voltage level.  They may also be used to adjust the line capacitance (allowing for slew rate adjustment).  There is also an access point that can be used for prototyping or signal access.

The msp430 controller section is just like the template stub and serves as a means to control the activation state of the card over I2C/TWI from the backplane.  Here the 6 available lines are brought out to access points for prototyping.  If the card is only being used for termination these parts can be left empty on the board.  To the left is the 3v3 LDO section, which can also be left unfilled if unneeded.

Since the board space was available I added access ports for the bus voltage supply lines (1,2 and 3), for 3v3 with ample ground points.  I also added access to the board I2C/TWI lines and the bus SPI lines.  Again, if the board only needs to be used for termination then these components do not need to be placed.

The schematics and board files are here along with other files associated with the tanukifu project.

 
The goal of the Tanukifu project is to build a flexible and open set of tools to explore system development for purely educational/academic reasons. 

Wednesday, April 20, 2011

Tf-alpha-card stub...

Way too busy to post much lately but I finally decided on the minimal elements of the card for the tf-alpha bus so I setup blank board as a template.

I'm using a little msp430 as the board power controller (on the backplane I2C/TWI bus) that is always powered itself for supply1 through a 3v3 ldo.  I added links for an electrolytc cap (may or may not be useful depending on what goes on a particular board) but it's isn't necessary all the time.  It's easy to add to the 3v3 access traces though...  As usual there is a spy by wire access port and a jumper set to power the msp430 through the access port or the onboard regulator.  I did add a power led for the 3v3 line since it could have large caps on it and so a vivual check (and an automatice drain) is nice to have...

Of course the pinout for the card looks the same as the bus connectors... (96 pin DIN41612 Type C)

I kept the pins facing the inside of the card area available for use... The programming on a given card will determine how they power up and enable parts of the card.  It isn't necessary to use a 2x31 series chip though (but I like the ADC availability so I use them in the layout).

The regulator for the msp430 needs to always be enabled (or the control mechanism won't be available of course)...  All the other supplies on the card ought to be under the control of the microcontroller though... (again, the 330uf cap isn't needed, but could be useful to have in the layout depending on the design and since it's basically free to include the holes now...)

As you would expect, a template ought to be mostly a happy copper plane... I routed the supply1 connective links on the farthest side (near the backplane).  There are two regions of pour near the voltage regulator that I restricted to adjust the return current pathing but otherwise it's happy copper land.

On the back you can see the I2C/TWI lines (far to the outside since they are much lower speed and probably noisier than what's coming on the boards later.  The enables (mater and unique) get routed between some pins and along the outside (but remember they are basically on/off and won't switch much so interference either way shouldn't be a problem here.


Just a tiny post and not all that interesting, but the files are here and it does serve as a good template for the series of cards coming.

The goal of the Tanukifu project is to build a flexible and open set of tools to explore system development for purely educational/academic reasons. 

Saturday, April 16, 2011

the tf-bp-alpha board...

The tf-bp-alpha board is intended to test out some ideas in hardware about experimental bus designs and protocols.  I pushed a two layer design partially just to see what the practical performance would be in actual hardware and partially to work on design skills.  I'm using DIN41612 type C connectors because of the availability, mechanical strength and number of available pines.

This figure shows the layer stacks without the copper fills.  Some unusual routes were made to increase the connectivity of some of the ground planes and to reduce the distance of some ground loops.  To limit the effect of short duration high current draws from altering the supply voltage available across the bus, large electrolytic caps were strategically placed.  There are three power supply lines: supply 1 is intended to provide 5 to 10 volts, supply 2 is intended to supply 12 to 36 volts and supply three is intended to be a low power battery backup.  In general I prefer to provide small voltage regulators close to the location they are needed and not to depend on supply lines having the correct voltages.
There are 21 signal lines intended to be used as differential pairs (close coupled to each other) and 21 signal lines intended to be used as single data lines (far apart and under the differential pairs).  These are arranged in 3 sets of six between power and ground lines and 1 set of 3 also near the second supply lone.

Looking at the gerbers for the copper planes makes it easier to see the strength of the connections between contiguous regions of copper (remember to check the distance of ground returns and as well as the types of signals that could influence them).
While I don't want to add impedance control on the signal lines on the backplane (and rather handle that on the cards attached - part of the project), I do want the ability to control the activation state of the cards and so I am including a pic microcontroller on the backplane.  The one I chose has an spi interface as well as an I2C/TWI interface that is routed to the connectors (unique pins for each connector for the spi interface).  I have also included a master enable line (which is intended to allow the functional behavior of all cards to be inhibited while configuration occurs).  Separate connectors are included to allow in circuit programming and reset of the controller.  In addition to the msten/scl/sda/and enable lines there are 2 independent lines from the microcontroller for status signals.  These 10 signals are brought to a socket and can be used with an led bar, or signals can accessed directly when needed.

As usual, I like to use labels for wiring the schematics - it makes it much easier to alter connectivity or include additional components in the future.  I also like to use different sheets in the schematic for logically groups components (power, connectors, controllers, displays...) but at times it makes more sense to include subsets of components on the same sheet.

The goal of the Tanukifu project is to build a flexible and open set of tools to explore system development for purely educational/academic reasons.  The files for the tf-bp-alpha boards can be found here.  Once I get the boards back and tested, the code for the microcontroller will be added as well.

Friday, April 8, 2011

The tf-spider board...

I've been looking around for a small cpld board to test out some bus protocol and interface designs but I couldn't find one that really fit my needs so... time to design my own one.  It's been a while since I did board work so I checked out the available tools and finally decided on Kicad.  I love software that's intuitive and just feels right to me.  This image shows all the layers of the design stacked up, but let's pull it apart a bit...

Here I removed the front and back copper fills so it's easier to read the words and discriminate the signal lines.  I made the power and ground lines thicker than the signal lines (red on the front copper plane and green on the back copper plane.  component labels in white will be visible on the silkscreen as well as the printed words in light blue (front) and purple (on the back) - they will all print white.

This is the copper layer for the front.

...and this is the copper plane for the back (green is copper and black is bare pcb.

The silk screen (front)... everything not black here is going to be white in the end.  I like labels because it makes things much easier to assemble...

 
The drill map is always a good thing to check, holes for through the hole components, vias (to connect signals across layers) and the mounting holes appear here.

Before you can make the board though, you need to make schematics to bind all the components together in a logical way (otherwise the prettiest board wouldn't do much at all).  This is the cpld (a Xilinx coolrunner II).



This is for the power... I find that 3.3 volts is common enough that I want it built on the board.  The cpld needs 1.8 volts so it get's it's own power supply (I am using the 3v3 output for efficiency reasons here).  






I really like to have tiny leds connected to each power source - not much beats being able to visually first...

I'm adding a small microcontroller here (a TI MSP30) so that I can monitor the voltages and change the activation/power state of the cpld over TWI/I2C (this is what provides the enable for the 1v8 supply). As you can tell I really like to label the pins (blue) to make it much harder to make when making connections.

And of course lots of connectors...  One of the primary reasons I wanted to make my own board was that I couldn't find any cpld breakouts with multiple pins to connect to the ground and power lines (which is really nice when you are going to inject signals into other boards that have their own power subsystems).  I also really wanted the abilities to control the activation of the board over TWI/I2C and to be able to measure the voltages remotely.


If you don't need these extra abilities, there is a promising board from dangerous prototypes here.

I've put the all the Kicad files for the tf-spider here but be aware that it's still in the process of being tested.  The code the msp430 on the board will be added after I can test it on a live board.

Thursday, April 7, 2011

Building an spi controller... (part 6)

To make things easier in practice (especially if you have multiple types of devices with different patterns and transfer protocols) it's not a bad idea to handle the transfers easily between modules.

This module allows the setting of internal parmeters like the clock divider, polarity/phase and mode.  It also handles the flag pairs for moving data between another module and the bus.

This is a useful approach if you want to add capability to a device that interacts with a software limited device (like a microcontroller).  If your design is tightly defined then there is no reason for the last few modules with the buffers and ability to have built in multiple pattern logic for different devices.



 // This the primary controller - handles cmd signals and data_in
   always @ (posedge clock) begin
      if (reset) begin
         softreset        <= 1'b0;
         clock_divider    <= 8'b00000000;
         spi_select       <= 5'b11111;   
         polarity         <= 1'b0;
         phase            <= 1'b0;
         slave            <= 1'b0;               
         cmd_ack          <= 1'b0;         
         send_intent      <= 1'b0;
         spi_clock_live   <= 1'b0;
         spi_miso_enable  <= 1'b0;
         spi_mosi_enable  <= 1'b0;
         cmd_switch       <= `TF_SPI_IDLE;
     mode             <= `TF_SPI_MODE_DEFAULT;    
      end // in reset
      if (!reset) begin                 
         // we have a new command to process     
         case (cmd_switch)
           (`TF_SPI_CMD_RESET): begin
              // This is a soft reset - isolate since we have to ack the cmd
              softreset        <= 1'b1; // propagates where needed
              clock_divider    <= 8'b00000000;
              spi_select       <= 5'b11111;      
              polarity         <= 1'b0;
              phase            <= 1'b0;
              slave            <= 1'b0;       
              cmd_ack          <= 1'b1;          
              send_intent      <= 1'b0;
              spi_clock_live   <= 1'b0;
              spi_miso_enable  <= 1'b0;
              spi_mosi_enable  <= 1'b0;
              cmd_switch       <= `TF_SPI_IDLE;
          mode             <= `TF_SPI_MODE_DEFAULT;
           end // cmd is TF_SPI_CMD_RESET   
           (`TF_SPI_IDLE): begin   
              if (cmd_enable && !cmd_ack) begin
                 cmd_switch <= cmd;  // pass command to switch                          
              end                                            
              else if (!cmd_enable && cmd_ack) begin
                 if (softreset) begin
                    softreset <= 1'b0;  
                 end // softreset clear when enable drops                
                 cmd_ack <= 1'b0; // release block on command accept
              end // block release conditions
              else if (slave) begin
                 // don't drive clock or select and listen/send if selected
                 spi_select_enable <= 1'b0;     
                 if (select == spi_select) begin                      
                    spi_miso_enable   <= 1'b1;
                    spi_mosi_enable   <= 1'b0;
                 end
              end // slave enabler if selected  
              else if (!slave) begin
                 // not much to do
                 spi_select_enable <= 1'b1;     
                      
                 cmd_switch       <= `TF_SPI_IDLE;
              end

          // set mode dependent bits to transfer
          case(mode)
        (`TF_SPI_MODE_DEFAULT): begin
           transferbits <= 6'b000111;           
        end
        (`TF_SPI_MODE_LTC1407A1): begin
                   transferbits <= 6'b100001;  
        end
        (`TF_SPI_MODE_64): begin
           transferbits <= 6'b111111;    
        end
          endcase
           end // case: (`TF_SPI_IDLE)          
           (`TF_SPI_SET_CONFIG): begin
                // look at the data_in lines to set the polarity and phase mode
                polarity        <= data_in[0];
                phase           <= data_in[1];
                spi_select[0]   <= data_in[2];
                spi_select[1]   <= data_in[3];
                spi_select[2]   <= data_in[4];
                spi_select[3]   <= data_in[5];
                spi_select[4]   <= data_in[6];
                slave           <= data_in[7];          
                cmd_switch      <= `TF_SPI_IDLE;
                cmd_ack         <= 1'b1;
           end // case: (`TF_SPI_SET_CONFIG)  
       (`TF_SPI_SET_MODE): begin
                // look at the data_in lines to set the transfer logic mode
                mode[2]         <= data_in[7];      
                mode[1]         <= data_in[6];      
                mode[0]         <= data_in[5];      
                cmd_switch      <= `TF_SPI_IDLE;
                cmd_ack         <= 1'b1;
           end // case: (`TF_SPI_SET_MODE)
           (`TF_SPI_SET_DIVIDER): begin
                // look at the data_in lines to set the spi clock divider
                clock_divider   <= data_in;             
                cmd_switch      <= `TF_SPI_IDLE;
                cmd_ack         <= 1'b1;
             end // case: (`TF_SPI_SET_DIVIDER)            
           (`TF_SPI_CMD_SEND): begin
              // here we want to send data on the bus
              if (!xmt_ack && !send_intent) begin
         // set based on mode then flag to send_intent
         case(mode)
           (`TF_SPI_MODE_DEFAULT): begin
                      xmt_buffer[7:0]   <= data_in[7:0];                  
                      send_intent       <= 1'b1;
           end
           (`TF_SPI_MODE_LTC1407A1): begin
              // no meaning here since the device can't read
              cmd_ack         <= 1'b1;                 
                      send_intent     <= 1'b0;    
                   end
           (`TF_SPI_MODE_64): begin
              if (bytes_in == 3'b000) begin
             xmt_buffer[63:56]   <= data_in[7:0];
              end
              else if (bytes_in == 3'b001) begin
             xmt_buffer[55:48]   <= data_in[7:0];
              end
              else if (bytes_in == 3'b010) begin
             xmt_buffer[47:40]   <= data_in[7:0];
              end
              else if (bytes_in == 3'b011) begin
             xmt_buffer[39:32]   <= data_in[7:0];
              end
              else if (bytes_in == 3'b100) begin
             xmt_buffer[31:24]   <= data_in[7:0];
              end
              else if (bytes_in == 3'b101) begin
             xmt_buffer[23:16]   <= data_in[7:0];
              end
              else if (bytes_in == 3'b110) begin
             xmt_buffer[15:8]    <= data_in[7:0];
              end
              else if (bytes_in == 3'b111) begin
             xmt_buffer[7:0]     <= data_in[7:0];           
             send_intent         <= 1'b1;              
              end
              if (bytes_in < 3'b111) begin
             bytes_in <= bytes_in + 1;              
              end
                end
              default: begin
             cmd_ack         <= 1'b1;                 
             send_intent     <= 1'b0;    
              end
         endcase // case (mode)
              end
              if (xmt_ack && send_intent) begin
                 send_intent    <= 1'b0;    
         bytes_in       <= 3'b000;        
                 cmd_switch     <= `TF_SPI_IDLE;
                 cmd_ack        <= 1'b1;
              end
             end // case: (`TF_SPI_CMD_SEND)    
           (`TF_SPI_CMD_STOP): begin
              spi_select_enable <= 1'b0;
              spi_miso_enable   <= 1'b0;
              spi_mosi_enable   <= 1'b0;
              slave             <= 1'b0;
              if (!spi_select_enable) begin
                 spi_clock_live    <= 1'b0;             
                 cmd_switch        <= `TF_SPI_IDLE;
                 cmd_ack           <= 1'b1;
              end
             end // case: (`TF_SPI_CMD_STOP)     
           (`TF_SPI_CMD_START): begin
              slave             <= 1'b0;
              spi_miso_enable   <= 1'b0;
              spi_mosi_enable   <= 1'b1;
              spi_clock_live    <= 1'b1;
              if (spi_clock_enable) begin
                 spi_select_enable <= 1'b1;
                 cmd_switch        <= `TF_SPI_IDLE;
                 cmd_ack           <= 1'b1;
              end
           end // case: (`TF_SPI_CMD_START)     
           default: begin
              // nothing to do here, just ack the cmd
              cmd_switch     <= `TF_SPI_IDLE;
              cmd_ack        <= 1'b1;
           end // cmd is unknown                                                
         endcase   
      end // if (!reset)
   end // always @ (posedge clock)
To make things easier to understand we can make a single module that hides the internal connections and only reveals the relevant signals to the outside world.

You can see how the different modules are highly connected together internally but relatively few signals need to connect to the outside world. 

The goal of building the spi interface this way was to look at exactly what happens when a design demands unknown flexibility... If you change the internal buffer sizes, expand or reduce the number of device specific transfer patterns built in or alter the io buffer sizes you can get a feel for the consequences... doing this in software is so much easier, but that's ot always possible


As usual, all of the full verilog files can be found here. 

Wednesday, April 6, 2011

Building an spi controller... (part 5)

In a similar way that we were pumping data onto the bus, we are going to use the master/slave mode and the logical signals from the previous module to pump data into an internal buffer onto the correct line (MISO or MOSI).

This buffer size is completely arbitrary and could be any size and still function correctly (but here I have it setup to do byte transfers out).  The number of bits to transfer from the buffer is passed as well as a pair of lines to indicate when transfers should begin and when all the needed bits have been sent.

Like the previous module it would be better to use a block of memory instead of registers but for this example it's easy to understand.  Since many devices use different transfer patterns/number of bits/setup bits etc... I added (mode) to allow individual patterns to be built it - all of this can be adjusted as needed.  The The hdl is straightforward in verilog...

 // If we want to receive data then data_out_enable will be high
   // until the buffer is filled, read according to mode and slave
   // status.  Once the buffer is filled, throw data_out_ack high then
   // clear data_out_ack when data_out_enable goes low.
   always @ (posedge clock) begin
      if (reset) begin
         // reset all the control registers/delays
         data_out_ack    <= 1'b0;
         data_rec_enable <= 1'b0;       
         rec_counter     <= 0;
         rec_intent      <= 1'b0;
         bytes_out       <= 3'b000;     
      end
      else if (!reset) begin   
         if (data_out_enable && !data_rec_enable && !rec_intent &&
              (!slave || (select == spi_select))) begin
            // we want to receive
            rec_intent  <= 1'b1;
            bytes_out   <= 3'b000;             
            rec_counter <= transferbits;
         end
         else if (data_out_enable && rec_intent && !data_rec_enable) begin
            // we want to rec and the buffer has space for it - leave here
            // by setting data_rec_enable high
            if (busdetect_read0) begin
              rec_buffer[rec_counter] <= 1'b0;
               if (rec_counter > 0) begin
                  rec_counter <= rec_counter - 1;
               end            
               else if (rec_counter == 0) begin
                  data_rec_enable <= 1'b1;  
                  rec_intent      <= 1'b0;
               end       
            end
            else if (busdetect_read1) begin
               rec_buffer[rec_counter] <= 1'b1;
               if (rec_counter > 0) begin
                  rec_counter  <= rec_counter - 1;
               end            
               else if (rec_counter == 0) begin
                  data_rec_enable <= 1'b1;  
                  rec_intent      <= 1'b0;
               end
            end                                
         end
         else if (data_out_enable && rec_intent && data_rec_enable) begin
            // clear flag when buffer is full just in case
            rec_intent   <= 1'b0;          
         end
         else if (data_out_enable && !data_out_ack && !rec_intent && data_rec_enable) begin
            // we have the data in the buffer and need to pump it out in bytes
            // data_out_ack signals data is ready to read.  Drop data_rec_enable when last set
            case(mode)
              (`TF_SPI_MODE_DEFAULT): begin
                 data_out[7:0]   <= rec_buffer[7:0];              
                 data_rec_enable <= 1'b0;
                 data_out_ack    <= 1'b1;               
              end
          (`TF_SPI_MODE_LTC1407A1): begin   
         // 34 bit read pad of 2 then 14 bit adc0 then pad2 then 14bit adc1 then pad2
         if (bytes_out == 3'b000) begin
                    data_out[7:0]   <= {2'b00, rec_buffer[31:26]}; 
                    data_out_ack    <= 1'b1;   
                 end
                 else if (bytes_out == 3'b001) begin
                    data_out[7:0]   <= rec_buffer[25:18]; 
                    data_out_ack    <= 1'b1;   
                 end
                 else if (bytes_out == 3'b010) begin
                    data_out[7:0]   <= {2'b00, rec_buffer[15:10]}; 
                    data_out_ack    <= 1'b1;   
                 end
                 else if (bytes_out == 3'b011) begin
                    data_out[7:0]   <= rec_buffer[9:2];                   
                    data_rec_enable <= 1'b0;
                    data_out_ack    <= 1'b1;   
                 end
                      if (bytes_out < 3'b111) begin
                    bytes_out <= bytes_out + 1;              
                 end
          end
              (`TF_SPI_MODE_64): begin
                 if (bytes_out == 3'b000) begin
                    data_out[7:0]   <= rec_buffer[63:56]; 
                    data_out_ack    <= 1'b1;   
                 end
                 else if (bytes_out == 3'b001) begin
                    data_out[7:0]   <= rec_buffer[55:48]; 
                    data_out_ack    <= 1'b1;   
                 end
                 else if (bytes_out == 3'b010) begin
                    data_out[7:0]   <= rec_buffer[47:40]; 
                    data_out_ack    <= 1'b1;   
                 end
                 else if (bytes_out == 3'b011) begin
                    data_out[7:0]   <= rec_buffer[39:32]; 
                    data_out_ack    <= 1'b1;   
                 end
                 else if (bytes_out == 3'b100) begin
                    data_out[7:0]   <= rec_buffer[31:24]; 
                    data_out_ack    <= 1'b1;   
                 end
                 else if (bytes_out == 3'b101) begin
                    data_out[7:0]   <= rec_buffer[23:16]; 
                    data_out_ack    <= 1'b1;   
                 end
                 else if (bytes_out == 3'b110) begin
                    data_out[7:0]   <= rec_buffer[15:8]; 
                    data_out_ack    <= 1'b1;   
                 end
                 else if (bytes_out == 3'b111) begin
                    data_out[7:0]   <= rec_buffer[7:0];                   
                    data_rec_enable <= 1'b0;
                    data_out_ack    <= 1'b1;                 
                 end
                 if (bytes_out < 3'b111) begin
                    bytes_out <= bytes_out + 1;              
                 end
              end              
            endcase // case (mode)
         end
         else if (!data_out_enable && data_out_ack) begin
            // clear flag
            data_out_ack <= 1'b0;                  
         end      
      end
   end
 As usual, all of the full verilog files can be found here. 

Monday, April 4, 2011

Building an SPI controller... (part 4)

To pump data onto the bus we are going to use the master/slave mode and the logical signals from the previous module to pump data from an internal buffer onto the correct line (MISO or MOSI).  Since we are not restricted to a given number of bits but most of the devices tend to need transfers of less than 64 bits I am making the buffer 64 bits wide. 

This buffer size is completely arbitrary and could be any size and still function correctly.  The number of bits to transfer from the buffer is passed as well as a pair of lines to indicate when transfers should begin and when all the needed bits have been sent.  By making this buffer larger, there is less overhead on the flag pairs (and by making it smaller there is more overhead)...

For a fixed application this type of module isn't needed but it makes the other modules easier to maintain.  Since I am controlling the MISO and MOSI lines here (tristated) it made sense to include a buffer.


  always @ (posedge clock) begin  
      if (reset) begin
         xmt_busy            <= 1'b0;  
         xmt_ack             <= 1'b0;
         xmt_counter         <= 6'b000000; 
      end      
      else if (!reset) begin
         if (send_intent && !xmt_ack && !xmt_busy) begin
            // setup initial transmit counter and sig
            xmt_busy           <= 1'b1;
            xmt_counter        <= transferbits;  
         end
         else if (!send_intent & xmt_ack) begin
            // clear signal
            xmt_busy <= 1'b0;      
            xmt_ack  <= 1'b0;
         end
         if (send_intent && busdetect_writenow && xmt_busy) begin
            if (!slave) begin
               spi_mosi_write <= xmt_buffer[xmt_counter];              
            end                                            
            else if (slave) begin
               spi_miso_write <= xmt_buffer[xmt_counter];    
            end 
            if (xmt_counter == 6'b000000) begin
               // sent last we need to so finish
               xmt_busy <= 1'b0;     
               xmt_ack  <= 1'b1;       
            end
            else if (xmt_counter > 0) begin
               xmt_counter <= xmt_counter - 1;
            end
         end // conditions to write
      end // not in reset
   end // write handler
 As usual, all of the full verilog files can be found here. 

Friday, April 1, 2011

Building an SPI controller... (part 3)

Last time we made a module to detect changes in the clock, MISO and MOSI lines and the time before that we made a module to drive the clock taking into account the polarity and phase of the bus.  This time we are going to make a module to extract logical information...

We need to know the moment we should transmit data and whether we received a low (0) or high (1) bit.  To create these signals we need to integrate the raw bus signals, polarity, phase, master/slave state, clock divider.

This was the way I wrote the verilog but if I had it to do again I would have used more assigns and fewer conditionals (but I'm leaving it this way here)...
if ((xmt_busy_p != xmt_busy) && xmtbegin) begin
              // we are just starting or stopping transmittion
              // Sometimes we need to setup the write early since
              // we get a read edge before a write edge on the spi clock.
              if (polarity == 1'b0 && phase == 1'b0 && busdetect_spi == 1'b0)  begin
                 // mode 0 write on falling clock read on rising clock
                 busdetect_writenow <= 1'b1; // no delay
              end                         
              else if (polarity == 1'b1 && phase == 1'b0 && busdetect_spi == 1'b1) begin
                 // mode 2 read on falling clock write on rising clock
                 busdetect_writenow <= 1'b1; // no delay
              end
         end                                    
         else if (busdetect_spi_pos != busdetect_spi_pos_p) begin
            busdetect_spi  <= 1'b1;        
            // spi should be high, had rising clock
            if (polarity == 1'b0 && phase == 1'b0)  begin
               // mode 0 write on falling clock read on rising clock
               if (!slave) begin
                  busdetect_read0 <= ~busdetect_miso0;
                  busdetect_read1 <= busdetect_miso0;
               end
               else if (slave) begin
                  busdetect_read0 <= ~busdetect_mosi0;
                  busdetect_read1 <= busdetect_mosi0;
               end
            end
            else if (polarity == 1'b0 && phase == 1'b1) begin
              // mode 1 write on rising clock read on falling clock
               write_interval <= 1'b1;                   
            end
            else if (polarity == 1'b1 && phase == 1'b0)  begin
              // mode 2 write on rising clock read on falling clock
               write_interval <= 1'b1;         
            end
            else if (polarity == 1'b1 && phase == 1'b1) begin
              // mode 3 write on falling clock read on rising clock
               if (!slave) begin
                  busdetect_read0 <= ~busdetect_miso0;
                  busdetect_read1 <= busdetect_miso0;
               end
               else if (slave) begin
                  busdetect_read0 <= ~busdetect_mosi0;
                  busdetect_read1 <= busdetect_mosi0;
               end   
            end          
         end
         else if (busdetect_spi_neg != busdetect_spi_neg_p) begin
            busdetect_spi  <= 1'b0;
            // spi should be low, had falling clock
            if (polarity == 1'b0 && phase == 1'b0)  begin
              // mode 0 write on falling clock read on rising clock
               write_interval <= 1'b1;
            end
            else if (polarity == 1'b0 && phase == 1'b1) begin
              // mode 1 write on rising clock read on falling clock
               if (!slave) begin
                  busdetect_read0 <= ~busdetect_miso1;
                  busdetect_read1 <= busdetect_miso1;
               end
               else if (slave) begin
                  busdetect_read0 <= ~busdetect_mosi1;
                  busdetect_read1 <= busdetect_mosi1;
               end                 
            end
            else if (polarity == 1'b1 && phase == 1'b0)  begin
              // mode 2 write on rising clock read on falling clock
               if (!slave) begin
                  busdetect_read0 <= ~busdetect_miso1;
                  busdetect_read1 <= busdetect_miso1;
               end
               else if (slave) begin
                  busdetect_read0 <= ~busdetect_mosi1;
                  busdetect_read1 <= busdetect_mosi1;
               end                    
            end
            else if (polarity == 1'b1 && phase == 1'b1) begin
              // mode 3 write on falling clock read on rising clock
               write_interval <= 1'b1;
            end                  
         end   
         else begin
            // clear signals so they only last 1 clock pulse
            write_interval     <= 1'b0;
            busdetect_read0    <= 1'b0;    
            busdetect_read1    <= 1'b0; 
         end   

         // single pulse indicates best time to write inside cycle
         if (interval_count > 0) begin
            interval_count <= interval_count - 1;                 
            interval_trip  <= 1'b0;        
         end
         else if (write_interval) begin    
            interval_count <= clock_divider;
         end
         else if ((interval_count == 7'b0000000) && !interval_trip) begin
            busdetect_writenow <= 1'b1;                   
            interval_trip      <= 1'b1;                   
         end
        
         // clear flag after 1 cycle
         if (busdetect_writenow) begin
            busdetect_writenow <= 1'b0;
         end   
        
         // remember previous edge states
         busdetect_spi_pos_p <= busdetect_spi_pos;
         busdetect_spi_neg_p <= busdetect_spi_neg; 
         xmt_busy_p          <= xmt_busy;
Like most things in life there are many ways of doing things and by looking at the width of the logic it should be obvious that there is a significantly larger delay to get the signal from the inputs to the outputs than is needed... It's not difficult to redesign this so that the results come faster, but I did it this way because it's easier to understand how the signals are interpreted for the different modes.  I generally like to make certain the signals are correct before I optimize things later.  The timing really isn't that bad for this type of module because there are relatively large delays between the signal edges. 

As usual, all of the full verilog files can be found here.