1
votes

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.

  1. The byte is copied from the SPI register to a register.
  2. 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.

1
I found a solution which was to use 4 flags to signify when the 2 clocks are required to transfer some data, however I am still not sure as to whether this will give the expected results.FreelanceConsultant

1 Answers

0
votes

Some synthesis tools don't support multiple clocks in the same process. Fixing that would also point out that you are trying to load SPI_REG using both clocks.

As a suggestion, use a process for each clock domain, don't used shared variables, don't use flags. The only place you'll need to handshake across clock domains is for the SPI_BUFFER_OUT. SPI_BUFFER_IN is loaded by the system clock, _OUT by the SPI clock.

Beware of metastability caused by clock boundary crossing handshakes. Should only be to the system, which has the higher speed clock and/or operates asynchronously. Metastability can't be simulated without timed models. Such models usually just complain

With an asynchronous system interface you could use a single latch for the buffer either loaded from the system side or the SPI side. It would require a bit of input multiplexing and OR'ing two enables together.

And if your system clock and SPI clock were derived from a higher speed clock and had an integer relationship to each other you could use a clock enable and the higher speed clock.