forked from cores/microwatt
spi: Add SPI Flash controller
This adds an SPI flash controller which supports direct memory-mapped access to the flash along with a manual mode to send commands. The direct mode can be set via generic to default to single wire or quad mode. The controller supports normal, dual and quad accesses with configurable commands, clock divider, dummy clocks etc... The SPI clock can be an even divider of sys_clk starting at 2 (so max 50Mhz with our typical Arty designs). A flash offset is carried via generics to syscon to tell SW about which portion of the flash is reserved for the FPGA bitfile. There is currently no plumbing to make the CPU reset past that address (TBD). Note: Operating at 50Mhz has proven unreliable without adding some delay to the sampling of the input data. I'm working in improving this, in the meantime, I'm leaving the default set at 25 Mhz. Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>jtag-port
parent
15467fe536
commit
cc4dcb3597
@ -0,0 +1,601 @@
|
||||
library ieee;
|
||||
use ieee.std_logic_1164.all;
|
||||
use ieee.numeric_std.all;
|
||||
|
||||
library work;
|
||||
use work.wishbone_types.all;
|
||||
|
||||
entity spi_flash_ctrl is
|
||||
generic (
|
||||
-- Default config for auto-mode
|
||||
DEF_CLK_DIV : natural := 2; -- Clock divider SCK = CLK/((CLK_DIV+1)*2)
|
||||
DEF_QUAD_READ : boolean := false; -- Use quad read with 8 clk dummy
|
||||
|
||||
-- Number of data lines (1=MISO/MOSI, otherwise 2 or 4)
|
||||
DATA_LINES : positive := 1
|
||||
);
|
||||
port (
|
||||
clk : in std_ulogic;
|
||||
rst : in std_ulogic;
|
||||
|
||||
-- Wishbone ports:
|
||||
wb_in : in wb_io_master_out;
|
||||
wb_out : out wb_io_slave_out;
|
||||
|
||||
-- Wishbone extra selects
|
||||
wb_sel_reg : in std_ulogic;
|
||||
wb_sel_map : in std_ulogic;
|
||||
|
||||
-- SPI port
|
||||
sck : out std_ulogic;
|
||||
cs_n : 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_flash_ctrl;
|
||||
|
||||
architecture rtl of spi_flash_ctrl is
|
||||
|
||||
-- Register indices
|
||||
constant SPI_REG_BITS : positive := 3;
|
||||
|
||||
-- Register addresses (matches wishbone addr downto 2, ie, 4 bytes per reg)
|
||||
constant SPI_REG_DATA : std_ulogic_vector(SPI_REG_BITS-1 downto 0) := "000";
|
||||
constant SPI_REG_CTRL : std_ulogic_vector(SPI_REG_BITS-1 downto 0) := "001";
|
||||
constant SPI_REG_AUTO_CFG : std_ulogic_vector(SPI_REG_BITS-1 downto 0) := "010";
|
||||
constant SPI_REG_INVALID : std_ulogic_vector(SPI_REG_BITS-1 downto 0) := "111";
|
||||
|
||||
-- Control register
|
||||
signal ctrl_reg : std_ulogic_vector(15 downto 0) := (others => '0');
|
||||
alias ctrl_reset : std_ulogic is ctrl_reg(0);
|
||||
alias ctrl_cs : std_ulogic is ctrl_reg(1);
|
||||
alias ctrl_rsrv1 : std_ulogic is ctrl_reg(2);
|
||||
alias ctrl_rsrv2 : std_ulogic is ctrl_reg(3);
|
||||
alias ctrl_div : std_ulogic_vector(7 downto 0) is ctrl_reg(15 downto 8);
|
||||
|
||||
-- Auto mode config register
|
||||
signal auto_cfg_reg : std_ulogic_vector(29 downto 0) := (others => '0');
|
||||
alias auto_cfg_cmd : std_ulogic_vector(7 downto 0) is auto_cfg_reg(7 downto 0);
|
||||
alias auto_cfg_dummies : std_ulogic_vector(2 downto 0) is auto_cfg_reg(10 downto 8);
|
||||
alias auto_cfg_mode : std_ulogic_vector(1 downto 0) is auto_cfg_reg(12 downto 11);
|
||||
alias auto_cfg_addr4 : std_ulogic is auto_cfg_reg(13);
|
||||
alias auto_cfg_rsrv1 : std_ulogic is auto_cfg_reg(14);
|
||||
alias auto_cfg_rsrv2 : std_ulogic is auto_cfg_reg(15);
|
||||
alias auto_cfg_div : std_ulogic_vector(7 downto 0) is auto_cfg_reg(23 downto 16);
|
||||
alias auto_cfg_cstout : std_ulogic_vector(5 downto 0) is auto_cfg_reg(29 downto 24);
|
||||
|
||||
-- Constants below match top 2 bits of rxtx "mode"
|
||||
constant SPI_AUTO_CFG_MODE_SINGLE : std_ulogic_vector(1 downto 0) := "00";
|
||||
constant SPI_AUTO_CFG_MODE_DUAL : std_ulogic_vector(1 downto 0) := "10";
|
||||
constant SPI_AUTO_CFG_MODE_QUAD : std_ulogic_vector(1 downto 0) := "11";
|
||||
|
||||
-- Signals to rxtx
|
||||
signal cmd_valid : std_ulogic;
|
||||
signal cmd_clk_div : natural range 0 to 255;
|
||||
signal cmd_mode : std_ulogic_vector(2 downto 0);
|
||||
signal cmd_ready : std_ulogic;
|
||||
signal d_clks : std_ulogic_vector(2 downto 0);
|
||||
signal d_rx : std_ulogic_vector(7 downto 0);
|
||||
signal d_tx : std_ulogic_vector(7 downto 0);
|
||||
signal d_ack : std_ulogic;
|
||||
signal bus_idle : std_ulogic;
|
||||
|
||||
-- Latch to track that we have a pending read
|
||||
signal pending_read : std_ulogic;
|
||||
|
||||
-- Wishbone latches
|
||||
signal wb_req : wb_io_master_out;
|
||||
signal wb_stash : wb_io_master_out;
|
||||
signal wb_rsp : wb_io_slave_out;
|
||||
|
||||
-- Wishbone decode
|
||||
signal wb_valid : std_ulogic;
|
||||
signal wb_reg_valid : std_ulogic;
|
||||
signal wb_reg_dat_v : std_ulogic;
|
||||
signal wb_map_valid : std_ulogic;
|
||||
signal wb_reg : std_ulogic_vector(SPI_REG_BITS-1 downto 0);
|
||||
|
||||
-- Auto mode clock counts XXX FIXME: Look at reasonable values based
|
||||
-- on system clock maybe ? Or make them programmable.
|
||||
constant CS_DELAY_ASSERT : integer := 1; -- CS low to cmd
|
||||
constant CS_DELAY_RECOVERY : integer := 10; -- CS high to CS low
|
||||
constant DEFAULT_CS_TIMEOUT : integer := 32;
|
||||
|
||||
-- Automatic mode state
|
||||
type auto_state_t is (AUTO_IDLE, AUTO_CS_ON, AUTO_CMD,
|
||||
AUTO_ADR0, AUTO_ADR1, AUTO_ADR2, AUTO_ADR3,
|
||||
AUTO_DUMMY,
|
||||
AUTO_DAT0, AUTO_DAT1, AUTO_DAT2, AUTO_DAT3,
|
||||
AUTO_DAT0_DATA, AUTO_DAT1_DATA, AUTO_DAT2_DATA, AUTO_DAT3_DATA,
|
||||
AUTO_SEND_ACK, AUTO_WAIT_REQ, AUTO_RECOVERY);
|
||||
-- Automatic mode signals
|
||||
signal auto_cs : std_ulogic;
|
||||
signal auto_cmd_valid : std_ulogic;
|
||||
signal auto_cmd_mode : std_ulogic_vector(2 downto 0);
|
||||
signal auto_d_txd : std_ulogic_vector(7 downto 0);
|
||||
signal auto_d_clks : std_ulogic_vector(2 downto 0);
|
||||
signal auto_data_next : std_ulogic_vector(wb_out.dat'left downto 0);
|
||||
signal auto_cnt_next : integer range 0 to 63;
|
||||
signal auto_ack : std_ulogic;
|
||||
signal auto_next : auto_state_t;
|
||||
signal auto_lad_next : std_ulogic_vector(31 downto 0);
|
||||
signal auto_latch_adr : std_ulogic;
|
||||
|
||||
-- Automatic mode latches
|
||||
signal auto_data : std_ulogic_vector(wb_out.dat'left downto 0) := (others => '0');
|
||||
signal auto_cnt : integer range 0 to 63 := 0;
|
||||
signal auto_state : auto_state_t := AUTO_IDLE;
|
||||
signal auto_last_addr : std_ulogic_vector(31 downto 0);
|
||||
|
||||
begin
|
||||
|
||||
-- Instanciate low level shifter
|
||||
spi_rxtx: entity work.spi_rxtx
|
||||
generic map (
|
||||
DATA_LINES => DATA_LINES
|
||||
)
|
||||
port map(
|
||||
rst => rst,
|
||||
clk => clk,
|
||||
clk_div_i => cmd_clk_div,
|
||||
cmd_valid_i => cmd_valid,
|
||||
cmd_ready_o => cmd_ready,
|
||||
cmd_mode_i => cmd_mode,
|
||||
cmd_clks_i => d_clks,
|
||||
cmd_txd_i => d_tx,
|
||||
d_rxd_o => d_rx,
|
||||
d_ack_o => d_ack,
|
||||
bus_idle_o => bus_idle,
|
||||
sck => sck,
|
||||
sdat_o => sdat_o,
|
||||
sdat_oe => sdat_oe,
|
||||
sdat_i => sdat_i
|
||||
);
|
||||
|
||||
-- Valid wb command
|
||||
wb_valid <= wb_req.stb and wb_req.cyc;
|
||||
wb_reg_valid <= wb_valid and wb_sel_reg;
|
||||
wb_map_valid <= wb_valid and wb_sel_map;
|
||||
|
||||
-- Register decode. For map accesses, make it look like "invalid"
|
||||
wb_reg <= wb_req.adr(SPI_REG_BITS+1 downto 2) when wb_reg_valid else SPI_REG_INVALID;
|
||||
|
||||
-- Shortcut because we test that a lot: data register access
|
||||
wb_reg_dat_v <= '1' when wb_reg = SPI_REG_DATA else '0';
|
||||
|
||||
-- Wishbone request -> SPI request
|
||||
wb_request_sync: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
-- We need to latch whether a read is in progress to block
|
||||
-- a subsequent store, otherwise the acks will collide.
|
||||
--
|
||||
-- We are heavy handed and force a wait for an idle bus if
|
||||
-- a store is behind a load. Shouldn't happen with flashes
|
||||
-- in practice.
|
||||
--
|
||||
if cmd_valid = '1' and cmd_ready = '1' then
|
||||
pending_read <= '1';
|
||||
elsif bus_idle = '1' then
|
||||
pending_read <= '0';
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
wb_request_comb: process(all)
|
||||
begin
|
||||
if ctrl_cs = '1' then
|
||||
-- Data register access (see wb_request_sync)
|
||||
cmd_valid <= wb_reg_dat_v and not (pending_read and wb_req.we);
|
||||
|
||||
-- Clock divider from control reg
|
||||
cmd_clk_div <= to_integer(unsigned(ctrl_div));
|
||||
|
||||
-- Mode based on sel
|
||||
if wb_req.sel = "0010" then
|
||||
-- dual mode
|
||||
cmd_mode <= "10" & wb_req.we;
|
||||
d_clks <= "011";
|
||||
elsif wb_req.sel = "0100" then
|
||||
-- quad mode
|
||||
cmd_mode <= "11" & wb_req.we;
|
||||
d_clks <= "001";
|
||||
else
|
||||
-- single bit
|
||||
cmd_mode <= "01" & wb_req.we;
|
||||
d_clks <= "111";
|
||||
end if;
|
||||
d_tx <= wb_req.dat(7 downto 0);
|
||||
cs_n <= not ctrl_cs;
|
||||
else
|
||||
cmd_valid <= auto_cmd_valid;
|
||||
cmd_mode <= auto_cmd_mode;
|
||||
cmd_clk_div <= to_integer(unsigned(auto_cfg_div));
|
||||
d_tx <= auto_d_txd;
|
||||
d_clks <= auto_d_clks;
|
||||
cs_n <= not auto_cs;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
-- Generate wishbone responses
|
||||
--
|
||||
-- Note: wb_out and wb_in should only appear in this synchronous process
|
||||
--
|
||||
-- Everything else should work on wb_req and wb_rsp
|
||||
wb_response_sync: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
if rst = '1' then
|
||||
wb_out.ack <= '0';
|
||||
wb_out.stall <= '0';
|
||||
else
|
||||
-- Latch wb responses as well for 1 cycle. Stall is updated
|
||||
-- below
|
||||
wb_out <= wb_rsp;
|
||||
|
||||
-- Implement a stash buffer. If we are stalled and stash is
|
||||
-- free, fill it up. This will generate a WB stall on the
|
||||
-- next cycle.
|
||||
if wb_rsp.stall = '1' and wb_out.stall = '0' and
|
||||
wb_in.cyc = '1' and wb_in.stb = '1' then
|
||||
wb_stash <= wb_in;
|
||||
wb_out.stall <= '1';
|
||||
end if;
|
||||
|
||||
-- We aren't stalled, see what we can do
|
||||
if wb_rsp.stall = '0' then
|
||||
if wb_out.stall = '1' then
|
||||
-- Something in stash ! use it and clear stash
|
||||
wb_req <= wb_stash;
|
||||
wb_out.stall <= '0';
|
||||
else
|
||||
-- Nothing in stash, grab request from WB
|
||||
if wb_in.cyc = '1' then
|
||||
wb_req <= wb_in;
|
||||
else
|
||||
wb_req.cyc <= wb_in.cyc;
|
||||
wb_req.stb <= wb_in.stb;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
wb_response_comb: process(all)
|
||||
begin
|
||||
-- Defaults
|
||||
wb_rsp.ack <= '0';
|
||||
wb_rsp.dat <= x"00" & d_rx & d_rx & d_rx;
|
||||
wb_rsp.stall <= '0';
|
||||
|
||||
-- Depending on the access type...
|
||||
if wb_map_valid = '1' then
|
||||
|
||||
-- Memory map access
|
||||
wb_rsp.stall <= not auto_ack; -- XXX FIXME: Allow pipelining
|
||||
wb_rsp.ack <= auto_ack;
|
||||
wb_rsp.dat <= auto_data;
|
||||
|
||||
elsif ctrl_cs = '1' and wb_reg = SPI_REG_DATA then
|
||||
|
||||
-- Data register in manual mode
|
||||
--
|
||||
-- Stall stores if there's a pending read to avoid
|
||||
-- acks colliding. Otherwise accept all accesses
|
||||
-- immediately if rxtx is ready.
|
||||
--
|
||||
-- Note: This must match the logic setting cmd_valid
|
||||
-- in wb_request_comb.
|
||||
--
|
||||
-- We also ack stores immediately when accepted. Loads
|
||||
-- are handled separately further down.
|
||||
--
|
||||
if wb_req.we = '1' and pending_read = '1' then
|
||||
wb_rsp.stall <= '1';
|
||||
else
|
||||
wb_rsp.ack <= wb_req.we and cmd_ready;
|
||||
wb_rsp.stall <= not cmd_ready;
|
||||
end if;
|
||||
|
||||
-- Note: loads acks are handled elsewhere
|
||||
elsif wb_reg_valid = '1' then
|
||||
|
||||
-- Normal register access
|
||||
--
|
||||
-- Normally single cycle but ensure any auto-mode or manual
|
||||
-- operation is complete first
|
||||
--
|
||||
if auto_state = AUTO_IDLE and bus_idle = '1' then
|
||||
wb_rsp.ack <= '1';
|
||||
wb_rsp.stall <= '0';
|
||||
|
||||
case wb_reg is
|
||||
when SPI_REG_CTRL =>
|
||||
wb_rsp.dat <= (ctrl_reg'range => ctrl_reg, others => '0');
|
||||
when SPI_REG_AUTO_CFG =>
|
||||
wb_rsp.dat <= (auto_cfg_reg'range => auto_cfg_reg, others => '0');
|
||||
when others => null;
|
||||
end case;
|
||||
else
|
||||
wb_rsp.stall <= '1';
|
||||
end if;
|
||||
end if;
|
||||
|
||||
-- For loads in manual mode, we've accepted the command early
|
||||
-- so none of the above connditions might be true. We thus need
|
||||
-- to send the ack whenever we are getting it from rxtx.
|
||||
--
|
||||
-- This shouldn't collide with any of the above acks because we hold
|
||||
-- normal register accesses and stores when there is a pending
|
||||
-- load or the bus is busy.
|
||||
--
|
||||
if ctrl_cs = '1' and d_ack = '1' then
|
||||
assert pending_read = '1' report "d_ack without pending read !" severity failure;
|
||||
wb_rsp.ack <= '1';
|
||||
end if;
|
||||
end process;
|
||||
|
||||
-- Automatic mode state machine
|
||||
auto_sync: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
auto_state <= auto_next;
|
||||
auto_cnt <= auto_cnt_next;
|
||||
auto_data <= auto_data_next;
|
||||
if auto_latch_adr = '1' then
|
||||
auto_last_addr <= auto_lad_next;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
auto_comb: process(all)
|
||||
variable addr : std_ulogic_vector(31 downto 0);
|
||||
variable req_is_next : boolean;
|
||||
|
||||
function mode_to_clks(mode: std_ulogic_vector(1 downto 0)) return std_ulogic_vector is
|
||||
begin
|
||||
if mode = SPI_AUTO_CFG_MODE_QUAD then
|
||||
return "001";
|
||||
elsif mode = SPI_AUTO_CFG_MODE_DUAL then
|
||||
return "011";
|
||||
else
|
||||
return "111";
|
||||
end if;
|
||||
end function;
|
||||
begin
|
||||
-- Default outputs
|
||||
auto_ack <= '0';
|
||||
auto_cs <= '0';
|
||||
auto_cmd_valid <= '0';
|
||||
auto_d_txd <= x"00";
|
||||
auto_cmd_mode <= "001";
|
||||
auto_d_clks <= "111";
|
||||
auto_latch_adr <= '0';
|
||||
|
||||
-- Default next state
|
||||
auto_next <= auto_state;
|
||||
auto_cnt_next <= auto_cnt;
|
||||
auto_data_next <= auto_data;
|
||||
|
||||
-- Convert wishbone address into a flash address. We mask
|
||||
-- off the 4 top address bits to get rid of the "f" there.
|
||||
addr := "00" & wb_req.adr(29 downto 2) & "00";
|
||||
|
||||
-- Calculate the next address for store & compare later
|
||||
auto_lad_next <= std_ulogic_vector(unsigned(addr) + 4);
|
||||
|
||||
-- Match incoming request address with next address
|
||||
req_is_next := addr = auto_last_addr;
|
||||
|
||||
-- XXX TODO:
|
||||
-- - Support < 32-bit accesses
|
||||
|
||||
-- Reset
|
||||
if rst = '1' or ctrl_reset = '1' then
|
||||
auto_cs <= '0';
|
||||
auto_cnt_next <= 0;
|
||||
auto_next <= AUTO_IDLE;
|
||||
else
|
||||
-- Run counter
|
||||
if auto_cnt /= 0 then
|
||||
auto_cnt_next <= auto_cnt - 1;
|
||||
end if;
|
||||
|
||||
-- Automatic CS is set whenever state isn't IDLE or RECOVERY
|
||||
if auto_state /= AUTO_IDLE and
|
||||
auto_state /= AUTO_RECOVERY then
|
||||
auto_cs <= '1';
|
||||
end if;
|
||||
|
||||
-- State machine
|
||||
case auto_state is
|
||||
when AUTO_IDLE =>
|
||||
-- Access to the memory map only when manual CS isn't set
|
||||
if wb_map_valid = '1' and ctrl_cs = '0' then
|
||||
-- Ignore writes, we don't support them yet
|
||||
if wb_req.we = '1' then
|
||||
auto_ack <= '1';
|
||||
else
|
||||
-- Start machine with CS assertion delay
|
||||
auto_next <= AUTO_CS_ON;
|
||||
auto_cnt_next <= CS_DELAY_ASSERT;
|
||||
end if;
|
||||
end if;
|
||||
when AUTO_CS_ON =>
|
||||
if auto_cnt = 0 then
|
||||
-- CS asserted long enough, send command
|
||||
auto_next <= AUTO_CMD;
|
||||
end if;
|
||||
when AUTO_CMD =>
|
||||
auto_d_txd <= auto_cfg_cmd;
|
||||
auto_cmd_valid <= '1';
|
||||
if cmd_ready = '1' then
|
||||
if auto_cfg_addr4 = '1' then
|
||||
auto_next <= AUTO_ADR3;
|
||||
else
|
||||
auto_next <= AUTO_ADR2;
|
||||
end if;
|
||||
end if;
|
||||
when AUTO_ADR3 =>
|
||||
auto_d_txd <= addr(31 downto 24);
|
||||
auto_cmd_valid <= '1';
|
||||
if cmd_ready = '1' then
|
||||
auto_next <= AUTO_ADR2;
|
||||
end if;
|
||||
when AUTO_ADR2 =>
|
||||
auto_d_txd <= addr(23 downto 16);
|
||||
auto_cmd_valid <= '1';
|
||||
if cmd_ready = '1' then
|
||||
auto_next <= AUTO_ADR1;
|
||||
end if;
|
||||
when AUTO_ADR1 =>
|
||||
auto_d_txd <= addr(15 downto 8);
|
||||
auto_cmd_valid <= '1';
|
||||
if cmd_ready = '1' then
|
||||
auto_next <= AUTO_ADR0;
|
||||
end if;
|
||||
when AUTO_ADR0 =>
|
||||
auto_d_txd <= addr(7 downto 0);
|
||||
auto_cmd_valid <= '1';
|
||||
if cmd_ready = '1' then
|
||||
if auto_cfg_dummies = "000" then
|
||||
auto_next <= AUTO_DAT0;
|
||||
else
|
||||
auto_next <= AUTO_DUMMY;
|
||||
end if;
|
||||
end if;
|
||||
when AUTO_DUMMY =>
|
||||
auto_cmd_valid <= '1';
|
||||
auto_d_clks <= auto_cfg_dummies;
|
||||
if cmd_ready = '1' then
|
||||
auto_next <= AUTO_DAT0;
|
||||
end if;
|
||||
when AUTO_DAT0 =>
|
||||
auto_cmd_valid <= '1';
|
||||
auto_cmd_mode <= auto_cfg_mode & "0";
|
||||
auto_d_clks <= mode_to_clks(auto_cfg_mode);
|
||||
if cmd_ready = '1' then
|
||||
auto_next <= AUTO_DAT0_DATA;
|
||||
end if;
|
||||
when AUTO_DAT0_DATA =>
|
||||
if d_ack = '1' then
|
||||
auto_data_next(7 downto 0) <= d_rx;
|
||||
auto_next <= AUTO_DAT1;
|
||||
end if;
|
||||
when AUTO_DAT1 =>
|
||||
auto_cmd_valid <= '1';
|
||||
auto_cmd_mode <= auto_cfg_mode & "0";
|
||||
auto_d_clks <= mode_to_clks(auto_cfg_mode);
|
||||
if cmd_ready = '1' then
|
||||
auto_next <= AUTO_DAT1_DATA;
|
||||
end if;
|
||||
when AUTO_DAT1_DATA =>
|
||||
if d_ack = '1' then
|
||||
auto_data_next(15 downto 8) <= d_rx;
|
||||
auto_next <= AUTO_DAT2;
|
||||
end if;
|
||||
when AUTO_DAT2 =>
|
||||
auto_cmd_valid <= '1';
|
||||
auto_cmd_mode <= auto_cfg_mode & "0";
|
||||
auto_d_clks <= mode_to_clks(auto_cfg_mode);
|
||||
if cmd_ready = '1' then
|
||||
auto_next <= AUTO_DAT2_DATA;
|
||||
end if;
|
||||
when AUTO_DAT2_DATA =>
|
||||
if d_ack = '1' then
|
||||
auto_data_next(23 downto 16) <= d_rx;
|
||||
auto_next <= AUTO_DAT3;
|
||||
end if;
|
||||
when AUTO_DAT3 =>
|
||||
auto_cmd_valid <= '1';
|
||||
auto_cmd_mode <= auto_cfg_mode & "0";
|
||||
auto_d_clks <= mode_to_clks(auto_cfg_mode);
|
||||
if cmd_ready = '1' then
|
||||
auto_next <= AUTO_DAT3_DATA;
|
||||
end if;
|
||||
when AUTO_DAT3_DATA =>
|
||||
if d_ack = '1' then
|
||||
auto_data_next(31 downto 24) <= d_rx;
|
||||
auto_next <= AUTO_SEND_ACK;
|
||||
auto_latch_adr <= '1';
|
||||
end if;
|
||||
when AUTO_SEND_ACK =>
|
||||
auto_ack <= '1';
|
||||
auto_cnt_next <= to_integer(unsigned(auto_cfg_cstout));
|
||||
auto_next <= AUTO_WAIT_REQ;
|
||||
when AUTO_WAIT_REQ =>
|
||||
-- Incoming bus request we can take ? Otherwise do we need
|
||||
-- to cancel the wait ?
|
||||
if wb_map_valid = '1' and req_is_next and wb_req.we = '0' then
|
||||
auto_next <= AUTO_DAT0;
|
||||
elsif wb_map_valid = '1' or wb_reg_valid = '1' or auto_cnt = 0 then
|
||||
-- This means we can drop the CS right on the next clock.
|
||||
-- We make the assumption here that the two cycles min
|
||||
-- spent in AUTO_SEND_ACK and AUTO_WAIT_REQ are long enough
|
||||
-- to deassert CS. If that doesn't hold true in the future,
|
||||
-- add another state.
|
||||
auto_cnt_next <= CS_DELAY_RECOVERY;
|
||||
auto_next <= AUTO_RECOVERY;
|
||||
end if;
|
||||
when AUTO_RECOVERY =>
|
||||
if auto_cnt = 0 then
|
||||
auto_next <= AUTO_IDLE;
|
||||
end if;
|
||||
end case;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
-- Register write sync machine
|
||||
reg_write: process(clk)
|
||||
function reg_wr(r : in std_ulogic_vector;
|
||||
w : in wb_io_master_out) return std_ulogic_vector is
|
||||
variable b : natural range 0 to 31;
|
||||
variable t : std_ulogic_vector(r'range);
|
||||
begin
|
||||
t := r;
|
||||
for i in r'range loop
|
||||
if w.sel(i/8) = '1' then
|
||||
t(i) := w.dat(i);
|
||||
end if;
|
||||
end loop;
|
||||
return t;
|
||||
end function;
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
-- Reset auto-clear
|
||||
if rst = '1' or ctrl_reset = '1' then
|
||||
ctrl_reset <= '0';
|
||||
ctrl_cs <= '0';
|
||||
ctrl_rsrv1 <= '0';
|
||||
ctrl_rsrv2 <= '0';
|
||||
ctrl_div <= std_ulogic_vector(to_unsigned(DEF_CLK_DIV, 8));
|
||||
if DEF_QUAD_READ then
|
||||
auto_cfg_cmd <= x"6b";
|
||||
auto_cfg_dummies <= "111";
|
||||
auto_cfg_mode <= SPI_AUTO_CFG_MODE_QUAD;
|
||||
else
|
||||
auto_cfg_cmd <= x"03";
|
||||
auto_cfg_dummies <= "000";
|
||||
auto_cfg_mode <= SPI_AUTO_CFG_MODE_SINGLE;
|
||||
end if;
|
||||
auto_cfg_addr4 <= '0';
|
||||
auto_cfg_rsrv1 <= '0';
|
||||
auto_cfg_rsrv2 <= '0';
|
||||
auto_cfg_div <= std_ulogic_vector(to_unsigned(DEF_CLK_DIV, 8));
|
||||
auto_cfg_cstout <= std_ulogic_vector(to_unsigned(DEFAULT_CS_TIMEOUT, 6));
|
||||
end if;
|
||||
|
||||
if wb_reg_valid = '1' and wb_req.we = '1' and auto_state = AUTO_IDLE and bus_idle = '1' then
|
||||
if wb_reg = SPI_REG_CTRL then
|
||||
ctrl_reg <= reg_wr(ctrl_reg, wb_req);
|
||||
end if;
|
||||
if wb_reg = SPI_REG_AUTO_CFG then
|
||||
auto_cfg_reg <= reg_wr(auto_cfg_reg, wb_req);
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
end architecture;
|
@ -0,0 +1,386 @@
|
||||
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;
|
||||
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;
|
Loading…
Reference in New Issue