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.