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.
626 lines
23 KiB
626 lines
23 KiB
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; |
|
|
|
|