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.
625 lines
23 KiB
VHDL
625 lines
23 KiB
VHDL
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
|
|
|
|
-- Dummy clocks after boot
|
|
BOOT_CLOCKS : boolean := true; -- Send 8 dummy clocks after boot
|
|
|
|
-- 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 0, 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);
|
|
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_BOOT, 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_BOOT;
|
|
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 0) 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 <= not wb_req.we;
|
|
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';
|
|
wb_stash.cyc <= '0';
|
|
wb_stash.stb <= '0';
|
|
wb_stash.sel <= (others => '0');
|
|
wb_stash.we <= '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
|
|
if rst = '1' then
|
|
auto_last_addr <= (others => '0');
|
|
auto_state <= AUTO_BOOT;
|
|
else
|
|
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 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(27 downto 0) & "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_BOOT;
|
|
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 or BOOT
|
|
if auto_state /= AUTO_IDLE and
|
|
auto_state /= AUTO_RECOVERY and
|
|
auto_state /= AUTO_BOOT then
|
|
auto_cs <= '1';
|
|
end if;
|
|
|
|
-- State machine
|
|
case auto_state is
|
|
when AUTO_BOOT =>
|
|
if BOOT_CLOCKS then
|
|
auto_cmd_valid <= '1';
|
|
if cmd_ready = '1' then
|
|
auto_next <= AUTO_IDLE;
|
|
end if;
|
|
else
|
|
auto_next <= AUTO_IDLE;
|
|
end if;
|
|
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;
|
|
|