Communication

SPI controller

spi_master

SPI controller in mode 0: the clock idles low, data changes on the falling edge and is sampled on the rising edge, 8 bits, most significant first. Pulse start while busy is low to send tx_data and receive rx_data; done pulses when rx_data is ready. The SPI clock runs at clk / (2 × CLK_DIV).

Verified by Tapeo82 generic cells at the defaultsSPI

Instantiate it

Paste into your design and connect the signals.

systemverilog
spi_master #(.CLK_DIV(4)) u_spi (
  .clk(clk), .rst(rst),
  .start(spi_start), .tx_data(spi_tx), .rx_data(spi_rx), .busy(spi_busy), .done(spi_done),
  .sclk(spi_sclk), .mosi(spi_mosi), .miso(spi_miso), .cs_n(spi_cs_n)
);

Parameters

NameDefaultMeaning
CLK_DIV4Clock cycles per SPI clock half period (1 or more).

Ports

NameDirectionWidthMeaning
clkinput1Clock; everything changes on its rising edge.
rstinput1Synchronous reset, active high.
startinput1Pulse for one cycle to start an exchange (ignored while busy).
tx_datainput8Byte to send, read when start is high.
rx_dataoutput8Byte received in the last exchange.
busyoutput1High during an exchange.
doneoutput1Pulses when rx_data is ready.
sclkoutput1SPI clock to the peripheral.
mosioutput1Data to the peripheral.
misoinput1Data from the peripheral.
cs_noutput1Chip select, active low.

Add it to a project

It is copied to ip/spi_master/spi_master.sv with a README (ip/spi_master/README.md). When your design needs it, the Architect (new designs) or the Architect (changes) suggests it and asks before using it. Or add it yourself:

How it is verified

Before every release Tapeo checks this block: its self-checking testbench passes in Icarus Verilog, Verilator -Wall reports nothing, and Yosys synthesizes it (82 generic cells at the default parameters). That shows it works for the tested cases; your copy is part of your project, and your own testbench checks it in your design.

Source

spi_master.sv
// SPI controller, mode 0 (clock idles low; data changes on the falling edge
// and is sampled on the rising edge), 8 bits, most significant bit first.
// Pulse `start` while `busy` is low to exchange `tx_data` for `rx_data`;
// `done` pulses when `rx_data` is ready. The SPI clock runs at
// clk / (2 * CLK_DIV). Tapeo Hub (verified by Tapeo).
module spi_master #(
  parameter integer CLK_DIV = 4  // clk cycles per SPI clock half period, 1 or more
) (
  input  logic       clk,
  input  logic       rst,
  input  logic       start,
  input  logic [7:0] tx_data,
  output logic [7:0] rx_data,
  output logic       busy,
  output logic       done,
  output logic       sclk,
  output logic       mosi,
  input  logic       miso,
  output logic       cs_n
);
  localparam integer COUNT_WIDTH = (CLK_DIV > 1) ? $clog2(CLK_DIV) : 1;
  localparam logic [COUNT_WIDTH-1:0] LAST_TICK = COUNT_WIDTH'(CLK_DIV - 1);

  typedef enum logic [1:0] { IDLE, TRANSFER, FINISH } state_t;
  state_t state;
  logic [COUNT_WIDTH-1:0] ticks;
  logic [2:0] bit_index;
  logic [7:0] shift_tx, shift_rx;

  always_ff @(posedge clk) begin
    if (rst) begin
      state <= IDLE;
      ticks <= '0;
      bit_index <= '0;
      shift_tx <= '0;
      shift_rx <= '0;
      rx_data <= '0;
      sclk <= 1'b0;
      cs_n <= 1'b1;
      done <= 1'b0;
    end else begin
      done <= 1'b0;
      case (state)
        IDLE: begin
          sclk <= 1'b0;
          cs_n <= 1'b1;
          if (start) begin
            cs_n <= 1'b0;
            shift_tx <= tx_data;
            bit_index <= '0;
            ticks <= '0;
            state <= TRANSFER;
          end
        end
        TRANSFER: begin
          if (ticks == LAST_TICK) begin
            ticks <= '0;
            if (!sclk) begin
              sclk <= 1'b1;                          // rising edge: sample
              shift_rx <= {shift_rx[6:0], miso};
            end else begin
              sclk <= 1'b0;                          // falling edge: next bit
              if (bit_index == 3'd7) state <= FINISH;
              else begin
                bit_index <= bit_index + 1'b1;
                shift_tx <= {shift_tx[6:0], 1'b0};
              end
            end
          end else ticks <= ticks + 1'b1;
        end
        FINISH: begin
          cs_n <= 1'b1;
          rx_data <= shift_rx;
          done <= 1'b1;
          state <= IDLE;
        end
        default: state <= IDLE;
      endcase
    end
  end

  assign busy = (state != IDLE);
  assign mosi = shift_tx[7];  // the bit being sent; ignored while cs_n is high
endmodule

← Every block in Tapeo Hub