VHDL Code for SPI Master: Syntax, Example, and Tips
A basic
SPI master in VHDL controls the clock, data out (MOSI), and chip select signals to communicate with SPI slaves. The code includes a clock divider, state machine for sending bits, and output signals SCLK, MOSI, and CS. This design sends one byte per transaction and can be customized for your SPI device.Syntax
The SPI master in VHDL typically includes these parts:
- Inputs: system clock, reset, data to send, start signal
- Outputs: SPI clock (SCLK), Master Out Slave In (MOSI), chip select (CS), and a done flag
- Clock divider: to generate SPI clock from system clock
- State machine: controls sending bits and toggling signals
Below is the basic syntax pattern for an SPI master process.
vhdl
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity spi_master is
Port (
clk : in std_logic; -- system clock
rst : in std_logic; -- reset
start : in std_logic; -- start transmission
data_in : in std_logic_vector(7 downto 0); -- byte to send
sclk : out std_logic; -- SPI clock
mosi : out std_logic; -- Master Out Slave In
cs : out std_logic; -- Chip Select (active low)
done : out std_logic -- transmission done
);
end spi_master;
architecture Behavioral of spi_master is
-- internal signals
signal clk_div_cnt : integer range 0 to 1 := 0;
signal spi_clk : std_logic := '0';
signal bit_cnt : integer range 0 to 7 := 0;
signal shift_reg : std_logic_vector(7 downto 0) := (others => '0');
type state_type is (idle, transfer, finish);
signal state : state_type := idle;
begin
-- Clock divider process to generate SPI clock (half speed)
clk_divider : process(clk, rst) begin
if rst = '1' then
clk_div_cnt <= 0;
spi_clk <= '0';
elsif rising_edge(clk) then
clk_div_cnt <= clk_div_cnt + 1;
if clk_div_cnt = 1 then
spi_clk <= not spi_clk;
clk_div_cnt <= 0;
end if;
end if;
end process;
-- SPI master state machine
spi_process : process(clk, rst) begin
if rst = '1' then
state <= idle;
cs <= '1';
mosi <= '0';
done <= '0';
bit_cnt <= 0;
shift_reg <= (others => '0');
elsif rising_edge(clk) then
case state is
when idle =>
done <= '0';
cs <= '1';
mosi <= '0';
if start = '1' then
cs <= '0';
shift_reg <= data_in;
bit_cnt <= 7;
state <= transfer;
end if;
when transfer =>
if spi_clk = '0' then -- data changes on falling edge
mosi <= shift_reg(bit_cnt);
elsif spi_clk = '1' then -- data sampled on rising edge
if bit_cnt = 0 then
state <= finish;
else
bit_cnt <= bit_cnt - 1;
end if;
end if;
when finish =>
cs <= '1';
done <= '1';
state <= idle;
when others =>
state <= idle;
end case;
end if;
end process;
sclk <= spi_clk;
end Behavioral;Example
This example shows a simple SPI master that sends one byte when start is high. It generates the SPI clock at half the system clock speed and outputs the data bit by bit on MOSI. The CS signal goes low during transmission and high when done. The done signal indicates the end of sending.
vhdl
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity spi_master is
Port (
clk : in std_logic;
rst : in std_logic;
start : in std_logic;
data_in : in std_logic_vector(7 downto 0);
sclk : out std_logic;
mosi : out std_logic;
cs : out std_logic;
done : out std_logic
);
end spi_master;
architecture Behavioral of spi_master is
signal clk_div_cnt : integer range 0 to 1 := 0;
signal spi_clk : std_logic := '0';
signal bit_cnt : integer range 0 to 7 := 0;
signal shift_reg : std_logic_vector(7 downto 0) := (others => '0');
type state_type is (idle, transfer, finish);
signal state : state_type := idle;
begin
clk_divider : process(clk, rst) begin
if rst = '1' then
clk_div_cnt <= 0;
spi_clk <= '0';
elsif rising_edge(clk) then
clk_div_cnt <= clk_div_cnt + 1;
if clk_div_cnt = 1 then
spi_clk <= not spi_clk;
clk_div_cnt <= 0;
end if;
end if;
end process;
spi_process : process(clk, rst) begin
if rst = '1' then
state <= idle;
cs <= '1';
mosi <= '0';
done <= '0';
bit_cnt <= 0;
shift_reg <= (others => '0');
elsif rising_edge(clk) then
case state is
when idle =>
done <= '0';
cs <= '1';
mosi <= '0';
if start = '1' then
cs <= '0';
shift_reg <= data_in;
bit_cnt <= 7;
state <= transfer;
end if;
when transfer =>
if spi_clk = '0' then
mosi <= shift_reg(bit_cnt);
elsif spi_clk = '1' then
if bit_cnt = 0 then
state <= finish;
else
bit_cnt <= bit_cnt - 1;
end if;
end if;
when finish =>
cs <= '1';
done <= '1';
state <= idle;
when others =>
state <= idle;
end case;
end if;
end process;
sclk <= spi_clk;
end Behavioral;Output
No console output; hardware signals change as follows:
- CS goes low when start is high
- MOSI outputs bits of data_in one by one on SPI clock edges
- SCLK toggles at half system clock speed
- DONE goes high after all bits sent
Common Pitfalls
Common mistakes when writing SPI master in VHDL include:
- Not properly dividing the system clock to generate the SPI clock, causing timing errors.
- Changing data on the wrong clock edge, leading to incorrect data sampling by the slave.
- Forgetting to control the chip select (
CS) signal, which must be low during transmission. - Not resetting internal counters and state machines correctly on reset.
Always verify clock polarity and phase match your SPI slave device requirements.
vhdl
-- wrong approach: -- Changing MOSI on rising edge instead of falling edge if spi_clk = '1' then mosi <= shift_reg(bit_cnt); end if; -- correct approach: if spi_clk = '0' then mosi <= shift_reg(bit_cnt); end if;
Quick Reference
SPI Master Signals:
SCLK: SPI clock output, usually slower than system clockMOSI: Master Out Slave In data lineCS: Chip Select, active low to enable slavestart: Input signal to begin transmissiondone: Output signal indicating transmission complete
Tips:
- Use a clock divider to generate SPI clock from system clock
- Use a state machine to control data shifting and CS signal
- Match clock polarity and phase to your SPI slave device
- Reset all signals and counters properly
Key Takeaways
Use a clock divider to generate the SPI clock from the system clock.
Control the chip select (CS) signal to enable the slave only during transmission.
Shift data out on the correct SPI clock edge to match slave timing requirements.
Implement a state machine to manage transmission states cleanly.
Always reset internal signals and counters to avoid unexpected behavior.