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.