aboutsummaryrefslogtreecommitdiff
/*
 * | *WISHBONE DATASHEET*                                                      |
 * |---------------------------------------------------------------------------|
 * | *Description*                   | *Specification*                         |
 * |---------------------------------+-----------------------------------------|
 * | General description             | SPI master core                         |
 * |---------------------------------+-----------------------------------------|
 * | Supported cycles                | SLAVE, pipelined READ/WRITE             |
 * |---------------------------------+-----------------------------------------|
 * | Data port, size                 | 16-bit                                  |
 * | Data port, granularity          | 16-bit                                  |
 * | Data port, maximum operand size | 16-bit                                  |
 * | Data transfer ordering          | Big endian and/or little endian         |
 * | Data transfer ordering          | Undefined                               |
 * | Address port, size              | $clog2(MEMORY_BLOCKS + 1) + 8 bits      |
 * |---------------------------------+-----------------------------------------|
 * |                                 | NONE (determined by SPI slave devices   |
 * | Clock frequency constraints     |     and connections to them and also by |
 * |                                 |     memory primitive inferred)          |
 * |---------------------------------+-----------------------------------------|
 * |                                 | *Signal name*    | *WISHBONE Equiv.*    |
 * |                                 |------------------+----------------------|
 * |                                 | ACK_O            | ACK_O                |
 * |                                 | ADR_I            | ADR_I()              |
 * | Supported signal list and cross | CLK_I            | CLK_I                |
 * |     reference to equivalent     | DAT_I            | DAT_I()              |
 * |     WISHBONE signals            | DAT_O            | DAT_O()              |
 * |                                 | STB_I            | STB_I                |
 * |                                 | WE_I             | WE_I                 |
 * |                                 | RST_I            | RST_I                |
 * |                                 | STALL_O          | STALL_O              |
 * |---------------------------------+-----------------------------------------|
 * |                                 | Circuit assumes the use of synchronous  |
 * | Special requirements            |     RAM with asynchronour read          |
 * |                                 |     inreffable by synthesis software.   |
 * |---------------------------------+-----------------------------------------|
 * |                                 | The MEMORY_BLOCKS parameter can be used |
 * |                                 |     to decide the size of module's data |
 * |                                 |     transfer memory. SPI operations are |
 * |                                 |     performed as follows:               |
 * | Additional information          |     * Bytes to send through MOSI are    |
 * |                                 |       written at the beginning of data  |
 * |                                 |       transfer memory.                  |
 * |                                 |     * Number of bytes to send is        |
 * |                                 |       written to the "bytes_to_output"  |
 * |                                 |       register.                         |
 * |                                 |     * Number of bytes to receive from   |
 * |                                 |       MISO afterwards is written to the |
 * |                                 |       "bytes_to_recive" register.       |
 * |                                 |     * Operation is started by writing   |
 * |                                 |       any value to the "operating"      |
 * |                                 |       register.                         |
 * |                                 |     Also see the memory map below.      |
 * |                                 |     The "operating" register can be     |
 * |                                 |     read at any time to check if an     |
 * |                                 |     SPI operation has finished. It      |
 * |                                 |     reads a non-zero value if and only  |
 * |                                 |     if the SPI operation is still       |
 * |                                 |     occuring. Register writes are not   |
 * |                                 |     possible during SPI operation. Any  |
 * |                                 |     such write will be stalled and will |
 * |                                 |     complete after SPI operation        |
 * |                                 |     finishes. This behavior can be      |
 * |                                 |     exploited to wait for operation     |
 * |                                 |     completion.                         |
 */

/*
 * The memory map is as follows:
 *   h000 - (h100*MEMORY_BLOCKS)-1                   - data transfer memory
 *   h100*MEMORY_BLOCKS                              - "bytes_to_output" reg
 *   (h100*MEMORY_BLOCKS)+1                          - "bytes_to_receive" reg
 *   (h100*MEMORY_BLOCKS)+2 - (h100*MEMORY_BLOCKS)+3 - "operating" reg
 *
 * If MEMORY_BLOCKS is set to 1, this results in the following memory map:
 *   h000 - h0FF - data transfer memory
 *   h100        - "bytes_to_output" reg
 *   h101        - "bytes_to_receive" reg
 *   h102 - h103 - "operating" reg
 *
 * Accessing any half of the "operating" reg results in the same behavior.
 * Accessing higher addresses than specified results in UNDEFINED behavior.
 */

`default_nettype none

`define ADDR_WIDTH ($clog2(MEMORY_BLOCKS + 1) + 8)

