SPI Slave VHDL design

In this post, we are going to design the VHDL code for the SPI slave module that can be connected to an SPI master.

You will see:

  • SPI slave typical protocol
  • SPI slave four wire hardware design
  • VHDL implementation of a 4-wire SPI slave
  • VHDL simulation of SPI Master-slave communication
  • Layout consideration for SPI slave implementation

Serial Peripheral Interface Introduction โ€“ SPI Master / Slave

In a previous post, we introduced the SPI master controller module. The SPI master originates the frame for reading and writing multiple SPI slave devices using individual slave select (SS) lines.

The SPI is a three or four-wire serial bus as you can see in Figure 1.

Figure 1 โ€“ SPI Master-Slave 4-wire connection example (image Wikipedia)

For further information see https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus

The SPI is, basically, a serial to parallel converter controlled by the SS signal. Input data MOSI (Master Out Serial In) goes through an input shift register when the SS control signal is low. When the SS goes from low to high, data in the input shift register is ready to be read.

The same approach applies to the MISO (Master In Serial Output) output signal from the slave. In this case, the data in the output shift register is serialized at each clock cycle by shifting the parallel data.

Figure 2 reports the input-output shift register example for input MOSI and output MISO SPI slave signals.

Figure 2 โ€“ SPI-Slave MISO/MOSI shift register for 8-bit data example

SPI slave design overview

The SPI bus specifies four logic signals:

SCLK : Serial Clock (output from master).

MOSI : Master Output, Slave Input (output from master).

MISO : Master Input, Slave Output (output from slave).

SS : Slave Select (active low, output from master).

You can find alternate names, but the functions are the same.

Figure 3 SPI-Slave timing

The SPI timing example is shown in Figure 3. The input MOSI can be clocked either on the rising or falling edge of SCLK depending on the polarity CPOL selected. If MOSI is strobed on the rising edge of SCLK, MISO will be clocked on the falling edge and vice versa.

From Figure 3, when CPOL=1, the SPI slave data input/output changes on the falling edge of SCLK. The serial data is shifted on the input shift register on the rising edge of the clock (blue vertical dotted lines) and the output data is serialized by shifting the output shift register on the falling edge of the clock (red vertical dotted line).

In the case of CPOL=0, the behavior is similar, taking into account the opposite clock edges.

The SPI protocol also defines the clock phase (CPHA), which together with CPOL determines the four SPI modes (0-3). In this implementation data is sampled on the first clock edge after SS goes low, corresponding to CPHA = 0. The CPOL generic lets you select the polarity; you can extend the code by adding a CPHA generic for full mode support.

SPI slave VHDL implementation

Before going into the SPI slave VHDL code, letโ€™s review the SPI slave architecture of Figure 2 with timing reported in Figure 3.

o_busy             : serial data received/transmitted 
o_data_parallel    : received parallelized data
i_data_parallel    : parallel data to sent
i_sclk             : serial clock output
i_ss               : slave select
i_mosi             : Master Ouput Serial In (input serial data)
o_miso             : Master In Serial out (output serial data)

The SPI slave VHDL code implements the input and output shift register. The SPI Slave module works with the SCLK input clock and SS input select signal.

When you interface this SPI slave VHDL module with your design components, you must take care of clock domain crossing (CDC) between the SCLK input serial clock of the SPI slave module and the internal clock of your VHDL design.

The signal that shall be used to resynchronize the two clock domains is the o_busy output signal. To safely cross clock domains, the o_busy signal should be synchronized using a two-stage flip-flop synchronizer before being used in the internal clock domain. This is a standard technique to avoid metastability issues when sampling an asynchronous signal.

The internal clock that we are going to use in the VHDL design that instantiates the SPI slave module should run faster than the SPI slave clock. Moreover, the SPI slave clock is not a continuous clock.

You can detect the falling edge of the o_busy signal (busy goes low when data is ready) to strobe the parallel data received by the SPI slave module.

You must pay attention to the parallel data to be sent. In this case, the output parallel shift register is loaded on the first SPI clock edge when the SS signal is low. The clock edge depends on the CPOL configuration. In this case, you must guarantee that the parallel data to be shifted is stable before the SS signal goes low.

Below is the VHDL code for the SPI slave module. In the SPI slave module, the i_mosi input serial data is clocked using the i_sclk SPI serial clock.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity spi_slave is
generic(
    N                     : integer := 2;      -- number of bit to serialize
    CPOL                  : std_logic := '0' );  -- clock polarity
 port (
    o_busy                      : out std_logic;  -- receiving data if '1'
    i_data_parallel             : in  std_logic_vector(N-1 downto 0);  -- data to sent
    o_data_parallel             : out std_logic_vector(N-1 downto 0);  -- received data
    i_sclk                      : in  std_logic;
    i_ss                        : in  std_logic;
    i_mosi                      : in  std_logic;
    o_miso                      : out std_logic);
