An FPGA is a very universal device – if you need a specific digital serial interface, you simply build it on your own. Here is a SPI-interface, that transmits 16 bits, 8 bit for the address, another 8 bit for the data. It is very useful to configure devices like a SPI-ADC, DAC or other devices.

VHDL
-- 16-bit SPI-Transmitter
-- Christian Noeding, christian@noeding-online.de
-- https://chrisdevblog.com | https://github.com/xn--nding-jua
--
-- Released under GNU General Public License v3

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

entity spi_16bit_tx is
	port (
		clk			: in std_logic; -- something around 16 MHz
		i_address	: in std_logic_vector(7 downto 0);
		i_data		: in std_logic_vector(7 downto 0);
		i_start		: in std_logic;
		
		o_nCS		: out std_logic;
		o_cclk		: out std_logic;
		o_cdata		: out std_logic;
		o_busy		: out std_logic
	);
end entity;

architecture behavioral of spi_16bit_tx is
	type t_SM is (s_Idle, s_Tx, s_Cleanup);
	signal s_SM			: t_SM := s_Idle;
	
	signal startup		: std_logic := '1';
	signal count_clk	: natural range 0 to 1 := 0;
	signal nCS			  : std_logic := '0';
	signal cclk			  : std_logic := '0';
	signal cdata		  : std_logic := '0';
	signal tx_data		: std_logic_vector(15 downto 0);
	signal tx_counter	: natural range 0 to 16 := 0;
begin
	process (clk)
	begin
		if rising_edge(clk) then
			if (s_SM = s_Idle) then
				if (i_start = '1') then
					-- activate chip
					nCS <= '0';
					o_busy <= '1';
					startup <= '1';
					
					-- copy data to output-buffer
					tx_data(15 downto 8) <= i_address;
					tx_data(7 downto 0) <= i_data;
					tx_counter <= 0;

					-- go into transmission-state
					s_SM <= s_Tx;
				else
					nCS <= '1';
					cclk <= '0';
					cdata <= '0';
				end if;
				
			elsif (s_SM = s_Tx) then
				-- transmit at around 4 MHz
				if (count_clk = 1 or startup = '1') then -- divide clk by 4 (16 MHz / 4 = 4 MHz)
					if (cclk = '0' and startup = '0') then
						-- we have rising edge
						cclk <= '1';

						-- nothing to do here
					else
						-- we have falling edge
						cclk <= '0';
						startup <= '0';
						
						-- check if we have reached end of message
						if (tx_counter = 16) then
							tx_counter <= 0;
							s_SM <= s_Cleanup;
						else
							-- output bits until we reached end of message
							cdata <= tx_data(15 - tx_counter); -- send MSB first
							tx_counter <= tx_counter + 1;
						end if;
					end if;
					count_clk <= 0;
				else
					count_clk <= count_clk + 1;
				end if;
				
			elsif (s_SM = s_Cleanup) then
				nCS <= '1';
				cclk <= '0';
				cdata <= '0';
				o_busy <= '0';
				
				-- go back to idle-state
				s_SM <= s_Idle;
				
			end if;		
		end if;
	end process;
	
	o_nCS <= nCS;
	o_cclk <= cclk;
	o_cdata <= cdata;
end behavioral;

Some devices need another 8-bit for the SPI-map. Here is a slightly changed 24-bit SPI-transmitter:

VHDL
-- 24-bit SPI-Transmitter
-- Christian Noeding, christian@noeding-online.de
-- https://chrisdevblog.com | https://github.com/xn--nding-jua
--
-- Released under GNU General Public License v3

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

entity spi_tx is
	port (
		clk			: in std_logic; -- expecting 16 MHz
		i_address	: in std_logic_vector(7 downto 0);
		i_map			: in std_logic_vector(7 downto 0);
		i_data		: in std_logic_vector(7 downto 0);
		i_start		: in std_logic;
		
		o_nCS			: out std_logic;
		o_cclk		: out std_logic;
		o_cdata		: out std_logic;
		o_busy		: out std_logic
	);
end entity;

architecture behavioral of spi_tx is
	type t_SM is (s_Idle, s_Tx, s_Cleanup);
	signal s_SM			: t_SM := s_Idle;
	
	signal startup		: std_logic := '1';
	signal count_clk	: natural range 0 to 1 := 0;
	signal nCS			  : std_logic := '0';
	signal cclk			  : std_logic := '0';
	signal cdata		  : std_logic := '0';
	signal tx_data		: std_logic_vector(23 downto 0);
	signal tx_counter	: natural range 0 to 24 := 0;
begin
	process (clk)
	begin
		if rising_edge(clk) then
			if (s_SM = s_Idle) then
				if (i_start = '1') then
					-- activate chip
					nCS <= '0';
					o_busy <= '1';
					startup <= '1';
					
					-- copy data to output-buffer
					tx_data(23 downto 16) <= i_address;
					tx_data(15 downto 8) <= i_map;
					tx_data(7 downto 0) <= i_data;
					tx_counter <= 0;

					-- go into transmission-state
					s_SM <= s_Tx;
				else
					nCS <= '1';
					cclk <= '0';
					cdata <= '0';
				end if;
				
			elsif (s_SM = s_Tx) then
				-- transmit at around 4 MHz (CS42438 is able to handle SPI at max. 6 MHz)
				if (count_clk = 1 or startup = '1') then -- divide clk by 4 (16 MHz / 4 = 4 MHz)
					if (cclk = '0' and startup = '0') then
						-- we have rising edge
						cclk <= '1';

						-- nothing to do here
					else
						-- we have falling edge
						cclk <= '0';
						startup <= '0';
						
						-- check if we have reached end of message
						if (tx_counter = 24) then
							tx_counter <= 0;
							s_SM <= s_Cleanup;
						else
							-- output bits until we reached end of message
							cdata <= tx_data(23 - tx_counter); -- send MSB first
							tx_counter <= tx_counter + 1;
						end if;
					end if;
					count_clk <= 0;
				else
					count_clk <= count_clk + 1;
				end if;
				
			elsif (s_SM = s_Cleanup) then
				nCS <= '1';
				cclk <= '0';
				cdata <= '0';
				o_busy <= '0';
				
				-- go back to idle-state
				s_SM <= s_Idle;
				
			end if;		
		end if;
	end process;
	
	o_nCS <= nCS;
	o_cclk <= cclk;
	o_cdata <= cdata;
end behavioral;

Leave a comment

Your email address will not be published. Required fields are marked *