URL
https://opencores.org/ocsvn/timerocd/timerocd/trunk
Subversion Repositories timerocd
[/] [timerocd/] [trunk/] [src/] [spi_slave.vhd] - Rev 2
Compare with Previous | Blame | View Log
-------------------------------------------------------------------------------- -- File name : spi_slave.vhd -------------------------------------------------------------------------------- -- Copyright (C) 2015 Donna Whisnant/Dewtronics. -- Contact: http://www.dewtronics.com/ -- -- This file may be used under the terms of the GNU Lesser General Public License -- version 3.0 as published by the Free Software Foundation and appearing -- in the files lgpl-3.0.txt/gpl-3.0.txt included in the packaging of this file. -- Please review the following information to ensure the GNU Lesser General -- Public License version 3.0 requirements will be met: -- https://www.gnu.org/licenses/lgpl-3.0.html -- Attribution requested, but not required. -- -- Target Device: Xilinx Spartan-6 XC6SLX9-2-TQG144 -- Using Numato Mimas Spartan 6 FPGA Development Board -- http://numato.com/mimas-spartan-6-fpga-development-board.html -- -- -- SPI Slave Entity for TimerOCD -- -------------------------------------------------------------------------------- library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_arith.all; use IEEE.std_logic_unsigned.all; library UNISIM; use UNISIM.VCOMPONENTS.all; entity spi_slave is generic ( cpol : STD_LOGIC := '0'; --spi clock polarity mode cpha : STD_LOGIC := '0'; --spi clock phase mode d_width : INTEGER := 8 --data width in bits ); port ( sync_clk : in STD_LOGIC; --system clock in for synchronization of the SS signal sclk : in STD_LOGIC; --spi clk from master reset_n : in STD_LOGIC; --active low reset ss_n : in STD_LOGIC; --active low slave select mosi : in STD_LOGIC; --master out, slave in rrdy : out STD_LOGIC := '0'; --receive ready bit rx_data : out STD_LOGIC_VECTOR(d_width-1 downto 0) := (OTHERS => '0'); --receive register output to logic busy : out STD_LOGIC := '0'; --busy signal to logic ('1' during transaction) miso : out STD_LOGIC := 'Z'; --master in, slave out tx_load_data : in STD_LOGIC_VECTOR(d_width-1 downto 0); --Tx data to load (synchronous with end of address receive) addr : out STD_LOGIC_VECTOR(7 downto 0) := (OTHERS => '0'); -- Address to Read or Write (as received from SPI) addr_latch : out STD_LOGIC := '0'; -- Address Latch Enable Output (Set to True when the address is ready on the output and data should be supplied) cmd_load_data : in STD_LOGIC_VECTOR(3 downto 0) := (OTHERS => '0') -- Data nybble to shift out during low nybble of command byte receive ); end spi_slave; architecture logic of spi_slave is signal mode : STD_LOGIC; --groups modes by clock polarity relation to data signal clk : STD_LOGIC; --clock constant nRSB : integer := 4; -- Number of RxState Bits (used for conversions) signal rx_state : STD_LOGIC_VECTOR(nRSB-1 downto 0); -- Receive state-machine signal rxbit_cnt : INTEGER range 0 to d_width-1; -- Bit count for data received constant nTSB : integer := 3; -- Number of TxState Bits (used for conversions) signal tx_state : STD_LOGIC_VECTOR(nTSB-1 downto 0); -- Transmit state-machine constant nTBCSB : integer := 3; -- Transmit Bit Count State Bits (used for conversions) signal txbit_flg : STD_LOGIC_VECTOR(nTBCSB-1 downto 0); -- Bit count for data sent signal wr_add : STD_LOGIC := '1'; -- address of register to write ('0' = receive, '1' = status) signal cmd_add : STD_LOGIC := '0'; -- command or data ('0' = data, '1' = command) signal rx_buf : STD_LOGIC_VECTOR(d_width-2 downto 0) := (OTHERS => '0'); --receiver buffer signal tx_buf : STD_LOGIC_VECTOR(d_width-1 downto 0) := (OTHERS => '0'); -- transmit buffer signal cmd_buf : STD_LOGIC_VECTOR(2 downto 0) := (OTHERS => '0'); -- command buffer signal out_data : STD_LOGIC; -- Output data gated to miso signal syn_ss_n : STD_LOGIC; -- Synchronized SS_N signal signal syn_mosi : STD_LOGIC; -- MOSI synchronized to sync_clk signal syn_clk_fall : STD_LOGIC; -- CLK falling edge synchronized to sync_clk signal syn_clk_rise : STD_LOGIC; -- CLK rising edge synchronized to sync_clk signal resync_clk : STD_LOGIC_VECTOR(3 downto 0); signal resync_ss : STD_LOGIC_VECTOR(1 downto 0); signal resync_mosi : STD_LOGIC_VECTOR(1 downto 0); signal rrdy_int : STD_LOGIC; -- Internal rrdy. Set in the Rx State-Machine. Used to trigger a delayed rrdy pulse back to the main logic to get data a setup/hold time signal rrdy_out : STD_LOGIC; -- Output rrdy (used to eliminate buffer type) signal addr_latch_int : STD_LOGIC; -- Internal addr_latch. Set in the Rx State-Machine. Used to trigger a delayed addr_latch pulse back to the main logic to get data a setup/hold time signal addr_latch_out : STD_LOGIC; -- Output addr_latch (used to eliminate buffer type) begin --adjust clock so writes are on rising edge and reads on falling edge mode <= cpol XOR cpha; --'1' for modes that write on rising edge with mode select clk <= sclk when '1', NOT sclk when OTHERS; -- Input synchronization: sync_clk_proc:process(sync_clk) begin if (rising_edge(sync_clk)) then syn_ss_n <= resync_ss(0); syn_mosi <= resync_mosi(0); resync_clk <= resync_clk(2 downto 0) & clk; resync_ss <= resync_ss(0) & ss_n; resync_mosi <= resync_mosi(0) & mosi; end if; end process sync_clk_proc; syn_clk_fall <= resync_clk(3) and not resync_clk(2); -- '1' when going from 1 -> 0 syn_clk_rise <= not resync_clk(2) and resync_clk(1); -- '1' when going from 0 -> 1 -- output drive: with (syn_ss_n OR (NOT reset_n)) select miso <= 'Z' when '1', out_data when OTHERS; busy <= NOT syn_ss_n; --high during transactions -- rrdy synchronization: -- Since rxspiproc is triggered on the rising_edge of -- sync_clk, this process delays reporting it -- by 1/2 clock cycle, giving the data time to -- get stored: rrdyproc:process(syn_ss_n, sync_clk, reset_n) begin if ((syn_ss_n = '1') OR (reset_n = '0')) then rrdy_out <= '0'; elsif (falling_edge(sync_clk)) then if (rrdy_out = '0') then rrdy_out <= rrdy_int; end if; end if; end process rrdyproc; rrdy <= rrdy_out; -- addr_latch synchronization: -- Since rxspiproc is triggered on the rising_edge of -- sync clk, this process delays reporting it -- by 1/2 clock cycle, giving the data time to -- get stored: addrlatchproc:process(syn_ss_n, sync_clk, reset_n) begin if ((syn_ss_n = '1') OR (reset_n = '0')) then addr_latch_out <= '0'; elsif (falling_edge(sync_clk)) then if (addr_latch_out = '0') then addr_latch_out <= addr_latch_int; end if; end if; end process addrlatchproc; addr_latch <= addr_latch_out; -- rxspiproc: Receives incoming data rxspiproc:process(syn_ss_n, syn_clk_fall, reset_n) begin if ((syn_ss_n = '1') OR (reset_n = '0')) then rx_state <= (OTHERS => '0'); rx_buf <= (OTHERS => '0'); rxbit_cnt <= d_width-1; rrdy_int <= '0'; addr_latch_int <= '0'; elsif (falling_edge(syn_clk_fall)) then case CONV_INTEGER(rx_state) is when 0 => --read/write mode ('0' for write, '1' for read) wr_add <= syn_mosi; addr(7) <= syn_mosi; rx_state <= CONV_STD_LOGIC_VECTOR(1, nRSB); when 1 => --cmd/data mode ('0' for data, '1' for cmd) cmd_add <= syn_mosi; addr(6) <= syn_mosi; rx_state <= CONV_STD_LOGIC_VECTOR(3, nRSB); when 3 => addr(5) <= syn_mosi; rx_state <= CONV_STD_LOGIC_VECTOR(2, nRSB); when 2 => addr(4) <= syn_mosi; rx_state <= CONV_STD_LOGIC_VECTOR(6, nRSB); when 6 => addr(3) <= syn_mosi; rx_state <= CONV_STD_LOGIC_VECTOR(7, nRSB); when 7 => addr(2) <= syn_mosi; rx_state <= CONV_STD_LOGIC_VECTOR(5, nRSB); when 5 => addr(1) <= syn_mosi; rx_state <= CONV_STD_LOGIC_VECTOR(4, nRSB); when 4 => addr(0) <= syn_mosi; if (cmd_add = '1') then rx_state <= CONV_STD_LOGIC_VECTOR(13, nRSB); if (wr_add = '0') then rrdy_int <= '1'; end if; else addr_latch_int <= '1'; rx_state <= CONV_STD_LOGIC_VECTOR(12, nRSB); end if; when 12 => if (rxbit_cnt = 0) then if (wr_add = '0') then rx_data <= rx_buf & syn_mosi; rrdy_int <= '1'; end if; rx_state <= CONV_STD_LOGIC_VECTOR(13, nRSB); else if (wr_add = '0') then rx_buf <= rx_buf(rx_buf'HIGH-1 downto 0) & syn_mosi; end if; rxbit_cnt <= (rxbit_cnt - 1); -- Stay in this state until all bits are received end if; when 13 => -- Stay in this state. The SS_n signal will release us when OTHERS => end case; end if; end process rxspiproc; -- txspiproc: Transmits outgoing data -- Note: Commented out code for cmd_buf/cmd_load_data and -- tx_buf/tx_load_data can be used to determine the point -- where data must be ready/available from the main TimerOCD -- relative to the SPI traffic. This allows for faster SPI -- clock rates by only requiring the first couple of bits and/or -- first byte of data to be valid when the SPI clock begins -- shifting it out. Since several memory read operations are -- required to obtain the data needed to be sent, this allows -- for several SPI clock cycle times before that data reading -- must be completed. txspiproc:process(syn_ss_n, syn_clk_rise, reset_n) begin if ((syn_ss_n = '1') OR (reset_n = '0')) then tx_state <= (OTHERS => '0'); tx_buf <= (OTHERS => '0'); txbit_flg <= (OTHERS => '0'); out_data <= '0'; elsif (rising_edge(syn_clk_rise)) then case CONV_INTEGER(tx_state) is when 0 => tx_state <= CONV_STD_LOGIC_VECTOR(1, nTSB); when 1 => tx_state <= CONV_STD_LOGIC_VECTOR(3, nTSB); when 3 => tx_state <= CONV_STD_LOGIC_VECTOR(2, nTSB); when 2 => out_data <= cmd_load_data(3); -- cmd_buf <= cmd_load_data(2 downto 0); tx_state <= CONV_STD_LOGIC_VECTOR(6, nTSB); when 6 => -- out_data <= cmd_buf(2); out_data <= cmd_load_data(2); cmd_buf <= cmd_load_data(2 downto 0); tx_state <= CONV_STD_LOGIC_VECTOR(7, nTSB); when 7 => out_data <= cmd_buf(1); tx_state <= CONV_STD_LOGIC_VECTOR(5, nTSB); when 5 => out_data <= cmd_buf(0); tx_state <= CONV_STD_LOGIC_VECTOR(4, nTSB); when 4 => if (cmd_add='0') then case CONV_INTEGER(txbit_flg) is when 0 => out_data <= tx_load_data(d_width-1); -- tx_buf <= tx_load_data(d_width-2 downto 0) & "0"; txbit_flg <= CONV_STD_LOGIC_VECTOR(1, nTBCSB); when 1 => out_data <= tx_load_data(d_width-2); -- tx_buf <= tx_load_data(d_width-3 downto 0) & "00"; txbit_flg <= CONV_STD_LOGIC_VECTOR(3, nTBCSB); when 3 => out_data <= tx_load_data(d_width-3); -- tx_buf <= tx_load_data(d_width-4 downto 0) & "000"; txbit_flg <= CONV_STD_LOGIC_VECTOR(2, nTBCSB); when 2 => out_data <= tx_load_data(d_width-4); -- tx_buf <= tx_load_data(d_width-5 downto 0) & "0000"; txbit_flg <= CONV_STD_LOGIC_VECTOR(6, nTBCSB); when 6 => out_data <= tx_load_data(d_width-5); -- tx_buf <= tx_load_data(d_width-6 downto 0) & "00000"; txbit_flg <= CONV_STD_LOGIC_VECTOR(7, nTBCSB); when 7 => out_data <= tx_load_data(d_width-6); -- tx_buf <= tx_load_data(d_width-7 downto 0) & "000000"; txbit_flg <= CONV_STD_LOGIC_VECTOR(5, nTBCSB); when 5 => out_data <= tx_load_data(d_width-7); tx_buf <= tx_load_data(d_width-8 downto 0) & "0000000"; txbit_flg <= CONV_STD_LOGIC_VECTOR(4, nTBCSB); when 4 => out_data <= tx_buf(tx_buf'HIGH); tx_buf <= tx_buf(tx_buf'HIGH-1 downto 0) & "0"; -- Stay here and shift out remaining data. SS_n will release us when OTHERS => end case; else out_data <= '0'; end if; -- stay in this state shifting data out when OTHERS => -- There are no other states, but keep ghdl happy end case; end if; end process txspiproc; end logic;