end spi_slave;

architecture rtl of spi_slave is

signal r_shift_ena                           : std_logic;
signal r_tx_data                             : std_logic_vector(N-2 downto 0);  -- data to sent
signal r_rx_data                             : std_logic_vector(N-1 downto 0);  -- received data

begin
o_data_parallel  <= r_rx_data;
o_busy           <= r_shift_ena;

p_spi_slave_input : process(i_sclk)
begin
    if(i_sclk'event and i_sclk=CPOL) then -- CPOL='0' => falling edge; CPOL='1' => risinge edge
        if(i_ss='0') then
            r_rx_data              <= r_rx_data(N-2 downto 0)&i_mosi;
        end if;
    end if;
end process p_spi_slave_input;

p_spi_slave_output : process(i_sclk,i_ss)
begin
    if(i_ss='1') then
        r_shift_ena            <= '0';
    elsif(i_sclk'event and i_sclk= not CPOL) then -- CPOL='0' => falling edge; CPOL='1' => risinge edge
        r_shift_ena            <= '1';
        if(r_shift_ena='0') then
            o_miso                 <= i_data_parallel(N-1);
            r_tx_data              <= i_data_parallel(N-2 downto 0);
        else
            o_miso                 <= r_tx_data(N-2);
            r_tx_data              <= r_tx_data(N-3 downto 0)&'0';
        end if;
    end if;
end process p_spi_slave_output;

end rtl;

The module connected to the SPI slave can use the port signal o_busy to detect when the input frame has been received. It can be easily done just by detecting the falling edge of the signal. To detect the falling edge of busy you can refer to this post where you can find how to implement a good edge detector.

SPI Slave VHDL design

Get the complete VHDL code from this article.

SPI slave simulation results

Figure 4 SPI-Slave Modelsim simulation results

Figure 4 reports a simulation of the SPI slave module. As you can see, the output port โ€œo_data_parallelโ€ contains the parallel value of the received serial data. The simulation shows that โ€œo_data_parallelโ€ contains the MOSI serial data parallelized in the output register. Data is valid on the falling edge of the โ€œo_busyโ€ signal.

The first bit is the MSB (most significant bit).

The input port โ€œi_data_parallelโ€ can be used to send back to the SPI master data from the internal design. In the simulation, the test-bench parallelizes the MISO serial data into the โ€œr_miso_parallelโ€ test register to verify the correct behavior of the SPI-slave module.

You can easily customize the VHDL of the SPI slave to match your requirements. For instance, you can connect the SPI slave to an internal control logic that interprets the โ€œo_data_parallelโ€ value and performs read/write operations to an internal control register.

As you can understand, if you need to read out a value, you can decode a read command from the input data stream and, on the next command, you can send back the read value on the MISO serial line. In other words, the read value has one command delay latency. This is a common behavior in the SPI link.

SPI slave layout results

This section reports a layout of the SPI slave VHDL using Intel/Altera Quartus II on Terasic DE0 FPGA. The layout is implemented using N=4.

Figure 5 SPI-slave 4-bit layout example

In this example, the value of 4 is used only to better analyze the output design diagram in Figure 5.

The SPI protocol is driven by the serial clock โ€œi_sclkโ€. Even if the serial SPI I/O stream is synchronous with respect to the serial clock, when the signals i_sclk, i_ss, i_mosi are input to the FPGA, the internal logic must be considered asynchronous.

The o_miso signal is synchronous with respect to the i_sclk.

In this case, you must pay attention to the constraints related to such signals.

From the SPI slave VHDL code and from the figure reporting the related layout, it is clear that โ€œi_ssโ€ is used as asynchronous reset for the control signal that is used to enable the input and output shift register.

It is also used to drive the o_busy signal used to detect the completion of the SPI slave serial data burst. So, after the layout, especially if your device is congested in terms of FPGA area resources, you must control if the skew between input signals is acceptable considering the serial clock constraint.

This can be done using input constraints on the SPI slave input pin to specify the maximum delay from the FPGA pad to SPI internal registers. Another possible solution is to fix the SPI slave register location close to the FPGA pads to minimize the routing connection of this section.

Conclusion

In this post, we designed a 4-wire SPI slave VHDL module. A Modelsim simulation has been reported to verify the correct behavior of the SPI slave.

The last section describes an example of implementation on FPGA that pointed out the typical layout consideration to be taken into account when the SPI slave is implemented on an FPGA.

References

[1] https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus

[2] Download Intelยฎ Quartusยฎ Prime Software

[3] Altera DE0 Board

[4] VHDL Online Courses