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); 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); signal auto_cnt : integer range 0 to 63; signal auto_state : auto_state_t; 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; auto_cnt <= 0; auto_data <= (others => '0'); 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;