--------------------------------------------------------------------------------
--
--   FileName:         spi_master.vhd
--   Dependencies:     none
--   Design Software:  Quartus II Version 9.0 Build 132 SJ Full Version
--
--   HDL CODE IS PROVIDED "AS IS."  DIGI-KEY EXPRESSLY DISCLAIMS ANY
--   WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT NOT
--   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
--   PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL DIGI-KEY
--   BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL
--   DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR EQUIPMENT, COST OF
--   PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS
--   BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF),
--   ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER SIMILAR COSTS.
--
--   Version History
--   Version 1.0 7/23/2010 Scott Larson
--     Initial Public Release
--   Version 1.1 4/11/2013 Scott Larson
--     Corrected ModelSim simulation error (explicitly reset clk_toggles signal)
--    
--------------------------------------------------------------------------------

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;

ENTITY spi_master IS
  GENERIC(
    slaves               : INTEGER := 1;  --number of spi slaves
    d_width              : INTEGER := 16); --data bus width
  PORT(
    clock                : IN     STD_LOGIC;                             --system clock
    reset_n              : IN     STD_LOGIC;                             --asynchronous reset
    enable               : IN     STD_LOGIC;                             --initiate transaction
    cpol                 : IN     STD_LOGIC;                             --spi clock polarity
    cpha                 : IN     STD_LOGIC;                             --spi clock phase
    cont                 : IN     STD_LOGIC;                             --continuous mode command
    clk_div              : IN     INTEGER;                               --system clock cycles per 1/2 period of sclk
    addr                 : IN     INTEGER;                               --address of slave
    tx_data              : IN     STD_LOGIC_VECTOR(d_width-1 DOWNTO 0);  --data to transmit
    miso                 : IN     STD_LOGIC;                             --master in, slave out
    sclk                 : OUT    STD_LOGIC;                             --spi clock
    ss_n                 : OUT    STD_LOGIC_VECTOR(slaves-1 DOWNTO 0);   --slave select
    mosi                 : OUT    STD_LOGIC;                             --master out, slave in
    busy                 : OUT    STD_LOGIC;                             --busy / data ready signal
    rx_data              : OUT    STD_LOGIC_VECTOR(d_width-1 DOWNTO 0)); --data received
END spi_master;

ARCHITECTURE logic OF spi_master IS
  TYPE machine IS(ready, execute);                           --state machine data type
  SIGNAL state           : machine;                              --current state
  SIGNAL slave           : INTEGER;                              --slave selected for current transaction
  SIGNAL clk_ratio       : INTEGER;                              --current clk_div
  SIGNAL count           : INTEGER;                              --counter to trigger sclk from system clock
  SIGNAL clk_toggles     : INTEGER RANGE 0 TO d_width*2 + 1;     --count spi clock toggles
  SIGNAL assert_data     : STD_LOGIC;                            --'1' is tx sclk toggle, '0' is rx sclk toggle
  SIGNAL continue        : STD_LOGIC;                            --flag to continue transaction
  SIGNAL rx_buffer       : STD_LOGIC_VECTOR(d_width-1 DOWNTO 0); --receive data buffer
  SIGNAL tx_buffer       : STD_LOGIC_VECTOR(d_width-1 DOWNTO 0); --transmit data buffer
  SIGNAL last_bit_rx     : INTEGER RANGE 0 TO d_width*2;         --last rx data bit location
  
  signal ss_n_for_read   : STD_LOGIC_VECTOR(slaves-1 DOWNTO 0); 
  signal sclk_for_read   : std_logic;
