You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
394 lines
12 KiB
394 lines
12 KiB
library ieee; |
|
use ieee.std_logic_1164.all; |
|
use ieee.numeric_std.all; |
|
|
|
library work; |
|
use work.wishbone_types.all; |
|
|
|
entity spi_rxtx is |
|
generic ( |
|
DATA_LINES : positive := 1; -- Number of data lines |
|
-- 1=MISO/MOSI, otherwise 2 or 4 |
|
INPUT_DELAY : natural range 0 to 1 := 1 -- Delay latching of SPI input: |
|
-- 0=no delay, 1=clk/2 |
|
); |
|
port ( |
|
clk : in std_ulogic; |
|
rst : in std_ulogic; |
|
|
|
-- |
|
-- Clock divider |
|
-- SCK = CLK/((CLK_DIV+1)*2) : 0=CLK/2, 1=CLK/4, 2=CLK/6.... |
|
-- |
|
-- This need to be changed before a command. |
|
-- XX TODO add handshake |
|
clk_div_i : in natural range 0 to 255; |
|
|
|
-- |
|
-- Command port (includes write data) |
|
-- |
|
|
|
-- Valid & ready: command sampled when valid=1 and ready=1 |
|
cmd_valid_i : in std_ulogic; |
|
cmd_ready_o : out std_ulogic; |
|
|
|
-- Command modes: |
|
-- 000 : Single bit read+write |
|
-- 010 : Single bit read |
|
-- 011 : Single bit write |
|
-- 100 : Dual read |
|
-- 101 : Dual write |
|
-- 110 : Quad read |
|
-- 111 : Quad write |
|
cmd_mode_i : in std_ulogic_vector(2 downto 0); |
|
|
|
-- # clocks-1 in a command (#bits-1) |
|
cmd_clks_i : in std_ulogic_vector(2 downto 0); |
|
|
|
-- Write data (sampled with command) |
|
cmd_txd_i : in std_ulogic_vector(7 downto 0); |
|
|
|
-- |
|
-- Read data port. Data valid when d_ack=1, no ready |
|
-- signal, receiver must be ready |
|
-- |
|
d_rxd_o : out std_ulogic_vector(7 downto 0); |
|
d_ack_o : out std_ulogic := '0'; |
|
|
|
-- Set when all commands are done. Needed for callers to know when |
|
-- to release CS# |
|
bus_idle_o : out std_ulogic; |
|
|
|
-- |
|
-- SPI port. These might need to go into special IOBUFs or STARTUPE2 on |
|
-- Xilinx. |
|
-- |
|
-- Data lines are organized as follow: |
|
-- |
|
-- DATA_LINES = 1 |
|
-- |
|
-- sdat_o(0) is MOSI (master output slave input) |
|
-- sdat_i(0) is MISO (master input slave output) |
|
-- |
|
-- DATA_LINES > 1 |
|
-- |
|
-- sdat_o(0..n) are DQ(0..n) |
|
-- sdat_i(0..n) are DQ(0..n) |
|
-- |
|
-- as such, beware that: |
|
-- |
|
-- sdat_o(0) is MOSI (master output slave input) |
|
-- sdat_i(1) is MISO (master input slave output) |
|
-- |
|
-- In order to leave dealing with the details of how to wire the tristate |
|
-- and bidirectional pins to the system specific toplevel, we separate |
|
-- the input and output signals, and provide a "sdat_oe" signal which |
|
-- is the "output enable" of each line. |
|
-- |
|
sck : out std_ulogic; |
|
sdat_o : out std_ulogic_vector(DATA_LINES-1 downto 0); |
|
sdat_oe : out std_ulogic_vector(DATA_LINES-1 downto 0); |
|
sdat_i : in std_ulogic_vector(DATA_LINES-1 downto 0) |
|
); |
|
end entity spi_rxtx; |
|
|
|
architecture rtl of spi_rxtx is |
|
|
|
-- Internal clock signal. Output is gated by sck_en_int |
|
signal sck_0 : std_ulogic; |
|
signal sck_1 : std_ulogic; |
|
|
|
-- Clock divider latch |
|
signal clk_div : natural range 0 to 255; |
|
|
|
-- 1 clk pulses indicating when to send and when to latch |
|
-- |
|
-- Typically for CPOL=CPHA |
|
-- sck_send is sck falling edge |
|
-- sck_recv is sck rising edge |
|
-- |
|
-- Those pulses are generated "ahead" of the corresponding |
|
-- edge so then are "seen" at the rising sysclk edge matching |
|
-- the corresponding sck edgeg. |
|
signal sck_send : std_ulogic; |
|
signal sck_recv : std_ulogic; |
|
|
|
-- Command mode latch |
|
signal cmd_mode : std_ulogic_vector(2 downto 0); |
|
|
|
-- Output shift register (use fifo ?) |
|
signal oreg : std_ulogic_vector(7 downto 0); |
|
|
|
-- Input latch |
|
signal dat_i_l : std_ulogic_vector(DATA_LINES-1 downto 0); |
|
|
|
-- Data ack latch |
|
signal dat_ack_l : std_ulogic; |
|
|
|
-- Delayed recv signal for the read machine |
|
signal sck_recv_d : std_ulogic; |
|
|
|
-- Input shift register (use fifo ?) |
|
signal ireg : std_ulogic_vector(7 downto 0); |
|
|
|
-- Bit counter |
|
signal bit_count : std_ulogic_vector(2 downto 0); |
|
|
|
-- Next/start/stop command signals. Set when counter goes negative |
|
signal next_cmd : std_ulogic; |
|
signal start_cmd : std_ulogic; |
|
signal end_cmd : std_ulogic; |
|
|
|
function data_single(mode : std_ulogic_vector(2 downto 0)) return boolean is |
|
begin |
|
return mode(2) = '0'; |
|
end; |
|
function data_dual(mode : std_ulogic_vector(2 downto 0)) return boolean is |
|
begin |
|
return mode(2 downto 1) = "10"; |
|
end; |
|
function data_quad(mode : std_ulogic_vector(2 downto 0)) return boolean is |
|
begin |
|
return mode(2 downto 1) = "11"; |
|
end; |
|
function data_write(mode : std_ulogic_vector(2 downto 0)) return boolean is |
|
begin |
|
return mode(0) = '1'; |
|
end; |
|
|
|
type state_t is (STANDBY, DATA); |
|
signal state : state_t; |
|
begin |
|
|
|
-- We don't support multiple data lines at this point |
|
assert DATA_LINES = 1 or DATA_LINES = 2 or DATA_LINES = 4 |
|
report "Unsupported DATA_LINES configuration !" severity failure; |
|
|
|
-- Clock generation |
|
-- |
|
-- XX HARD WIRE CPOL=1 CPHA=1 for now |
|
sck_gen: process(clk) |
|
variable counter : integer range 0 to 255; |
|
begin |
|
if rising_edge(clk) then |
|
if rst = '1' then |
|
sck_0 <= '1'; |
|
sck_1 <= '1'; |
|
sck_send <= '0'; |
|
sck_recv <= '0'; |
|
clk_div <= 0; |
|
counter := 0; |
|
elsif counter = clk_div then |
|
counter := 0; |
|
|
|
-- Latch new divider |
|
clk_div <= clk_div_i; |
|
|
|
-- Internal version of the clock |
|
sck_0 <= not sck_0; |
|
|
|
-- Generate send/receive pulses to run out state machine |
|
sck_recv <= not sck_0; |
|
sck_send <= sck_0; |
|
else |
|
counter := counter + 1; |
|
sck_recv <= '0'; |
|
sck_send <= '0'; |
|
end if; |
|
|
|
-- Delayed version of the clock to line up with |
|
-- the up/down signals |
|
-- |
|
-- XXX Figure out a better way |
|
if (state = DATA and end_cmd = '0') or (next_cmd = '1' and cmd_valid_i = '1') then |
|
sck_1 <= sck_0; |
|
else |
|
sck_1 <= '1'; |
|
end if; |
|
end if; |
|
end process; |
|
|
|
-- SPI clock |
|
sck <= sck_1; |
|
|
|
-- Ready to start the next command. This is set on the clock down |
|
-- after the counter goes negative. |
|
-- Note: in addition to latching a new command, this will cause |
|
-- the counter to be reloaded. |
|
next_cmd <= '1' when sck_send = '1' and bit_count = "111" else '0'; |
|
|
|
-- We start a command when we have a valid request at that time. |
|
start_cmd <= next_cmd and cmd_valid_i; |
|
|
|
-- We end commands if we get start_cmd and there's nothing to |
|
-- start. This sends up to standby holding CLK high |
|
end_cmd <= next_cmd and not cmd_valid_i; |
|
|
|
-- Generate cmd_ready. It will go up and down with sck, we could |
|
-- gate it with cmd_valid to make it look cleaner but that would |
|
-- add yet another combinational loop on the wishbone that I'm |
|
-- to avoid. |
|
cmd_ready_o <= next_cmd; |
|
|
|
-- Generate bus_idle_o |
|
bus_idle_o <= '1' when state = STANDBY else '0'; |
|
|
|
-- Main state machine. Also generates cmd and data ACKs |
|
machine: process(clk) |
|
begin |
|
if rising_edge(clk) then |
|
if rst = '1' then |
|
state <= STANDBY; |
|
cmd_mode <= "000"; |
|
else |
|
-- First clk down of a new cycle. Latch a request if any |
|
-- or get out. |
|
if start_cmd = '1' then |
|
state <= DATA; |
|
cmd_mode <= cmd_mode_i; |
|
elsif end_cmd = '1' then |
|
state <= STANDBY; |
|
end if; |
|
end if; |
|
end if; |
|
end process; |
|
|
|
-- Run the bit counter in DATA state. It will update on rising |
|
-- SCK edges. It starts at d_clks on command latch |
|
count_bit: process(clk) |
|
begin |
|
if rising_edge(clk) then |
|
if rst = '1' then |
|
bit_count <= (others => '0'); |
|
else |
|
if start_cmd = '1' then |
|
bit_count <= cmd_clks_i; |
|
elsif state /= DATA then |
|
bit_count <= (others => '1'); |
|
elsif sck_recv = '1' then |
|
bit_count <= std_ulogic_vector(unsigned(bit_count) - 1); |
|
end if; |
|
end if; |
|
end if; |
|
end process; |
|
|
|
-- Shift output data |
|
shift_out: process(clk) |
|
begin |
|
if rising_edge(clk) then |
|
-- Starting a command |
|
if start_cmd = '1' then |
|
oreg <= cmd_txd_i(7 downto 0); |
|
elsif sck_send = '1' then |
|
-- Get shift amount |
|
if data_single(cmd_mode) then |
|
oreg <= oreg(6 downto 0) & '0'; |
|
elsif data_dual(cmd_mode) then |
|
oreg <= oreg(5 downto 0) & "00"; |
|
else |
|
oreg <= oreg(3 downto 0) & "0000"; |
|
end if; |
|
end if; |
|
end if; |
|
end process; |
|
|
|
-- Data out |
|
sdat_o(0) <= oreg(7); |
|
dl2: if DATA_LINES > 1 generate |
|
sdat_o(1) <= oreg(6); |
|
end generate; |
|
dl4: if DATA_LINES > 2 generate |
|
sdat_o(2) <= oreg(5); |
|
sdat_o(3) <= oreg(4); |
|
end generate; |
|
|
|
-- Data lines direction |
|
dlines: process(all) |
|
begin |
|
for i in DATA_LINES-1 downto 0 loop |
|
sdat_oe(i) <= '0'; |
|
if state = DATA then |
|
-- In single mode, we always enable MOSI, otherwise |
|
-- we control the output enable based on the direction |
|
-- of transfer. |
|
-- |
|
if i = 0 and (data_single(cmd_mode) or data_write(cmd_mode)) then |
|
sdat_oe(i) <= '1'; |
|
end if; |
|
if i = 1 and data_dual(cmd_mode) and data_write(cmd_mode) then |
|
sdat_oe(i) <= '1'; |
|
end if; |
|
if i > 0 and data_quad(cmd_mode) and data_write(cmd_mode) then |
|
sdat_oe(i) <= '1'; |
|
end if; |
|
end if; |
|
end loop; |
|
end process; |
|
|
|
-- Latch input data no delay |
|
input_delay_0: if INPUT_DELAY = 0 generate |
|
process(clk) |
|
begin |
|
if rising_edge(clk) then |
|
dat_i_l <= sdat_i; |
|
end if; |
|
end process; |
|
end generate; |
|
|
|
-- Latch input data half clock delay |
|
input_delay_1: if INPUT_DELAY = 1 generate |
|
process(clk) |
|
begin |
|
if falling_edge(clk) then |
|
dat_i_l <= sdat_i; |
|
end if; |
|
end process; |
|
end generate; |
|
|
|
-- Shift input data |
|
shift_in: process(clk) |
|
begin |
|
if rising_edge(clk) then |
|
if rst = '1' then |
|
ireg <= (others => '0'); |
|
end if; |
|
|
|
-- Delay the receive signal to match the input latch |
|
if state = DATA then |
|
sck_recv_d <= sck_recv; |
|
else |
|
sck_recv_d <= '0'; |
|
end if; |
|
|
|
-- Generate read data acks |
|
if bit_count = "000" and sck_recv = '1' then |
|
dat_ack_l <= not cmd_mode(0); |
|
else |
|
dat_ack_l <= '0'; |
|
end if; |
|
|
|
-- And delay them as well |
|
d_ack_o <= dat_ack_l; |
|
|
|
-- Shift register on delayed data & receive signal |
|
if sck_recv_d = '1' then |
|
if DATA_LINES = 1 then |
|
ireg <= ireg(6 downto 0) & dat_i_l(0); |
|
else |
|
if data_dual(cmd_mode) then |
|
ireg <= ireg(5 downto 0) & dat_i_l(1) & dat_i_l(0); |
|
elsif data_quad(cmd_mode) then |
|
ireg <= ireg(3 downto 0) & dat_i_l(3) & dat_i_l(2) & dat_i_l(1) & dat_i_l(0); |
|
else |
|
assert(data_single(cmd_mode)); |
|
ireg <= ireg(6 downto 0) & dat_i_l(1); |
|
end if; |
|
end if; |
|
end if; |
|
end if; |
|
end process; |
|
|
|
-- Data recieve register |
|
d_rxd_o <= ireg; |
|
|
|
end architecture;
|
|
|