You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
microwatt/spi_rxtx.vhdl

388 lines
12 KiB
VHDL

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 := '0';
-- Input shift register (use fifo ?)
signal ireg : std_ulogic_vector(7 downto 0) := (others => '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 := STANDBY;
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 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 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
-- 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;