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.

618 lines
31 KiB
VHDL

-- #################################################################################################
-- # << NEORV32 - Execute In Place (XIP) Module >> #
-- # ********************************************************************************************* #
-- # This module allows the CPU to execute code (and read constant data) directly from an SPI #
-- # flash memory. Two host ports are implemented: one for accessing the control and status #
-- # registers (mapped to the processor's IO space) and one for the actual instruction/data fetch. #
-- # The actual address space mapping of the "instruction/data interface" is done by programming #
-- # special control register bits. #
-- # ********************************************************************************************* #
-- # BSD 3-Clause License #
-- # #
-- # Copyright (c) 2022, Stephan Nolting. All rights reserved. #
-- # #
-- # Redistribution and use in source and binary forms, with or without modification, are #
-- # permitted provided that the following conditions are met: #
-- # #
-- # 1. Redistributions of source code must retain the above copyright notice, this list of #
-- # conditions and the following disclaimer. #
-- # #
-- # 2. Redistributions in binary form must reproduce the above copyright notice, this list of #
-- # conditions and the following disclaimer in the documentation and/or other materials #
-- # provided with the distribution. #
-- # #
-- # 3. Neither the name of the copyright holder nor the names of its contributors may be used to #
-- # endorse or promote products derived from this software without specific prior written #
-- # permission. #
-- # #
-- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS #
-- # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF #
-- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE #
-- # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, #
-- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE #
-- # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED #
-- # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING #
-- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED #
-- # OF THE POSSIBILITY OF SUCH DAMAGE. #
-- # ********************************************************************************************* #
-- # The NEORV32 Processor - https://github.com/stnolting/neorv32 (c) Stephan Nolting #
-- #################################################################################################
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library neorv32;
use neorv32.neorv32_package.all;
entity neorv32_xip is
port (
-- global control --
clk_i : in std_ulogic; -- global clock line
rstn_i : in std_ulogic; -- global reset line, low-active, async
-- host access: control register access port --
ct_addr_i : in std_ulogic_vector(31 downto 0); -- address
ct_rden_i : in std_ulogic; -- read enable
ct_wren_i : in std_ulogic; -- write enable
ct_data_i : in std_ulogic_vector(31 downto 0); -- data in
ct_data_o : out std_ulogic_vector(31 downto 0); -- data out
ct_ack_o : out std_ulogic; -- transfer acknowledge
-- host access: transparent SPI access port (read-only) --
acc_addr_i : in std_ulogic_vector(31 downto 0); -- address
acc_rden_i : in std_ulogic; -- read enable
acc_wren_i : in std_ulogic; -- write enable
acc_data_o : out std_ulogic_vector(31 downto 0); -- data out
acc_ack_o : out std_ulogic; -- transfer acknowledge
acc_err_o : out std_ulogic; -- transfer error
-- status --
xip_en_o : out std_ulogic; -- XIP enable
xip_acc_o : out std_ulogic; -- pending XIP access
xip_page_o : out std_ulogic_vector(03 downto 0); -- XIP page
-- clock generator --
clkgen_en_o : out std_ulogic; -- enable clock generator
clkgen_i : in std_ulogic_vector(07 downto 0);
-- SPI device interface --
spi_csn_o : out std_ulogic; -- chip-select, low-active
spi_clk_o : out std_ulogic; -- serial clock
spi_data_i : in std_ulogic; -- device data output
spi_data_o : out std_ulogic -- controller data output
);
end neorv32_xip;
architecture neorv32_xip_rtl of neorv32_xip is
-- IO space: module base address --
constant hi_abb_c : natural := index_size_f(io_size_c)-1; -- high address boundary bit
constant lo_abb_c : natural := index_size_f(xip_size_c); -- low address boundary bit
-- CT register access control --
signal ct_acc_en : std_ulogic; -- module access enable
signal ct_addr : std_ulogic_vector(31 downto 0); -- access address
signal ct_wren : std_ulogic; -- word write enable
signal ct_rden : std_ulogic; -- read enable
-- control register --
constant ctrl_enable_c : natural := 0; -- r/w: module enable
constant ctrl_spi_prsc0_c : natural := 1; -- r/w: SPI clock prescaler select - bit 0
constant ctrl_spi_prsc1_c : natural := 2; -- r/w: SPI clock prescaler select - bit 1
constant ctrl_spi_prsc2_c : natural := 3; -- r/w: SPI clock prescaler select - bit 2
constant ctrl_spi_cpol_c : natural := 4; -- r/w: SPI (idle) clock polarity
constant ctrl_spi_cpha_c : natural := 5; -- r/w: SPI clock phase
constant ctrl_spi_nbytes0_c : natural := 6; -- r/w: SPI number of bytes in transmission (1..9) - bit 0
constant ctrl_spi_nbytes3_c : natural := 9; -- r/w: SPI number of bytes in transmission (1..9) - bit 3
constant ctrl_xip_enable_c : natural := 10; -- r/w: XIP access mode enable
constant ctrl_xip_abytes0_c : natural := 11; -- r/w: XIP number of address bytes (0=1,1=2,2=3,3=4) - bit 0
constant ctrl_xip_abytes1_c : natural := 12; -- r/w: XIP number of address bytes (0=1,1=2,2=3,3=4) - bit 1
constant ctrl_rd_cmd0_c : natural := 13; -- r/w: SPI flash read command - bit 0
constant ctrl_rd_cmd7_c : natural := 20; -- r/w: SPI flash read command - bit 7
constant ctrl_page0_c : natural := 21; -- r/w: XIP memory page - bit 0
constant ctrl_page3_c : natural := 24; -- r/w: XIP memory page - bit 3
constant ctrl_spi_csen_c : natural := 25; -- r/w: SPI chip-select enabled
constant ctrl_highspeed_c : natural := 26; -- r/w: SPI high-speed mode enable (ignoring ctrl_spi_prsc)
constant ctrl_burst_en_c : natural := 27; -- r/w: XIP burst mode enable
--
constant ctrl_phy_busy_c : natural := 30; -- r/-: SPI PHY is busy when set
constant ctrl_xip_busy_c : natural := 31; -- r/-: XIP access in progress
--
signal ctrl : std_ulogic_vector(27 downto 0);
-- Direct SPI access registers --
signal spi_data_lo : std_ulogic_vector(31 downto 0);
signal spi_data_hi : std_ulogic_vector(31 downto 0); -- write-only!
signal spi_trigger : std_ulogic; -- trigger direct SPI operation
-- XIP access address --
signal xip_addr : std_ulogic_vector(31 downto 0);
-- SPI access fetch arbiter --
type arbiter_state_t is (S_DIRECT, S_IDLE, S_CHECK, S_TRIG, S_BUSY, S_ERROR);
type arbiter_t is record
state : arbiter_state_t;
state_nxt : arbiter_state_t;
addr : std_ulogic_vector(31 downto 0);
addr_lookahead : std_ulogic_vector(31 downto 0);
busy : std_ulogic;
tmo_cnt : std_ulogic_vector(04 downto 0); -- timeout counter for auto CS de-assert (burst mode only)
end record;
signal arbiter : arbiter_t;
-- SPI clock --
signal spi_clk_en : std_ulogic;
-- Component: SPI PHY --
component neorv32_xip_phy
port (
-- global control --
clk_i : in std_ulogic; -- clock
spi_clk_en_i : in std_ulogic; -- pre-scaled SPI clock-enable
-- operation configuration --
cf_enable_i : in std_ulogic; -- module enable (reset if low)
cf_cpha_i : in std_ulogic; -- clock phase
cf_cpol_i : in std_ulogic; -- clock idle polarity
-- operation control --
op_start_i : in std_ulogic; -- trigger new transmission
op_final_i : in std_ulogic; -- end current transmission
op_csen_i : in std_ulogic; -- actually enabled device for transmission
op_busy_o : out std_ulogic; -- transmission in progress when set
op_nbytes_i : in std_ulogic_vector(03 downto 0); -- actual number of bytes to transmit (1..9)
op_wdata_i : in std_ulogic_vector(71 downto 0); -- write data
op_rdata_o : out std_ulogic_vector(31 downto 0); -- read data
-- SPI interface --
spi_csn_o : out std_ulogic;
spi_clk_o : out std_ulogic;
spi_data_i : in std_ulogic;
spi_data_o : out std_ulogic
);
end component;
-- SPI PHY interface --
type phy_if_t is record
start : std_ulogic; -- trigger new transmission
final : std_ulogic; -- stop current transmission
busy : std_ulogic; -- transmission in progress when set
wdata : std_ulogic_vector(71 downto 0); -- write data
rdata : std_ulogic_vector(31 downto 0); -- read data
end record;
signal phy_if : phy_if_t;
begin
-- Access Control (IO/CTRL port) ----------------------------------------------------------
-- -------------------------------------------------------------------------------------------
ct_acc_en <= '1' when (ct_addr_i(hi_abb_c downto lo_abb_c) = xip_base_c(hi_abb_c downto lo_abb_c)) else '0';
ct_addr <= xip_base_c(31 downto lo_abb_c) & ct_addr_i(lo_abb_c-1 downto 2) & "00"; -- word aligned
ct_wren <= ct_acc_en and ct_wren_i;
ct_rden <= ct_acc_en and ct_rden_i;
-- Control Write Access -------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
ctrl_write_access : process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
ctrl <= (others => '0');
spi_data_lo <= (others => '0');
spi_data_hi <= (others => '0');
spi_trigger <= '0';
elsif rising_edge(clk_i) then
spi_trigger <= '0';
if (ct_wren = '1') then -- only full-word writes!
-- control register --
if (ct_addr = xip_ctrl_addr_c) then
ctrl(ctrl_enable_c) <= ct_data_i(ctrl_enable_c);
ctrl(ctrl_spi_prsc2_c downto ctrl_spi_prsc0_c) <= ct_data_i(ctrl_spi_prsc2_c downto ctrl_spi_prsc0_c);
ctrl(ctrl_spi_cpol_c) <= ct_data_i(ctrl_spi_cpol_c);
ctrl(ctrl_spi_cpha_c) <= ct_data_i(ctrl_spi_cpha_c);
ctrl(ctrl_spi_nbytes3_c downto ctrl_spi_nbytes0_c) <= ct_data_i(ctrl_spi_nbytes3_c downto ctrl_spi_nbytes0_c);
ctrl(ctrl_xip_enable_c) <= ct_data_i(ctrl_xip_enable_c);
ctrl(ctrl_xip_abytes1_c downto ctrl_xip_abytes0_c) <= ct_data_i(ctrl_xip_abytes1_c downto ctrl_xip_abytes0_c);
ctrl(ctrl_rd_cmd7_c downto ctrl_rd_cmd0_c) <= ct_data_i(ctrl_rd_cmd7_c downto ctrl_rd_cmd0_c);
ctrl(ctrl_page3_c downto ctrl_page0_c) <= ct_data_i(ctrl_page3_c downto ctrl_page0_c);
ctrl(ctrl_spi_csen_c) <= ct_data_i(ctrl_spi_csen_c);
ctrl(ctrl_highspeed_c) <= ct_data_i(ctrl_highspeed_c);
ctrl(ctrl_burst_en_c) <= ct_data_i(ctrl_burst_en_c);
end if;
-- SPI direct data access register lo --
if (ct_addr = xip_data_lo_addr_c) then
spi_data_lo <= ct_data_i;
end if;
-- SPI direct data access register hi --
if (ct_addr = xip_data_hi_addr_c) then
spi_data_hi <= ct_data_i;
spi_trigger <= '1'; -- trigger direct SPI transaction
end if;
end if;
end if;
end process ctrl_write_access;
-- XIP enabled --
xip_en_o <= ctrl(ctrl_enable_c);
-- XIP page output --
xip_page_o <= ctrl(ctrl_page3_c downto ctrl_page0_c);
-- Control Read Access --------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
ctrl_read_access : process(clk_i)
begin
if rising_edge(clk_i) then
ct_ack_o <= ct_wren or ct_rden; -- access acknowledge
ct_data_o <= (others => '0');
if (ct_rden = '1') then
case ct_addr(3 downto 2) is
when "00" => -- 'xip_ctrl_addr_c' - control register
ct_data_o(ctrl_enable_c) <= ctrl(ctrl_enable_c);
ct_data_o(ctrl_spi_prsc2_c downto ctrl_spi_prsc0_c) <= ctrl(ctrl_spi_prsc2_c downto ctrl_spi_prsc0_c);
ct_data_o(ctrl_spi_cpol_c) <= ctrl(ctrl_spi_cpol_c);
ct_data_o(ctrl_spi_cpha_c) <= ctrl(ctrl_spi_cpha_c);
ct_data_o(ctrl_spi_nbytes3_c downto ctrl_spi_nbytes0_c) <= ctrl(ctrl_spi_nbytes3_c downto ctrl_spi_nbytes0_c);
ct_data_o(ctrl_xip_enable_c) <= ctrl(ctrl_xip_enable_c);
ct_data_o(ctrl_xip_abytes1_c downto ctrl_xip_abytes0_c) <= ctrl(ctrl_xip_abytes1_c downto ctrl_xip_abytes0_c);
ct_data_o(ctrl_rd_cmd7_c downto ctrl_rd_cmd0_c) <= ctrl(ctrl_rd_cmd7_c downto ctrl_rd_cmd0_c);
ct_data_o(ctrl_page3_c downto ctrl_page0_c) <= ctrl(ctrl_page3_c downto ctrl_page0_c);
ct_data_o(ctrl_spi_csen_c) <= ctrl(ctrl_spi_csen_c);
ct_data_o(ctrl_highspeed_c) <= ctrl(ctrl_highspeed_c);
ct_data_o(ctrl_burst_en_c) <= ctrl(ctrl_burst_en_c);
--
ct_data_o(ctrl_phy_busy_c) <= phy_if.busy;
ct_data_o(ctrl_xip_busy_c) <= arbiter.busy;
when "10" => -- 'xip_data_lo_addr_c' - SPI direct data access register lo
ct_data_o <= phy_if.rdata;
when others => -- unavailable (not implemented or write-only)
ct_data_o <= (others => '0');
end case;
end if;
end if;
end process ctrl_read_access;
-- XIP Address Computation Logic ----------------------------------------------------------
-- -------------------------------------------------------------------------------------------
xip_access_logic: process(arbiter.addr, ctrl)
variable tmp_v : std_ulogic_vector(31 downto 0);
begin
tmp_v(31 downto 28) := "0000";
tmp_v(27 downto 02) := arbiter.addr(27 downto 02);
tmp_v(01 downto 00) := "00"; -- always align to 32-bit boundary; sub-word read accesses are handled by the CPU logic
case ctrl(ctrl_xip_abytes1_c downto ctrl_xip_abytes0_c) is -- shift address bits to be MSB-aligned
when "00" => xip_addr <= tmp_v(07 downto 0) & x"000000"; -- 1 address byte
when "01" => xip_addr <= tmp_v(15 downto 0) & x"0000"; -- 2 address bytes
when "10" => xip_addr <= tmp_v(23 downto 0) & x"00"; -- 3 address bytes
when others => xip_addr <= tmp_v(31 downto 0); -- 4 address bytes
end case;
end process xip_access_logic;
-- SPI Access Arbiter ---------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
arbiter_sync: process(clk_i)
begin
if rising_edge(clk_i) then
-- state control --
if (ctrl(ctrl_enable_c) = '0') or (ctrl(ctrl_xip_enable_c) = '0') then -- sync reset
arbiter.state <= S_DIRECT;
else
arbiter.state <= arbiter.state_nxt;
end if;
-- address look-ahead --
if (acc_rden_i = '1') and (acc_addr_i(31 downto 28) = ctrl(ctrl_page3_c downto ctrl_page0_c)) then
arbiter.addr <= acc_addr_i; -- buffer address (reducing fan-out on CPU's address net)
end if;
arbiter.addr_lookahead <= std_ulogic_vector(unsigned(arbiter.addr) + 4); -- prefetch address of *next* linear access
-- pending flash access timeout --
if (ctrl(ctrl_enable_c) = '0') or (ctrl(ctrl_xip_enable_c) = '0') or (arbiter.state = S_BUSY) then -- sync reset
arbiter.tmo_cnt <= (others => '0');
elsif (arbiter.tmo_cnt(arbiter.tmo_cnt'left) = '0') then -- stop if maximum reached
arbiter.tmo_cnt <= std_ulogic_vector(unsigned(arbiter.tmo_cnt) + 1);
end if;
end if;
end process arbiter_sync;
-- FSM - combinatorial part --
arbiter_comb: process(arbiter, ctrl, xip_addr, phy_if, acc_rden_i, acc_wren_i, acc_addr_i, spi_data_hi, spi_data_lo, spi_trigger)
begin
-- arbiter defaults --
arbiter.state_nxt <= arbiter.state;
-- bus interface defaults --
acc_data_o <= (others => '0');
acc_ack_o <= '0';
acc_err_o <= '0';
-- SPI PHY interface defaults --
phy_if.start <= '0';
phy_if.final <= arbiter.tmo_cnt(arbiter.tmo_cnt'left) or (not ctrl(ctrl_burst_en_c)); -- terminate if timeout or if burst mode not enabled
phy_if.wdata <= ctrl(ctrl_rd_cmd7_c downto ctrl_rd_cmd0_c) & xip_addr & x"00000000"; -- MSB-aligned: CMD + address + 32-bit zero data
-- fsm --
case arbiter.state is
when S_DIRECT => -- XIP access disabled; direct SPI access
-- ------------------------------------------------------------
phy_if.wdata <= spi_data_hi & spi_data_lo & x"00"; -- MSB-aligned data
phy_if.start <= spi_trigger;
phy_if.final <= '1'; -- do not keep CS active after transmission is done
arbiter.state_nxt <= S_IDLE;
when S_IDLE => -- wait for new bus request
-- ------------------------------------------------------------
if (acc_addr_i(31 downto 28) = ctrl(ctrl_page3_c downto ctrl_page0_c)) then
if (acc_rden_i = '1') then
arbiter.state_nxt <= S_CHECK;
elsif (acc_wren_i = '1') then
arbiter.state_nxt <= S_ERROR;
end if;
end if;
when S_CHECK => -- check if we can resume flash access
-- ------------------------------------------------------------
if (arbiter.addr(27 downto 2) = arbiter.addr_lookahead(27 downto 2)) and (ctrl(ctrl_burst_en_c) = '1') and -- access to *next linear* address
(arbiter.tmo_cnt(arbiter.tmo_cnt'left) = '0') then -- no "pending access" timeout yet
phy_if.start <= '1'; -- resume flash access
arbiter.state_nxt <= S_BUSY;
else
phy_if.final <= '1'; -- restart flash access
arbiter.state_nxt <= S_TRIG;
end if;
when S_TRIG => -- trigger NEW flash read
-- ------------------------------------------------------------
phy_if.start <= '1';
arbiter.state_nxt <= S_BUSY;
when S_BUSY => -- wait for PHY to complete operation
-- ------------------------------------------------------------
acc_data_o <= bswap32_f(phy_if.rdata); -- convert incrementing byte-read to little-endian
if (phy_if.busy = '0') then
acc_ack_o <= '1';
arbiter.state_nxt <= S_IDLE;
end if;
when S_ERROR => -- access error
-- ------------------------------------------------------------
acc_err_o <= '1';
arbiter.state_nxt <= S_IDLE;
when others => -- undefined
-- ------------------------------------------------------------
arbiter.state_nxt <= S_IDLE;
end case;
end process arbiter_comb;
-- arbiter status --
arbiter.busy <= '1' when (arbiter.state = S_TRIG) or (arbiter.state = S_BUSY) else '0'; -- actual XIP access in progress
-- status output --
xip_acc_o <= arbiter.busy;
-- SPI Clock Generator --------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
-- enable clock generator --
clkgen_en_o <= ctrl(ctrl_enable_c);
-- clock select --
spi_clk_en <= clkgen_i(to_integer(unsigned(ctrl(ctrl_spi_prsc2_c downto ctrl_spi_prsc0_c)))) or ctrl(ctrl_highspeed_c);
-- SPI Physical Interface -----------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
neorv32_xip_phy_inst: neorv32_xip_phy
port map (
-- global control --
clk_i => clk_i,
spi_clk_en_i => spi_clk_en,
-- operation configuration --
cf_enable_i => ctrl(ctrl_enable_c), -- module enable (reset if low)
cf_cpha_i => ctrl(ctrl_spi_cpha_c), -- clock phase
cf_cpol_i => ctrl(ctrl_spi_cpol_c), -- clock idle polarity
-- operation control --
op_start_i => phy_if.start, -- trigger new transmission
op_final_i => phy_if.final, -- end current transmission
op_csen_i => ctrl(ctrl_spi_csen_c), -- actually enabled device for transmission
op_busy_o => phy_if.busy, -- transmission in progress when set
op_nbytes_i => ctrl(ctrl_spi_nbytes3_c downto ctrl_spi_nbytes0_c), -- actual number of bytes to transmit
op_wdata_i => phy_if.wdata, -- write data
op_rdata_o => phy_if.rdata, -- read data
-- SPI interface --
spi_csn_o => spi_csn_o,
spi_clk_o => spi_clk_o,
spi_data_i => spi_data_i,
spi_data_o => spi_data_o
);
end neorv32_xip_rtl;
-- ############################################################################################################################
-- ############################################################################################################################
-- #################################################################################################
-- # << NEORV32 - XIP Module - SPI Physical Interface >> #
-- # ********************************************************************************************* #
-- # BSD 3-Clause License #
-- # #
-- # Copyright (c) 2022, Stephan Nolting. All rights reserved. #
-- # #
-- # Redistribution and use in source and binary forms, with or without modification, are #
-- # permitted provided that the following conditions are met: #
-- # #
-- # 1. Redistributions of source code must retain the above copyright notice, this list of #
-- # conditions and the following disclaimer. #
-- # #
-- # 2. Redistributions in binary form must reproduce the above copyright notice, this list of #
-- # conditions and the following disclaimer in the documentation and/or other materials #
-- # provided with the distribution. #
-- # #
-- # 3. Neither the name of the copyright holder nor the names of its contributors may be used to #
-- # endorse or promote products derived from this software without specific prior written #
-- # permission. #
-- # #
-- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS #
-- # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF #
-- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE #
-- # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, #
-- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE #
-- # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED #
-- # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING #
-- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED #
-- # OF THE POSSIBILITY OF SUCH DAMAGE. #
-- # ********************************************************************************************* #
-- # The NEORV32 Processor - https://github.com/stnolting/neorv32 (c) Stephan Nolting #
-- #################################################################################################
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library neorv32;
use neorv32.neorv32_package.all;
entity neorv32_xip_phy is
port (
-- global control --
clk_i : in std_ulogic; -- clock
spi_clk_en_i : in std_ulogic; -- pre-scaled SPI clock-enable
-- operation configuration --
cf_enable_i : in std_ulogic; -- module enable (reset if low)
cf_cpha_i : in std_ulogic; -- clock phase
cf_cpol_i : in std_ulogic; -- clock idle polarity
-- operation control --
op_start_i : in std_ulogic; -- trigger new transmission
op_final_i : in std_ulogic; -- end current transmission
op_csen_i : in std_ulogic; -- actually enabled device for transmission
op_busy_o : out std_ulogic; -- transmission in progress when set
op_nbytes_i : in std_ulogic_vector(03 downto 0); -- actual number of bytes to transmit (1..9)
op_wdata_i : in std_ulogic_vector(71 downto 0); -- write data
op_rdata_o : out std_ulogic_vector(31 downto 0); -- read data
-- SPI interface --
spi_csn_o : out std_ulogic;
spi_clk_o : out std_ulogic;
spi_data_i : in std_ulogic;
spi_data_o : out std_ulogic
);
end neorv32_xip_phy;
architecture neorv32_xip_phy_rtl of neorv32_xip_phy is
-- serial engine --
type ctrl_state_t is (S_IDLE, S_WAIT, S_START, S_SYNC, S_RTX_A, S_RTX_B, S_DONE);
type ctrl_t is record
state : ctrl_state_t;
sreg : std_ulogic_vector(71 downto 0); -- only the lowest 32-bit are used as RX data
bitcnt : std_ulogic_vector(06 downto 0);
di_sync : std_ulogic;
csen : std_ulogic;
end record;
signal ctrl : ctrl_t;
begin
-- Serial Interface Engine ----------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
serial_engine: process(clk_i)
begin
if rising_edge(clk_i) then
if (cf_enable_i = '0') then -- sync reset
spi_clk_o <= '0';
spi_csn_o <= '1';
ctrl.state <= S_IDLE;
ctrl.csen <= '0';
ctrl.sreg <= (others => '0');
ctrl.bitcnt <= (others => '0');
ctrl.di_sync <= '0';
else -- fsm
case ctrl.state is
when S_IDLE => -- wait for new transmission trigger
-- ------------------------------------------------------------
spi_csn_o <= '1'; -- flash disabled
spi_clk_o <= cf_cpol_i;
ctrl.bitcnt <= op_nbytes_i & "000"; -- number of bytes
ctrl.csen <= op_csen_i;
if (op_start_i = '1') then
ctrl.state <= S_START;
end if;
when S_START => -- start of transmission (keep current spi_csn_o state!)
-- ------------------------------------------------------------
ctrl.sreg <= op_wdata_i;
if (spi_clk_en_i = '1') then
ctrl.state <= S_SYNC;
end if;
when S_WAIT => -- wait for resume transmission trigger
-- ------------------------------------------------------------
spi_csn_o <= not ctrl.csen; -- keep CS active
ctrl.bitcnt <= "0100000"; -- 4 bytes = 32-bit read data
-- ctrl.sreg <= (others => '0'); -- do we need this???
if (op_final_i = '1') then -- terminate pending flash access
ctrl.state <= S_IDLE;
elsif (op_start_i = '1') then -- resume flash access
ctrl.state <= S_SYNC;
end if;
when S_SYNC => -- synchronize SPI clock
-- ------------------------------------------------------------
spi_csn_o <= not ctrl.csen; -- enable flash
if (spi_clk_en_i = '1') then
if (cf_cpha_i = '1') then -- clock phase shift
spi_clk_o <= not cf_cpol_i;
end if;
ctrl.state <= S_RTX_A;
end if;
when S_RTX_A => -- first half of bit transmission
-- ------------------------------------------------------------
if (spi_clk_en_i = '1') then
spi_clk_o <= not (cf_cpha_i xor cf_cpol_i);
ctrl.di_sync <= spi_data_i;
ctrl.bitcnt <= std_ulogic_vector(unsigned(ctrl.bitcnt) - 1);
ctrl.state <= S_RTX_B;
end if;
when S_RTX_B => -- second half of bit transmission
-- ------------------------------------------------------------
if (spi_clk_en_i = '1') then
ctrl.sreg <= ctrl.sreg(ctrl.sreg'left-1 downto 0) & ctrl.di_sync;
if (or_reduce_f(ctrl.bitcnt) = '0') then -- all bits transferred?
spi_clk_o <= cf_cpol_i;
ctrl.state <= S_DONE; -- transmission done
else
spi_clk_o <= cf_cpha_i xor cf_cpol_i;
ctrl.state <= S_RTX_A; -- next bit
end if;
end if;
when S_DONE => -- transmission done
-- ------------------------------------------------------------
if (spi_clk_en_i = '1') then
ctrl.state <= S_WAIT;
end if;
when others => -- undefined
-- ------------------------------------------------------------
ctrl.state <= S_IDLE;
end case;
end if;
end if;
end process serial_engine;
-- serial unit busy --
op_busy_o <= '0' when (ctrl.state = S_IDLE) or (ctrl.state = S_WAIT) else '1';
-- serial data output --
spi_data_o <= ctrl.sreg(ctrl.sreg'left);
-- RX data --
op_rdata_o <= ctrl.sreg(31 downto 0);
end neorv32_xip_phy_rtl;