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.