I am trying to implement something like an SPI interface in VHDL, to use with an FPGA.
My understanding of VHDL is limited, as I have only been using it for 2 days, and I think I haven't understood how the flow of data is controlled using clock edges.
My current understanding is that you can treat VHDL like a procedural programming language, as long as each instruction you wish to execute in your procedure is done in sequence, one instruction per clock cycle.
This probably sounds very strange, so this is what I have so far. There is a register called SPI_REG which contains 8 bits of data, which are clocked in and out by an external clock. (The master to slave SPI device clock. The FPGA is the slave device. The master is an Arduino, Raspberry Pi, mobile phone, etc.)
There is a serial in and serial out port, which connect to the master device. (MISO and MOSI data lines.)
There is a counter, which counts the number of clocks. It counts 8 clocks, from 0 up to 7, and then rolls over. This counter activates a flag when 8 bits of data have been clocked into the register by the master device.
The FPGA internal clock is running at a much higher speed (50 MHz?) than the SPI external clock. Every rising edge of this internal clock, we check to see if the flag is set, which means that 8 bits of data have been transferred, and the FPGA must copy this data out, do something with it and put some return data back in before the next SPI clock. Hence why the internal clock must be so much higher.
This process of "byte shuffling" must be done in stages.
- The byte is copied from the SPI register to a register.
- A return byte is copied to the SPI register from yet another register.
So there are 3 registers in total. A SPI register, a send register and a receive register.
In order to test the program, I decided to fill the send register with the contents of the receive register XOR'ed with zero byte. "00000000" (This doesn't "do" anything yet.)
I hope I explained that well enough, here is some code.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity interface is
port
(
-- SPI
SPI_MOSI: in std_logic;
SPI_MISO: out std_logic;
SPI_CLK: in std_logic;
-- FPGA clocks
SYSTEM_CLK: in std_logic;
);
end interface;
architecture Behavioral of interface is
-- SPI
signal SPI_REG: std_logic_vector(7 downto 0) := (others => '0');
signal SPI_BUFFER_OUT: std_logic_vector(7 downto 0) := (others => '0'); -- Holds most recent 8 bits transferred over SPI
signal SPI_BUFFER_IN: std_logic_vector(7 downto 0) := (others => '0'); -- Holds next 8 bits to be transferred over SPI
-- Shift register data
--DATA_IN: in std_logic_vector(7 downto 0) := (others => '0');
--DATA_OUT: out std_logic_vector(7 downto 0) := (others => '0');
begin
-- SPI interface
process(SYSTEM_CLK, SPI_CLK)--, AUDIO_CLK, BUSY)
-- SPI interface
variable spi_clk_count: integer := 0; -- count 8 data clocks
variable system_clk_count: integer := 0; -- count system clocks for moving data out and in to spi register
variable system_clk_flag: boolean := false;
variable send_data_count: integer := 0;
-- ADC control
variable read_data_clock_count: integer := 0; -- count clock pulses for timing the reading of the ADC
variable convst_flag: integer := 0; -- flag for the convst signal to the ADC
begin
-- SPI external clock
if rising_edge(SPI_CLK) then
-- Clock data out of serial_out (MISO or MOSI)
if system_clk_flag = false then
-- Complete transfers
SPI_MISO <= SPI_REG(7);
-- Clock data in spi_reg along 1 bit and feed in 1 bit from serial_in (MOSI or MISO)
SPI_REG <= SPI_REG(6 downto 0) & SPI_MOSI; -- Concatinate bits
end if;
-- Check for full 8 bits
if spi_clk_count = 7 then
spi_clk_count := 0;
-- Signal system clock to move byte out of and byte in to register
if system_clk_flag = false then
-- Set buffer overrun error flag
-- Signal data to be clocked out and clocked in anyway
system_clk_flag := true;
end if;
else
-- Increment spi clock counter
spi_clk_count := spi_clk_count + 1;
end if;
end if;
-- System internal clock
if rising_edge(SYSTEM_CLK) then
-- To test, return the XOR of the bits with zero (do nothing)
-- Continue shuffling data if required
if system_clk_count = 1 then
-- Increment system_clock_count
-- Change flag to "step 2"
system_clk_count := 2;
-- This is for debugging purposes
SPI_BUFFER_IN <= SPI_BUFFER_OUT xor "00000000";
elsif system_clk_count = 2 then
-- Increment system_clock_count
-- Change flag to "step 3"
system_clk_count := 3;
-- Move data from SPI_BUFFER_IN to register
SPI_REG <= SPI_BUFFER_IN;
elsif system_clk_count = 3 then
-- Signal that we are done
system_clk_flag := false;
-- Reset clock count
system_clk_count := 0;
-- Check for signal for system clock to move data to and from SPI register
elsif system_clk_flag = true then -- This must be done here because it is the lowest priority of the statements
-- Signal for byte shuffling, clock data in and out
system_clk_count := 1;
-- Move data from SPI register to SPI_BUFFER_OUT register
SPI_BUFFER_OUT <= SPI_REG;
end if;
end if;
end Behavioral;
I have a feeling that the way I have constructed this is completely wrong. I can draw a circuit diagram which would (I think) work, but I am having real trouble designing something using VHDL.
It has no syntax errors, but cannot by synthesized. Any help is greatly appreciated.