BEGIN
  PROCESS(clock, reset_n)
  BEGIN

    IF(reset_n = '0') THEN                      --reset system
      busy <= '1';                              --set busy signal
      ss_n <= (OTHERS => '1');                  --deassert all slave select lines
      ss_n_for_read <= (OTHERS => '1');         --Kopie ss_n pro cteni, aby jsme nemeli buffer
      mosi <= 'Z';                              --set master out to high impedance
      rx_data <= (OTHERS => '0');               --clear receive data port
      state <= ready;                           --go to ready state when reset is exited

    ELSIF(clock'EVENT AND clock = '1') THEN
      CASE state IS                             --state machine

        WHEN ready =>
          busy             <= '0';              --clock out not busy signal
          ss_n             <= (OTHERS => '1');  --set all slave select outputs high
          ss_n_for_read    <= (OTHERS => '1');
          mosi             <= 'Z';              --set mosi output high impedance
          continue         <= '0';              --clear continue flag

          --user input to initiate transaction
          IF(enable = '1') THEN       
          
            --Nastavit spravny slave
            busy           <= '1';              --set busy signal
            IF(addr < slaves) THEN              --check for valid slave address
              slave        <= addr;             --clock in current slave selection if valid
            ELSE
              slave        <= 0;                --set to first slave if not valid
            END IF;
            
            --Nastavit rychlost
            IF(clk_div = 0) THEN                --check for valid spi speed
              clk_ratio    <= 1;                --set to maximum speed if zero
              count        <= 1;                --initiate system-to-spi clock counter
            ELSE  
              clk_ratio    <= clk_div;          --set to input selection if valid
              count        <= clk_div;          --initiate system-to-spi clock counter
            END IF;
            
            sclk           <= cpol;             --Nastavit idle stav clocku
            sclk_for_read  <= cpol;
            assert_data    <= NOT cpha;         --set spi clock phase
            tx_buffer      <= tx_data;          --clock in data for transmit into buffer
            clk_toggles    <= 0;                --initiate clock toggle counter
            last_bit_rx    <= d_width*2 + conv_integer(cpha) - 1; --set last rx data bit
            state          <= execute;          --proceed to execute state
          ELSE
            state <= ready;                     --remain in ready state
          END IF;

        WHEN execute =>
          busy <= '1';                          --set busy signal
          ss_n(slave) <= '0';                   --set proper slave select output
          ss_n_for_read(slave) <= '0';
          
          --system clock to sclk ratio is met
          IF(count = clk_ratio) THEN        
                count <= 1;                         --reset system-to-spi clock counter
                assert_data <= NOT assert_data;     --switch transmit/receive indicator
                
                --Nulovani a navysovani pocitadla nabeznych hran clocku za transakci
                IF(clk_toggles = d_width*2 + 1) THEN
                  clk_toggles <= 0;                 --reset spi clock toggles counter
                ELSE
                  clk_toggles <= clk_toggles + 1;   --increment spi clock toggles counter
                END IF;
                
                --spi clock toggle needed
                --zmena clocku kdyz je counter plny a slaveSelect je v nule - aby clock nejel kdyz se nic nedeje
                IF(clk_toggles <= d_width*2 AND ss_n_for_read(slave) = '0') THEN 
                  sclk <= NOT sclk_for_read; --toggle spi clock
                  sclk_for_read <= NOT sclk_for_read;
                END IF;
                
                --receive spi clock toggle
                IF(assert_data = '0' AND clk_toggles < last_bit_rx + 1 AND ss_n_for_read(slave) = '0') THEN 
                  rx_buffer <= rx_buffer(d_width-2 DOWNTO 0) & miso; --shift in received bit
                END IF;
                  
                --transmit spi clock toggle
                IF(assert_data = '1' AND clk_toggles < last_bit_rx) THEN 
                  mosi <= tx_buffer(d_width-1);                     --clock out data bit
                  tx_buffer <= tx_buffer(d_width-2 DOWNTO 0) & '0'; --shift data transmit buffer
                END IF;
                
                --last data receive, but continue
                IF(clk_toggles = last_bit_rx AND cont = '1') THEN 
                  tx_buffer <= tx_data;                       --reload transmit buffer
                  clk_toggles <= last_bit_rx - d_width*2 + 1; --reset spi clock toggle counter
                  continue <= '1';                            --set continue flag
                END IF;
                
                --normal end of transaction, but continue
                IF(continue = '1') THEN  
                  continue <= '0';                  --clear continue flag
                  busy <= '0';                      --clock out signal that first receive data is ready
                  rx_data <= rx_buffer;             --clock out received data to output port    
                END IF;
                
                --end of transaction
                IF((clk_toggles = d_width*2 + 1) AND cont = '0') THEN   
                  busy <= '0';                      --clock out not busy signal
                  ss_n <= (OTHERS => '1');          --set all slave selects high
                  ss_n_for_read <= (OTHERS => '1'); 
                  mosi <= 'Z';                      --set mosi output high impedance
                  rx_data <= rx_buffer;             --clock out received data to output port
                  state <= ready;                   --return to ready state
                ELSE                                --not end of transaction
                  state <= execute;                 --remain in execute state
                END IF;
          
          ELSE        --system clock to sclk ratio not met
            count <= count + 1; --increment counter
            state <= execute;   --remain in execute state
          END IF;

      END CASE;
    END IF;
  END PROCESS; 
END logic;
