For the core SPI lets keep it brief, the SPI interface is basically just a shift register and it combines the transmitter and receiver. The clock comes from an external source so resynchronization needs to be done but I would argue that belongs outside of the SPI component; normally what I do is make a couple holding registers and use a single phase semaphore (t-flop).
It is better to use a uart if there is a reliable clock available, the SPI clock is asynchronous and unpredictable.
Here is my SPI block, it has been in several ASICs. There was once a schematic version. There are a couple signals to the external interface to trigger command interpretation or writing the data. Feel free to use it as GPL2
-- spi_block.vhd is the SPI communication interface
-- simple 8 bit interface.
-- CPOL=0, CPHA=1 (shift output on low edge, capture rising edge)
-- Note: first bit is high bit which is driven when CSB goes low.
-- ldcr=1 on load cycle, cyc_6=1 on interpret cycle before this
-- ld data will parallel load on ldcr=1 cycle.
ENTITY spi_block IS PORT (
OBUF: OUT byte;
CR : OUT byte;
ld : IN byte;
SCK : IN std_logic;
MOSI : IN std_logic;
CSB : IN std_logic;
MISO : OUT std_logic;
ldcr : OUT std_logic;
cyc_6 : OUT std_logic
); END spi_block;
architecture rtl of spi_block is
-- output mapping
signal CRi,mx : byte;
signal misoi,ldcri,cyc_6i : std_logic;
signal miso_d:std_logic; -- init by POR
-- internal signals
signal ct: std_logic_vector(2 downto 0); -- 8 count
signal sh_ldn : std_logic;
MISO <= misoi after 2 ns;
ldcr <= ldcri;
cyc_6 <= cyc_6i;
CR <= CRi after 2 ns;
CRi(0) <= MOSI;
CRi(7 downto 1) <= mx(6 downto 0);
ldcri <= \'1\' when ct = \"111\" else \'0\';
cyc_6i <= \'1\' when ct = \"110\" else \'0\';
sh_ldn <= \'0\' when ct = \"000\" else \'1\';
miso_d<=ld(7) when sh_ldn=\'0\' else mx(7); -- mux for miso data at start
-- Count the bits output on falling edge, parallel load when =\"111\"
if (CSB=\'1\') then
ct <= (others=>\'0\') ;
elsif (SCK\'event and SCK=\'0\') then
ct <= std_logic_vector(signed(ct) + 1) ;
-- use a latch for MISO to provide setup and hold for the master.
-- make sure this synthesizes a latch
begin -- MISO latch
if SCK = \'0\' then
-- IO is a parallel load shift register.
-- this will be done by gated register.
if (SCK\'event and SCK=\'1\') then -- load and shift on + edge
if CSB=\'0\' then
mx(0) <= mosi;
if sh_ldn=\'0\' then -- load at first clock
mx(7 downto 1) <= ld(6 downto 0);
mx(7 downto 1) <= mx(6 downto 0);