--------------------------------------------------------------------------------
--
--   FileName:         spi_3_wire_master.vhd
--   Dependencies:     none
--   Design Software:  Quartus II 64-Bit Version 12.1 Build 177 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 10/30/2013 Scott Larson
--     Initial Public Release
--    
--------------------------------------------------------------------------------

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;

ENTITY spi_3_wire_master IS
  GENERIC(
    slaves    : INTEGER := 1;  --number of spi slaves
    cmd_width : INTEGER := 8;  --command bus width
    d_width   : INTEGER := 8); --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
    clk_div : IN     INTEGER;                                --system clock cycles per 1/2 period of sclk
    addr    : IN     INTEGER;                                --address of slave
    rw      : IN     STD_LOGIC;                              --'0' for read, '1' for write
    tx_cmd  : IN     STD_LOGIC_VECTOR(cmd_width-1 DOWNTO 0); --command to transmit
    tx_data : IN     STD_LOGIC_VECTOR(d_width-1 DOWNTO 0);   --data to transmit
    sclk    : BUFFER STD_LOGIC;                              --spi clock
    ss_n    : BUFFER STD_LOGIC_VECTOR(slaves-1 DOWNTO 0);    --slave select
    sdio    : INOUT  STD_LOGIC;                              --serial data input output
    busy    : OUT    STD_LOGIC;                              --busy / data ready signal
    rx_data : OUT    STD_LOGIC_VECTOR(d_width-1 DOWNTO 0));  --data received
	 END spi_3_wire_master;



ARCHITECTURE logic OF spi_3_wire_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 (cmd_width+d_width)*2 + 2; --count spi clock toggles
  SIGNAL assert_data : STD_LOGIC;                                    --'1' is tx sclk toggle, '0' is rx sclk toggle
  SIGNAL rw_buffer   : STD_LOGIC;                                    --read/write buffer
  SIGNAL cmd_buffer  : STD_LOGIC_VECTOR(cmd_width-1 DOWNTO 0);       --command buffer
  SIGNAL d_buffer    : STD_LOGIC_VECTOR(d_width-1 DOWNTO 0);         --data buffer
  SIGNAL last_bit_rx : INTEGER RANGE 0 TO (cmd_width+d_width)*2;     --last rx data bit location

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
      sdio <= 'Z';                --set master data io 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
          sdio <= 'Z';              --set master data io to high impedance

          --user input to initiate transaction
          IF(enable = '1') THEN       
            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;
				
            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;            --set spi clock polarity
            assert_data <= NOT cpha; --set spi clock phase
            rw_buffer <= rw;         --clock in read/write instruction
				
            cmd_buffer <= tx_cmd;    --clock in command for transmit into buffer
            d_buffer <= tx_data;     --clock in data for transmit into buffer
				
            clk_toggles <= 0;        --initiate clock toggle counter
            last_bit_rx <= (cmd_width+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
          
          --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
            clk_toggles <= clk_toggles + 1;  --increment spi clock toggles counter
            
            --spi clock toggle needed
            IF(clk_toggles < (cmd_width+d_width)*2 + 1 AND ss_n(slave) = '0') THEN 
              sclk <= NOT sclk;  --toggle spi clock
            END IF;
            
															--transmit spi clock toggle
            IF(assert_data = '1' AND clk_toggles < last_bit_rx - d_width*2) THEN  --command part of transaction
              sdio <= cmd_buffer(cmd_width-1);                                    --clock out command bit
              cmd_buffer <= cmd_buffer(cmd_width-2 DOWNTO 0) & '0';               --shift command transmit buffer
            END IF;
				
            IF(assert_data = '1' AND rw_buffer = '1' AND clk_toggles > last_bit_rx - d_width*2) THEN  --write command and data part of transaction
              sdio <= d_buffer(d_width-1);                                                            --clock out data bit
              d_buffer <= d_buffer(d_width-2 DOWNTO 0) & '0';                                         --shift data transmit buffer
            END IF;
				
            IF(assert_data = '1' AND rw_buffer = '0' AND clk_toggles > last_bit_rx - d_width*2) THEN  --read command and data part of transaction
              sdio <= 'Z';                                                                            --set serial data line to high impedance
            END IF;
        
            --receive spi clock toggle
            IF(assert_data = '0' AND clk_toggles < last_bit_rx + 1) THEN 
              IF(rw_buffer = '0' AND clk_toggles > last_bit_rx - d_width*2) THEN --read transaction and data part of transaction
                d_buffer <= d_buffer(d_width-2 DOWNTO 0) & sdio;                 --shift in received bit
              END IF;
            END IF;
            
            --end of transaction
            IF(clk_toggles = (cmd_width+d_width)*2 + 1) THEN  
              busy <= '0';              --clock out not busy signal
              ss_n <= (OTHERS => '1');  --set all slave selects high
              sdio <= 'Z';              --set master data io to high impedance
              IF(rw_buffer = '0') THEN  --if transaction was a read
                rx_data <= d_buffer;    --clock out received data to output port
              END IF;
                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;
