| 1 |
2 |
dewhisna |
--------------------------------------------------------------------------------
|
| 2 |
|
|
-- File name : spi_slave.vhd
|
| 3 |
|
|
--------------------------------------------------------------------------------
|
| 4 |
|
|
-- Copyright (C) 2015 Donna Whisnant/Dewtronics.
|
| 5 |
|
|
-- Contact: http://www.dewtronics.com/
|
| 6 |
|
|
--
|
| 7 |
|
|
-- This file may be used under the terms of the GNU Lesser General Public License
|
| 8 |
|
|
-- version 3.0 as published by the Free Software Foundation and appearing
|
| 9 |
|
|
-- in the files lgpl-3.0.txt/gpl-3.0.txt included in the packaging of this file.
|
| 10 |
|
|
-- Please review the following information to ensure the GNU Lesser General
|
| 11 |
|
|
-- Public License version 3.0 requirements will be met:
|
| 12 |
|
|
-- https://www.gnu.org/licenses/lgpl-3.0.html
|
| 13 |
|
|
-- Attribution requested, but not required.
|
| 14 |
|
|
--
|
| 15 |
|
|
-- Target Device: Xilinx Spartan-6 XC6SLX9-2-TQG144
|
| 16 |
|
|
-- Using Numato Mimas Spartan 6 FPGA Development Board
|
| 17 |
|
|
-- http://numato.com/mimas-spartan-6-fpga-development-board.html
|
| 18 |
|
|
--
|
| 19 |
|
|
--
|
| 20 |
|
|
-- SPI Slave Entity for TimerOCD
|
| 21 |
|
|
--
|
| 22 |
|
|
--------------------------------------------------------------------------------
|
| 23 |
|
|
|
| 24 |
|
|
library IEEE;
|
| 25 |
|
|
use IEEE.std_logic_1164.all;
|
| 26 |
|
|
use IEEE.std_logic_arith.all;
|
| 27 |
|
|
use IEEE.std_logic_unsigned.all;
|
| 28 |
|
|
|
| 29 |
|
|
library UNISIM;
|
| 30 |
|
|
use UNISIM.VCOMPONENTS.all;
|
| 31 |
|
|
|
| 32 |
|
|
entity spi_slave is
|
| 33 |
|
|
generic (
|
| 34 |
|
|
cpol : STD_LOGIC := '0'; --spi clock polarity mode
|
| 35 |
|
|
cpha : STD_LOGIC := '0'; --spi clock phase mode
|
| 36 |
|
|
d_width : INTEGER := 8 --data width in bits
|
| 37 |
|
|
);
|
| 38 |
|
|
port (
|
| 39 |
|
|
sync_clk : in STD_LOGIC; --system clock in for synchronization of the SS signal
|
| 40 |
|
|
sclk : in STD_LOGIC; --spi clk from master
|
| 41 |
|
|
reset_n : in STD_LOGIC; --active low reset
|
| 42 |
|
|
ss_n : in STD_LOGIC; --active low slave select
|
| 43 |
|
|
mosi : in STD_LOGIC; --master out, slave in
|
| 44 |
|
|
rrdy : out STD_LOGIC := '0'; --receive ready bit
|
| 45 |
|
|
rx_data : out STD_LOGIC_VECTOR(d_width-1 downto 0) := (OTHERS => '0'); --receive register output to logic
|
| 46 |
|
|
busy : out STD_LOGIC := '0'; --busy signal to logic ('1' during transaction)
|
| 47 |
|
|
miso : out STD_LOGIC := 'Z'; --master in, slave out
|
| 48 |
|
|
tx_load_data : in STD_LOGIC_VECTOR(d_width-1 downto 0); --Tx data to load (synchronous with end of address receive)
|
| 49 |
|
|
addr : out STD_LOGIC_VECTOR(7 downto 0) := (OTHERS => '0'); -- Address to Read or Write (as received from SPI)
|
| 50 |
|
|
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)
|
| 51 |
|
|
cmd_load_data : in STD_LOGIC_VECTOR(3 downto 0) := (OTHERS => '0') -- Data nybble to shift out during low nybble of command byte receive
|
| 52 |
|
|
);
|
| 53 |
|
|
end spi_slave;
|
| 54 |
|
|
|
| 55 |
|
|
architecture logic of spi_slave is
|
| 56 |
|
|
signal mode : STD_LOGIC; --groups modes by clock polarity relation to data
|
| 57 |
|
|
signal clk : STD_LOGIC; --clock
|
| 58 |
|
|
constant nRSB : integer := 4; -- Number of RxState Bits (used for conversions)
|
| 59 |
|
|
signal rx_state : STD_LOGIC_VECTOR(nRSB-1 downto 0); -- Receive state-machine
|
| 60 |
|
|
signal rxbit_cnt : INTEGER range 0 to d_width-1; -- Bit count for data received
|
| 61 |
|
|
constant nTSB : integer := 3; -- Number of TxState Bits (used for conversions)
|
| 62 |
|
|
signal tx_state : STD_LOGIC_VECTOR(nTSB-1 downto 0); -- Transmit state-machine
|
| 63 |
|
|
constant nTBCSB : integer := 3; -- Transmit Bit Count State Bits (used for conversions)
|
| 64 |
|
|
signal txbit_flg : STD_LOGIC_VECTOR(nTBCSB-1 downto 0); -- Bit count for data sent
|
| 65 |
|
|
signal wr_add : STD_LOGIC := '1'; -- address of register to write ('0' = receive, '1' = status)
|
| 66 |
|
|
signal cmd_add : STD_LOGIC := '0'; -- command or data ('0' = data, '1' = command)
|
| 67 |
|
|
signal rx_buf : STD_LOGIC_VECTOR(d_width-2 downto 0) := (OTHERS => '0'); --receiver buffer
|
| 68 |
|
|
signal tx_buf : STD_LOGIC_VECTOR(d_width-1 downto 0) := (OTHERS => '0'); -- transmit buffer
|
| 69 |
|
|
signal cmd_buf : STD_LOGIC_VECTOR(2 downto 0) := (OTHERS => '0'); -- command buffer
|
| 70 |
|
|
signal out_data : STD_LOGIC; -- Output data gated to miso
|
| 71 |
|
|
signal syn_ss_n : STD_LOGIC; -- Synchronized SS_N signal
|
| 72 |
|
|
signal syn_mosi : STD_LOGIC; -- MOSI synchronized to sync_clk
|
| 73 |
|
|
signal syn_clk_fall : STD_LOGIC; -- CLK falling edge synchronized to sync_clk
|
| 74 |
|
|
signal syn_clk_rise : STD_LOGIC; -- CLK rising edge synchronized to sync_clk
|
| 75 |
|
|
signal resync_clk : STD_LOGIC_VECTOR(3 downto 0);
|
| 76 |
|
|
signal resync_ss : STD_LOGIC_VECTOR(1 downto 0);
|
| 77 |
|
|
signal resync_mosi : STD_LOGIC_VECTOR(1 downto 0);
|
| 78 |
|
|
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
|
| 79 |
|
|
signal rrdy_out : STD_LOGIC; -- Output rrdy (used to eliminate buffer type)
|
| 80 |
|
|
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
|
| 81 |
|
|
signal addr_latch_out : STD_LOGIC; -- Output addr_latch (used to eliminate buffer type)
|
| 82 |
|
|
begin
|
| 83 |
|
|
--adjust clock so writes are on rising edge and reads on falling edge
|
| 84 |
|
|
mode <= cpol XOR cpha; --'1' for modes that write on rising edge
|
| 85 |
|
|
with mode select
|
| 86 |
|
|
clk <= sclk when '1', NOT sclk when OTHERS;
|
| 87 |
|
|
|
| 88 |
|
|
-- Input synchronization:
|
| 89 |
|
|
sync_clk_proc:process(sync_clk)
|
| 90 |
|
|
begin
|
| 91 |
|
|
if (rising_edge(sync_clk)) then
|
| 92 |
|
|
syn_ss_n <= resync_ss(0);
|
| 93 |
|
|
syn_mosi <= resync_mosi(0);
|
| 94 |
|
|
resync_clk <= resync_clk(2 downto 0) & clk;
|
| 95 |
|
|
resync_ss <= resync_ss(0) & ss_n;
|
| 96 |
|
|
resync_mosi <= resync_mosi(0) & mosi;
|
| 97 |
|
|
end if;
|
| 98 |
|
|
end process sync_clk_proc;
|
| 99 |
|
|
syn_clk_fall <= resync_clk(3) and not resync_clk(2); -- '1' when going from 1 -> 0
|
| 100 |
|
|
syn_clk_rise <= not resync_clk(2) and resync_clk(1); -- '1' when going from 0 -> 1
|
| 101 |
|
|
|
| 102 |
|
|
-- output drive:
|
| 103 |
|
|
with (syn_ss_n OR (NOT reset_n)) select
|
| 104 |
|
|
miso <= 'Z' when '1', out_data when OTHERS;
|
| 105 |
|
|
|
| 106 |
|
|
busy <= NOT syn_ss_n; --high during transactions
|
| 107 |
|
|
|
| 108 |
|
|
-- rrdy synchronization:
|
| 109 |
|
|
-- Since rxspiproc is triggered on the rising_edge of
|
| 110 |
|
|
-- sync_clk, this process delays reporting it
|
| 111 |
|
|
-- by 1/2 clock cycle, giving the data time to
|
| 112 |
|
|
-- get stored:
|
| 113 |
|
|
rrdyproc:process(syn_ss_n, sync_clk, reset_n)
|
| 114 |
|
|
begin
|
| 115 |
|
|
if ((syn_ss_n = '1') OR (reset_n = '0')) then
|
| 116 |
|
|
rrdy_out <= '0';
|
| 117 |
|
|
elsif (falling_edge(sync_clk)) then
|
| 118 |
|
|
if (rrdy_out = '0') then
|
| 119 |
|
|
rrdy_out <= rrdy_int;
|
| 120 |
|
|
end if;
|
| 121 |
|
|
end if;
|
| 122 |
|
|
end process rrdyproc;
|
| 123 |
|
|
rrdy <= rrdy_out;
|
| 124 |
|
|
|
| 125 |
|
|
-- addr_latch synchronization:
|
| 126 |
|
|
-- Since rxspiproc is triggered on the rising_edge of
|
| 127 |
|
|
-- sync clk, this process delays reporting it
|
| 128 |
|
|
-- by 1/2 clock cycle, giving the data time to
|
| 129 |
|
|
-- get stored:
|
| 130 |
|
|
addrlatchproc:process(syn_ss_n, sync_clk, reset_n)
|
| 131 |
|
|
begin
|
| 132 |
|
|
if ((syn_ss_n = '1') OR (reset_n = '0')) then
|
| 133 |
|
|
addr_latch_out <= '0';
|
| 134 |
|
|
elsif (falling_edge(sync_clk)) then
|
| 135 |
|
|
if (addr_latch_out = '0') then
|
| 136 |
|
|
addr_latch_out <= addr_latch_int;
|
| 137 |
|
|
end if;
|
| 138 |
|
|
end if;
|
| 139 |
|
|
end process addrlatchproc;
|
| 140 |
|
|
addr_latch <= addr_latch_out;
|
| 141 |
|
|
|
| 142 |
|
|
-- rxspiproc: Receives incoming data
|
| 143 |
|
|
rxspiproc:process(syn_ss_n, syn_clk_fall, reset_n)
|
| 144 |
|
|
begin
|
| 145 |
|
|
if ((syn_ss_n = '1') OR (reset_n = '0')) then
|
| 146 |
|
|
rx_state <= (OTHERS => '0');
|
| 147 |
|
|
rx_buf <= (OTHERS => '0');
|
| 148 |
|
|
rxbit_cnt <= d_width-1;
|
| 149 |
|
|
rrdy_int <= '0';
|
| 150 |
|
|
addr_latch_int <= '0';
|
| 151 |
|
|
elsif (falling_edge(syn_clk_fall)) then
|
| 152 |
|
|
case CONV_INTEGER(rx_state) is
|
| 153 |
|
|
when 0 =>
|
| 154 |
|
|
--read/write mode ('0' for write, '1' for read)
|
| 155 |
|
|
wr_add <= syn_mosi;
|
| 156 |
|
|
addr(7) <= syn_mosi;
|
| 157 |
|
|
rx_state <= CONV_STD_LOGIC_VECTOR(1, nRSB);
|
| 158 |
|
|
when 1 =>
|
| 159 |
|
|
--cmd/data mode ('0' for data, '1' for cmd)
|
| 160 |
|
|
cmd_add <= syn_mosi;
|
| 161 |
|
|
addr(6) <= syn_mosi;
|
| 162 |
|
|
rx_state <= CONV_STD_LOGIC_VECTOR(3, nRSB);
|
| 163 |
|
|
when 3 =>
|
| 164 |
|
|
addr(5) <= syn_mosi;
|
| 165 |
|
|
rx_state <= CONV_STD_LOGIC_VECTOR(2, nRSB);
|
| 166 |
|
|
when 2 =>
|
| 167 |
|
|
addr(4) <= syn_mosi;
|
| 168 |
|
|
rx_state <= CONV_STD_LOGIC_VECTOR(6, nRSB);
|
| 169 |
|
|
when 6 =>
|
| 170 |
|
|
addr(3) <= syn_mosi;
|
| 171 |
|
|
rx_state <= CONV_STD_LOGIC_VECTOR(7, nRSB);
|
| 172 |
|
|
when 7 =>
|
| 173 |
|
|
addr(2) <= syn_mosi;
|
| 174 |
|
|
rx_state <= CONV_STD_LOGIC_VECTOR(5, nRSB);
|
| 175 |
|
|
when 5 =>
|
| 176 |
|
|
addr(1) <= syn_mosi;
|
| 177 |
|
|
rx_state <= CONV_STD_LOGIC_VECTOR(4, nRSB);
|
| 178 |
|
|
when 4 =>
|
| 179 |
|
|
addr(0) <= syn_mosi;
|
| 180 |
|
|
if (cmd_add = '1') then
|
| 181 |
|
|
rx_state <= CONV_STD_LOGIC_VECTOR(13, nRSB);
|
| 182 |
|
|
if (wr_add = '0') then
|
| 183 |
|
|
rrdy_int <= '1';
|
| 184 |
|
|
end if;
|
| 185 |
|
|
else
|
| 186 |
|
|
addr_latch_int <= '1';
|
| 187 |
|
|
rx_state <= CONV_STD_LOGIC_VECTOR(12, nRSB);
|
| 188 |
|
|
end if;
|
| 189 |
|
|
when 12 =>
|
| 190 |
|
|
if (rxbit_cnt = 0) then
|
| 191 |
|
|
if (wr_add = '0') then
|
| 192 |
|
|
rx_data <= rx_buf & syn_mosi;
|
| 193 |
|
|
rrdy_int <= '1';
|
| 194 |
|
|
end if;
|
| 195 |
|
|
rx_state <= CONV_STD_LOGIC_VECTOR(13, nRSB);
|
| 196 |
|
|
else
|
| 197 |
|
|
if (wr_add = '0') then
|
| 198 |
|
|
rx_buf <= rx_buf(rx_buf'HIGH-1 downto 0) & syn_mosi;
|
| 199 |
|
|
end if;
|
| 200 |
|
|
rxbit_cnt <= (rxbit_cnt - 1);
|
| 201 |
|
|
-- Stay in this state until all bits are received
|
| 202 |
|
|
end if;
|
| 203 |
|
|
when 13 =>
|
| 204 |
|
|
-- Stay in this state. The SS_n signal will release us
|
| 205 |
|
|
when OTHERS =>
|
| 206 |
|
|
end case;
|
| 207 |
|
|
end if;
|
| 208 |
|
|
end process rxspiproc;
|
| 209 |
|
|
|
| 210 |
|
|
|
| 211 |
|
|
-- txspiproc: Transmits outgoing data
|
| 212 |
|
|
-- Note: Commented out code for cmd_buf/cmd_load_data and
|
| 213 |
|
|
-- tx_buf/tx_load_data can be used to determine the point
|
| 214 |
|
|
-- where data must be ready/available from the main TimerOCD
|
| 215 |
|
|
-- relative to the SPI traffic. This allows for faster SPI
|
| 216 |
|
|
-- clock rates by only requiring the first couple of bits and/or
|
| 217 |
|
|
-- first byte of data to be valid when the SPI clock begins
|
| 218 |
|
|
-- shifting it out. Since several memory read operations are
|
| 219 |
|
|
-- required to obtain the data needed to be sent, this allows
|
| 220 |
|
|
-- for several SPI clock cycle times before that data reading
|
| 221 |
|
|
-- must be completed.
|
| 222 |
|
|
txspiproc:process(syn_ss_n, syn_clk_rise, reset_n)
|
| 223 |
|
|
begin
|
| 224 |
|
|
if ((syn_ss_n = '1') OR (reset_n = '0')) then
|
| 225 |
|
|
tx_state <= (OTHERS => '0');
|
| 226 |
|
|
tx_buf <= (OTHERS => '0');
|
| 227 |
|
|
txbit_flg <= (OTHERS => '0');
|
| 228 |
|
|
out_data <= '0';
|
| 229 |
|
|
elsif (rising_edge(syn_clk_rise)) then
|
| 230 |
|
|
case CONV_INTEGER(tx_state) is
|
| 231 |
|
|
when 0 =>
|
| 232 |
|
|
tx_state <= CONV_STD_LOGIC_VECTOR(1, nTSB);
|
| 233 |
|
|
when 1 =>
|
| 234 |
|
|
tx_state <= CONV_STD_LOGIC_VECTOR(3, nTSB);
|
| 235 |
|
|
when 3 =>
|
| 236 |
|
|
tx_state <= CONV_STD_LOGIC_VECTOR(2, nTSB);
|
| 237 |
|
|
when 2 =>
|
| 238 |
|
|
out_data <= cmd_load_data(3);
|
| 239 |
|
|
-- cmd_buf <= cmd_load_data(2 downto 0);
|
| 240 |
|
|
tx_state <= CONV_STD_LOGIC_VECTOR(6, nTSB);
|
| 241 |
|
|
when 6 =>
|
| 242 |
|
|
-- out_data <= cmd_buf(2);
|
| 243 |
|
|
out_data <= cmd_load_data(2);
|
| 244 |
|
|
cmd_buf <= cmd_load_data(2 downto 0);
|
| 245 |
|
|
tx_state <= CONV_STD_LOGIC_VECTOR(7, nTSB);
|
| 246 |
|
|
when 7 =>
|
| 247 |
|
|
out_data <= cmd_buf(1);
|
| 248 |
|
|
tx_state <= CONV_STD_LOGIC_VECTOR(5, nTSB);
|
| 249 |
|
|
when 5 =>
|
| 250 |
|
|
out_data <= cmd_buf(0);
|
| 251 |
|
|
tx_state <= CONV_STD_LOGIC_VECTOR(4, nTSB);
|
| 252 |
|
|
when 4 =>
|
| 253 |
|
|
if (cmd_add='0') then
|
| 254 |
|
|
case CONV_INTEGER(txbit_flg) is
|
| 255 |
|
|
when 0 =>
|
| 256 |
|
|
out_data <= tx_load_data(d_width-1);
|
| 257 |
|
|
-- tx_buf <= tx_load_data(d_width-2 downto 0) & "0";
|
| 258 |
|
|
txbit_flg <= CONV_STD_LOGIC_VECTOR(1, nTBCSB);
|
| 259 |
|
|
when 1 =>
|
| 260 |
|
|
out_data <= tx_load_data(d_width-2);
|
| 261 |
|
|
-- tx_buf <= tx_load_data(d_width-3 downto 0) & "00";
|
| 262 |
|
|
txbit_flg <= CONV_STD_LOGIC_VECTOR(3, nTBCSB);
|
| 263 |
|
|
when 3 =>
|
| 264 |
|
|
out_data <= tx_load_data(d_width-3);
|
| 265 |
|
|
-- tx_buf <= tx_load_data(d_width-4 downto 0) & "000";
|
| 266 |
|
|
txbit_flg <= CONV_STD_LOGIC_VECTOR(2, nTBCSB);
|
| 267 |
|
|
when 2 =>
|
| 268 |
|
|
out_data <= tx_load_data(d_width-4);
|
| 269 |
|
|
-- tx_buf <= tx_load_data(d_width-5 downto 0) & "0000";
|
| 270 |
|
|
txbit_flg <= CONV_STD_LOGIC_VECTOR(6, nTBCSB);
|
| 271 |
|
|
when 6 =>
|
| 272 |
|
|
out_data <= tx_load_data(d_width-5);
|
| 273 |
|
|
-- tx_buf <= tx_load_data(d_width-6 downto 0) & "00000";
|
| 274 |
|
|
txbit_flg <= CONV_STD_LOGIC_VECTOR(7, nTBCSB);
|
| 275 |
|
|
when 7 =>
|
| 276 |
|
|
out_data <= tx_load_data(d_width-6);
|
| 277 |
|
|
-- tx_buf <= tx_load_data(d_width-7 downto 0) & "000000";
|
| 278 |
|
|
txbit_flg <= CONV_STD_LOGIC_VECTOR(5, nTBCSB);
|
| 279 |
|
|
when 5 =>
|
| 280 |
|
|
out_data <= tx_load_data(d_width-7);
|
| 281 |
|
|
tx_buf <= tx_load_data(d_width-8 downto 0) & "0000000";
|
| 282 |
|
|
txbit_flg <= CONV_STD_LOGIC_VECTOR(4, nTBCSB);
|
| 283 |
|
|
when 4 =>
|
| 284 |
|
|
out_data <= tx_buf(tx_buf'HIGH);
|
| 285 |
|
|
tx_buf <= tx_buf(tx_buf'HIGH-1 downto 0) & "0";
|
| 286 |
|
|
-- Stay here and shift out remaining data. SS_n will release us
|
| 287 |
|
|
when OTHERS =>
|
| 288 |
|
|
end case;
|
| 289 |
|
|
else
|
| 290 |
|
|
out_data <= '0';
|
| 291 |
|
|
end if;
|
| 292 |
|
|
-- stay in this state shifting data out
|
| 293 |
|
|
when OTHERS => -- There are no other states, but keep ghdl happy
|
| 294 |
|
|
end case;
|
| 295 |
|
|
end if;
|
| 296 |
|
|
end process txspiproc;
|
| 297 |
|
|
end logic;
|