module spi_slave
  #(
    parameter MEMORY_BLOCKS = 1 /* 1 block stores 256 16-bit words */
    )
   (
    /* Interface to flash memory chip */
    output reg 			  sdo,
    input wire 			  sdi,
    output wire 		  sck,
    output reg 			  ss_n,

    /* Wishbone slave interface */
    output wire 		  ACK_O,
    input wire [`ADDR_WIDTH - 1:0] ADR_I,
    input wire 			  CLK_I,
    input wire [15:0] 		  DAT_I,
    output wire [15:0] 		  DAT_O,
    input wire 			  RST_I,
    input wire 			  STB_I,
    input wire 			  WE_I,
    output wire 		  STALL_O
   );

   parameter MEMORY_IDX_WIDTH = $clog2(MEMORY_BLOCKS) + 8;
   parameter MEMORY_MAX_ADDR = MEMORY_BLOCKS * 256 - 1;
   parameter BIT_COUNT_WIDTH = MEMORY_IDX_WIDTH + 4;

   reg [15:0] 			 memory [MEMORY_MAX_ADDR:0];

   reg [15:0] 			 data_read_from_memory;
   reg [15:0] 			 data_read_from_regs;
   wire [15:0] 			 data_to_write_to_memory;

   reg [`ADDR_WIDTH - 1:0] 	 ADR_I_latched;
   reg [15:0] 			 DAT_I_latched;
   reg [1:0] 			 wb_read_memory;
   reg 				 wb_write_memory;
   reg 				 wb_read_regs;
   reg 				 wb_write_regs;

   /*
    * The wires below are only relevant if regs are being accessed, i.e.
    * ADR_I_latched > MEMORY_MAX_ADDR.
    */
   wire 			 reg_bytes_to_output;
   wire 			 reg_bytes_to_receive;
   wire 			 reg_operating;

   assign reg_bytes_to_output = ADR_I_latched[1:0] == 2'b00;
   assign reg_bytes_to_receive = ADR_I_latched[1:0] == 2'b01;
   assign reg_operating = ADR_I_latched[1] == 1'b1;

   reg 				 ack;

   reg [2:0] 			 operating;

   wire [1:0] 			 start_operating;
   assign start_operating = {operating[2:1] == 2'b01, operating[1:0] == 2'b01};

   reg [15:0] 			 out_data; // sdo data
   reg [15:0] 			 in_data; // sdi data

   reg 				 bytes_to_output_odd;
   reg 				 bytes_to_receive_odd;
   reg [BIT_COUNT_WIDTH - 1:0] 	 bits_to_output;
   reg [BIT_COUNT_WIDTH - 1:0] 	 bits_to_receive;
   reg [MEMORY_IDX_WIDTH - 1:0]  memory_idx;

   reg [1:0] 			 spi_read_memory;
   reg 				 spi_write_memory;

   wire 			 spi_write_1byte;
   wire 			 initial_spi_read_memory;
   assign spi_write_1byte = bits_to_receive[3] == 0 && bytes_to_receive_odd;
   assign initial_spi_read_memory = start_operating[0];

   wire [MEMORY_IDX_WIDTH - 1:0] memory_read_idx;
   wire [MEMORY_IDX_WIDTH - 1:0] memory_write_idx;

   assign memory_read_idx = initial_spi_read_memory ? 0 :
			    spi_read_memory[0] ? memory_idx :
			    ADR_I_latched;

   assign memory_write_idx = spi_write_memory ? memory_idx :
			     ADR_I_latched;

   assign data_to_write_to_memory = spi_write_memory ?
				    (spi_write_1byte ?
				     {8'hXX, in_data[7:0]} :
				     {in_data[7:0], in_data[15:8]}) :
				    DAT_I_latched;

   wire 			 wb_reg_can_be_written;
   assign wb_reg_can_be_written = !operating[0];

   wire 			 wb_mread_completes;
   wire 			 wb_rread_completes;
   wire 			 wb_mwrite_completes;
   wire 			 wb_rwrite_completes;

   assign wb_mread_completes = !(spi_read_memory[0] ||
				 initial_spi_read_memory) &&
			       wb_read_memory[0];

   assign wb_rread_completes = wb_read_regs; /* can always read immediately */

   assign wb_mwrite_completes = !spi_write_memory && wb_write_memory;

   assign wb_rwrite_completes = wb_reg_can_be_written && wb_write_regs;

   wire 			 wb_operation_accepted;
   wire 			 wb_operation_pending;
   wire 			 wb_operation_completes;

   assign wb_operation_accepted = !STALL_O && STB_I;
   assign wb_operation_pending = wb_read_memory || wb_read_regs ||
				 wb_write_memory || wb_write_regs;
   assign wb_operation_completes = wb_mread_completes || wb_rread_completes ||
				   wb_mwrite_completes || wb_rwrite_completes;

   assign DAT_O = wb_read_memory[1] ? data_read_from_memory :
		  data_read_from_regs;
   assign ACK_O = ack;
   assign STALL_O = wb_operation_pending && !wb_operation_completes;

   parameter state_sending = 0;
   parameter state_receiving = 1;
   reg 				 state;

   assign sck = CLK_I;

   always @ (posedge CLK_I) begin
      operating[2:1] <= operating[1:0];

      data_read_from_memory <= memory[memory_read_idx];

      if (spi_write_memory || wb_write_memory)
	memory[memory_write_idx] <= data_to_write_to_memory;

      spi_read_memory[1] <= spi_read_memory[0] || initial_spi_read_memory;

      if (operating[0]) begin
	 spi_read_memory[0] <= bits_to_output[BIT_COUNT_WIDTH - 1:4] != 0 &&
			       bits_to_output[3:0] ==
			       (bytes_to_output_odd ? 11 : 3);

	 spi_write_memory <= bits_to_receive == 1 ||
			     bits_to_receive[3:0] ==
			     (bytes_to_receive_odd ? 9 : 1);
      end else begin
	 spi_read_memory[0] <= 0;
	 spi_write_memory <= 0;
      end

      out_data <= spi_read_memory[1] ?
		  {data_read_from_memory[7:0], data_read_from_memory[15:8]} :
		  {out_data[14:0], 1'bx};

      in_data <= {in_data[14:0], sdi};

      wb_read_memory[1] <= wb_read_memory[0];

      if (reg_bytes_to_output)
	data_read_from_regs <= bits_to_output[BIT_COUNT_WIDTH - 1:3];
      else if (reg_bytes_to_receive)
	data_read_from_regs <= bits_to_receive[BIT_COUNT_WIDTH - 1:3];
      else if (reg_operating)
	data_read_from_regs <= {16{operating[0]}};
      else
	data_read_from_regs <= 16'hXXXX;

      if (wb_write_regs && wb_reg_can_be_written) begin
	 if (reg_bytes_to_output) begin
	    bits_to_output <= DAT_I_latched << 3;
	    bytes_to_output_odd <= DAT_I_latched[0];
	 end
	 if (reg_bytes_to_receive) begin
	    bits_to_receive <= (DAT_I_latched << 3);
	    bytes_to_receive_odd <= DAT_I_latched[0];
	 end
	 if (reg_operating)
	   operating[0] <= 1; /* might be overwritten below */
      end

      if (RST_I) begin
    	 wb_read_memory[0] <= 0;
   	 wb_write_memory <= 0;
	 wb_read_regs <= 0;
	 wb_write_regs <= 0;

	 operating[0] <= 0;

	 bits_to_receive <= 16'hXXXX;
	 bits_to_output <= 16'hXXXX;//{{(BIT_COUNT_WIDTH - 4){1'bx}}, 4'h0};
       	 bytes_to_output_odd <= 1'bx;
       	 bytes_to_receive_odd <= 1'bx;

	 ack <= 0;
      end else begin // if (RST_I)
	 if (wb_operation_accepted) begin
	    ADR_I_latched <= ADR_I;
	    DAT_I_latched <= DAT_I;
	    wb_read_memory[0] <= !WE_I && ADR_I <= MEMORY_MAX_ADDR;
	    wb_write_memory <= WE_I && ADR_I <= MEMORY_MAX_ADDR;
	    wb_read_regs <= !WE_I && ADR_I > MEMORY_MAX_ADDR;
	    wb_write_regs <= WE_I && ADR_I > MEMORY_MAX_ADDR;
	 end else if (wb_operation_completes) begin
	    wb_read_memory[0] <= 0;
	    wb_write_memory <= 0;
	    wb_read_regs <= 0;
	    wb_write_regs <= 0;
	 end

	 ack <= wb_operation_completes;
      end // else: !if(RST_I)

      if (start_operating[1]) begin
	 memory_idx <= 1;
	 state <= state_sending;
      end

      if (operating == 3'b111) begin
	 if (state == state_sending) begin
	    bits_to_output <= bits_to_output - 1;

	    if (spi_read_memory[0])
	      memory_idx <= memory_idx + 1;

	    if (bits_to_output - 1 == 0) begin
	       state <= state_receiving;
	       memory_idx <= 0;

	       if (bits_to_receive == 0)
		 operating[0] <= 0;
	    end
	 end else if (state == state_receiving) begin
	    bits_to_receive <= bits_to_receive - 1;

	    if (spi_write_memory)
	      memory_idx <= memory_idx + 1;

	    if (bits_to_receive - 1 == 0)
	       operating[0] <= 0;
	 end
      end // if (operating[2])
   end // always @ (posedge CLK_I)

   always @ (negedge CLK_I) begin
      sdo <= out_data[15];
      ss_n <= !(operating == 3'b111);
   end

`ifdef SIMULATION
   initial begin
      operating[0] <= 0;
      ss_n <= 1;
   end
`endif
endmodule // spi_slave