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>
pull/204/head
Benjamin Herrenschmidt 4 years ago
parent 15467fe536
commit cc4dcb3597

@ -20,6 +20,9 @@ architecture behave of core_tb is
signal wb_dram_out : wishbone_slave_out; signal wb_dram_out : wishbone_slave_out;
signal wb_dram_ctrl_in : wb_io_master_out; signal wb_dram_ctrl_in : wb_io_master_out;
signal wb_dram_ctrl_out : wb_io_slave_out; signal wb_dram_ctrl_out : wb_io_slave_out;

-- Dummy SPI
signal spi_sdat_i : std_ulogic_vector(0 downto 0);
begin begin


soc0: entity work.soc soc0: entity work.soc
@ -28,19 +31,26 @@ begin
MEMORY_SIZE => (384*1024), MEMORY_SIZE => (384*1024),
RAM_INIT_FILE => "main_ram.bin", RAM_INIT_FILE => "main_ram.bin",
RESET_LOW => false, RESET_LOW => false,
CLK_FREQ => 100000000 CLK_FREQ => 100000000,
HAS_SPI_FLASH => false
) )
port map( port map(
rst => rst, rst => rst,
system_clk => clk, system_clk => clk,
uart0_rxd => '0', uart0_rxd => '0',
uart0_txd => open, uart0_txd => open,
spi_flash_sck => open,
spi_flash_cs_n => open,
spi_flash_sdat_o => open,
spi_flash_sdat_oe => open,
spi_flash_sdat_i => spi_sdat_i,
wb_dram_in => wb_dram_in, wb_dram_in => wb_dram_in,
wb_dram_out => wb_dram_out, wb_dram_out => wb_dram_out,
wb_dram_ctrl_in => wb_dram_ctrl_in, wb_dram_ctrl_in => wb_dram_ctrl_in,
wb_dram_ctrl_out => wb_dram_ctrl_out, wb_dram_ctrl_out => wb_dram_ctrl_out,
alt_reset => '0' alt_reset => '0'
); );
spi_sdat_i(0) <= '1';


clk_process: process clk_process: process
begin begin

@ -26,6 +26,22 @@ set_property -dict { PACKAGE_PIN E1 IOSTANDARD LVCMOS33 } [get_ports { led0_b }
set_property -dict { PACKAGE_PIN F6 IOSTANDARD LVCMOS33 } [get_ports { led0_g }]; set_property -dict { PACKAGE_PIN F6 IOSTANDARD LVCMOS33 } [get_ports { led0_g }];
set_property -dict { PACKAGE_PIN G6 IOSTANDARD LVCMOS33 } [get_ports { led0_r }]; set_property -dict { PACKAGE_PIN G6 IOSTANDARD LVCMOS33 } [get_ports { led0_r }];


################################################################################
# SPI Flash
################################################################################

set_property -dict { PACKAGE_PIN L13 IOSTANDARD LVCMOS33 } [get_ports { spi_flash_cs_n }];
set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { spi_flash_clk }];
set_property -dict { PACKAGE_PIN K17 IOSTANDARD LVCMOS33 } [get_ports { spi_flash_mosi }];
set_property -dict { PACKAGE_PIN K18 IOSTANDARD LVCMOS33 } [get_ports { spi_flash_miso }];
set_property -dict { PACKAGE_PIN L14 IOSTANDARD LVCMOS33 } [get_ports { spi_flash_wp_n }];
set_property -dict { PACKAGE_PIN M14 IOSTANDARD LVCMOS33 } [get_ports { spi_flash_hold_n }];

# Put registers into IOBs to improve timing
set_property IOB true [get_cells -hierarchical -filter {NAME =~*/spi_rxtx/*sck_1*}]
set_property IOB true [get_cells -hierarchical -filter {NAME =~*/spi_rxtx/dat_i_l*}]


################################################################################ ################################################################################
# DRAM (generated by LiteX) # DRAM (generated by LiteX)
################################################################################ ################################################################################

@ -25,6 +25,16 @@ set_property -dict {PACKAGE_PIN V18 IOSTANDARD LVCMOS33} [get_ports uart_main_rx
set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { led0 }]; set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { led0 }];
set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { led1 }]; set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { led1 }];


################################################################################
# SPI Flash
################################################################################

set_property -dict { PACKAGE_PIN T19 IOSTANDARD LVCMOS33 } [get_ports { spi_flash_cs_n }];
set_property -dict { PACKAGE_PIN P22 IOSTANDARD LVCMOS33 } [get_ports { spi_flash_mosi }];
set_property -dict { PACKAGE_PIN R22 IOSTANDARD LVCMOS33 } [get_ports { spi_flash_miso }];
set_property -dict { PACKAGE_PIN P21 IOSTANDARD LVCMOS33 } [get_ports { spi_flash_wp_n }];
set_property -dict { PACKAGE_PIN R21 IOSTANDARD LVCMOS33 } [get_ports { spi_flash_hold_n }];

################################################################################ ################################################################################
# DRAM (generated by LiteX) # DRAM (generated by LiteX)
################################################################################ ################################################################################

@ -10,44 +10,56 @@ use work.wishbone_types.all;


entity toplevel is entity toplevel is
generic ( generic (
MEMORY_SIZE : integer := 16384; MEMORY_SIZE : integer := 16384;
RAM_INIT_FILE : string := "firmware.hex"; RAM_INIT_FILE : string := "firmware.hex";
RESET_LOW : boolean := true; RESET_LOW : boolean := true;
CLK_FREQUENCY : positive := 100000000; CLK_FREQUENCY : positive := 100000000;
USE_LITEDRAM : boolean := false; USE_LITEDRAM : boolean := false;
NO_BRAM : boolean := false; NO_BRAM : boolean := false;
DISABLE_FLATTEN_CORE : boolean := false DISABLE_FLATTEN_CORE : boolean := false;
); SCLK_STARTUPE2 : boolean := false;
SPI_FLASH_OFFSET : integer := 4194304;
SPI_FLASH_DEF_CKDV : natural := 1;
SPI_FLASH_DEF_QUAD : boolean := true
);
port( port(
ext_clk : in std_ulogic; ext_clk : in std_ulogic;
ext_rst : in std_ulogic; ext_rst : in std_ulogic;


-- UART0 signals: -- UART0 signals:
uart_main_tx : out std_ulogic; uart_main_tx : out std_ulogic;
uart_main_rx : in std_ulogic; uart_main_rx : in std_ulogic;


-- LEDs -- LEDs
led0_b : out std_ulogic; led0_b : out std_ulogic;
led0_g : out std_ulogic; led0_g : out std_ulogic;
led0_r : out std_ulogic; led0_r : out std_ulogic;


-- DRAM wires -- SPI
ddram_a : out std_ulogic_vector(13 downto 0); spi_flash_cs_n : out std_ulogic;
ddram_ba : out std_ulogic_vector(2 downto 0); spi_flash_clk : out std_ulogic;
ddram_ras_n : out std_ulogic; spi_flash_mosi : inout std_ulogic;
ddram_cas_n : out std_ulogic; spi_flash_miso : inout std_ulogic;
ddram_we_n : out std_ulogic; spi_flash_wp_n : inout std_ulogic;
ddram_cs_n : out std_ulogic; spi_flash_hold_n : inout std_ulogic;
ddram_dm : out std_ulogic_vector(1 downto 0);
ddram_dq : inout std_ulogic_vector(15 downto 0); -- DRAM wires
ddram_dqs_p : inout std_ulogic_vector(1 downto 0); ddram_a : out std_ulogic_vector(13 downto 0);
ddram_dqs_n : inout std_ulogic_vector(1 downto 0); ddram_ba : out std_ulogic_vector(2 downto 0);
ddram_clk_p : out std_ulogic; ddram_ras_n : out std_ulogic;
ddram_clk_n : out std_ulogic; ddram_cas_n : out std_ulogic;
ddram_cke : out std_ulogic; ddram_we_n : out std_ulogic;
ddram_odt : out std_ulogic; ddram_cs_n : out std_ulogic;
ddram_reset_n : out std_ulogic ddram_dm : out std_ulogic_vector(1 downto 0);
); ddram_dq : inout std_ulogic_vector(15 downto 0);
ddram_dqs_p : inout std_ulogic_vector(1 downto 0);
ddram_dqs_n : inout std_ulogic_vector(1 downto 0);
ddram_clk_p : out std_ulogic;
ddram_clk_n : out std_ulogic;
ddram_cke : out std_ulogic;
ddram_odt : out std_ulogic;
ddram_reset_n : out std_ulogic
);
end entity toplevel; end entity toplevel;


architecture behaviour of toplevel is architecture behaviour of toplevel is
@ -81,6 +93,13 @@ architecture behaviour of toplevel is
-- Dumb PWM for the LEDs, those RGB LEDs are too bright otherwise -- Dumb PWM for the LEDs, those RGB LEDs are too bright otherwise
signal pwm_counter : std_ulogic_vector(8 downto 0); signal pwm_counter : std_ulogic_vector(8 downto 0);


-- SPI flash
signal spi_sck : std_ulogic;
signal spi_cs_n : std_ulogic;
signal spi_sdat_o : std_ulogic_vector(3 downto 0);
signal spi_sdat_oe : std_ulogic_vector(3 downto 0);
signal spi_sdat_i : std_ulogic_vector(3 downto 0);

-- Fixup various memory sizes based on generics -- Fixup various memory sizes based on generics
function get_bram_size return natural is function get_bram_size return natural is
begin begin
@ -106,62 +125,116 @@ begin


-- Main SoC -- Main SoC
soc0: entity work.soc soc0: entity work.soc
generic map( generic map(
MEMORY_SIZE => BRAM_SIZE, MEMORY_SIZE => BRAM_SIZE,
RAM_INIT_FILE => RAM_INIT_FILE, RAM_INIT_FILE => RAM_INIT_FILE,
RESET_LOW => RESET_LOW, RESET_LOW => RESET_LOW,
SIM => false, SIM => false,
CLK_FREQ => CLK_FREQUENCY, CLK_FREQ => CLK_FREQUENCY,
HAS_DRAM => USE_LITEDRAM, HAS_DRAM => USE_LITEDRAM,
DRAM_SIZE => 256 * 1024 * 1024, DRAM_SIZE => 256 * 1024 * 1024,
DRAM_INIT_SIZE => PAYLOAD_SIZE, DRAM_INIT_SIZE => PAYLOAD_SIZE,
DISABLE_FLATTEN_CORE => DISABLE_FLATTEN_CORE DISABLE_FLATTEN_CORE => DISABLE_FLATTEN_CORE,
) HAS_SPI_FLASH => true,
port map ( SPI_FLASH_DLINES => 4,
system_clk => system_clk, SPI_FLASH_OFFSET => SPI_FLASH_OFFSET,
rst => soc_rst, SPI_FLASH_DEF_CKDV => SPI_FLASH_DEF_CKDV,
uart0_txd => uart_main_tx, SPI_FLASH_DEF_QUAD => SPI_FLASH_DEF_QUAD
uart0_rxd => uart_main_rx, )
wb_dram_in => wb_dram_in, port map (
wb_dram_out => wb_dram_out, -- System signals
wb_dram_ctrl_in => wb_dram_ctrl_in, system_clk => system_clk,
wb_dram_ctrl_out => wb_dram_ctrl_out, rst => soc_rst,
wb_dram_is_csr => wb_dram_is_csr,
wb_dram_is_init => wb_dram_is_init, -- UART signals
alt_reset => core_alt_reset uart0_txd => uart_main_tx,
); uart0_rxd => uart_main_rx,

-- SPI signals
spi_flash_sck => spi_sck,
spi_flash_cs_n => spi_cs_n,
spi_flash_sdat_o => spi_sdat_o,
spi_flash_sdat_oe => spi_sdat_oe,
spi_flash_sdat_i => spi_sdat_i,

-- DRAM wishbone
wb_dram_in => wb_dram_in,
wb_dram_out => wb_dram_out,
wb_dram_ctrl_in => wb_dram_ctrl_in,
wb_dram_ctrl_out => wb_dram_ctrl_out,
wb_dram_is_csr => wb_dram_is_csr,
wb_dram_is_init => wb_dram_is_init,
alt_reset => core_alt_reset
);

-- SPI Flash
--
-- Note: Unlike many other boards, the SPI flash on the Arty has
-- an actual pin to generate the clock and doesn't require to use
-- the STARTUPE2 primitive.
--
spi_flash_cs_n <= spi_cs_n;
spi_flash_mosi <= spi_sdat_o(0) when spi_sdat_oe(0) = '1' else 'Z';
spi_flash_miso <= spi_sdat_o(1) when spi_sdat_oe(1) = '1' else 'Z';
spi_flash_wp_n <= spi_sdat_o(2) when spi_sdat_oe(2) = '1' else 'Z';
spi_flash_hold_n <= spi_sdat_o(3) when spi_sdat_oe(3) = '1' else 'Z';
spi_sdat_i(0) <= spi_flash_mosi;
spi_sdat_i(1) <= spi_flash_miso;
spi_sdat_i(2) <= spi_flash_wp_n;
spi_sdat_i(3) <= spi_flash_hold_n;

spi_sclk_startupe2: if SCLK_STARTUPE2 generate
spi_flash_clk <= 'Z';

STARTUPE2_INST: STARTUPE2
port map (
CLK => '0',
GSR => '0',
GTS => '0',
KEYCLEARB => '0',
PACK => '0',
USRCCLKO => spi_sck,
USRCCLKTS => '0',
USRDONEO => '1',
USRDONETS => '0'
);
end generate;

spi_direct_sclk: if not SCLK_STARTUPE2 generate
spi_flash_clk <= spi_sck;
end generate;


nodram: if not USE_LITEDRAM generate nodram: if not USE_LITEDRAM generate
signal ddram_clk_dummy : std_ulogic; signal ddram_clk_dummy : std_ulogic;
begin begin
reset_controller: entity work.soc_reset reset_controller: entity work.soc_reset
generic map( generic map(
RESET_LOW => RESET_LOW RESET_LOW => RESET_LOW
) )
port map( port map(
ext_clk => ext_clk, ext_clk => ext_clk,
pll_clk => system_clk, pll_clk => system_clk,
pll_locked_in => system_clk_locked, pll_locked_in => system_clk_locked,
ext_rst_in => ext_rst, ext_rst_in => ext_rst,
pll_rst_out => pll_rst, pll_rst_out => pll_rst,
rst_out => soc_rst rst_out => soc_rst
); );


clkgen: entity work.clock_generator clkgen: entity work.clock_generator
generic map( generic map(
CLK_INPUT_HZ => 100000000, CLK_INPUT_HZ => 100000000,
CLK_OUTPUT_HZ => CLK_FREQUENCY CLK_OUTPUT_HZ => CLK_FREQUENCY
) )
port map( port map(
ext_clk => ext_clk, ext_clk => ext_clk,
pll_rst_in => pll_rst, pll_rst_in => pll_rst,
pll_clk_out => system_clk, pll_clk_out => system_clk,
pll_locked_out => system_clk_locked pll_locked_out => system_clk_locked
); );


led0_b_pwm <= '1'; led0_b_pwm <= '1';
led0_r_pwm <= '1'; led0_r_pwm <= '1';
led0_g_pwm <= '0'; led0_g_pwm <= '0';
core_alt_reset <= '0'; core_alt_reset <= '0';


-- Vivado barfs on those differential signals if left -- Vivado barfs on those differential signals if left
@ -179,91 +252,91 @@ begin


has_dram: if USE_LITEDRAM generate has_dram: if USE_LITEDRAM generate
signal dram_init_done : std_ulogic; signal dram_init_done : std_ulogic;
signal dram_init_error : std_ulogic; signal dram_init_error : std_ulogic;
signal dram_sys_rst : std_ulogic; signal dram_sys_rst : std_ulogic;
begin begin


-- Eventually dig out the frequency from the generator -- Eventually dig out the frequency from the generator
-- but for now, assert it's 100Mhz -- but for now, assert it's 100Mhz
assert CLK_FREQUENCY = 100000000; assert CLK_FREQUENCY = 100000000;


reset_controller: entity work.soc_reset reset_controller: entity work.soc_reset
generic map( generic map(
RESET_LOW => RESET_LOW, RESET_LOW => RESET_LOW,
PLL_RESET_BITS => 18, PLL_RESET_BITS => 18,
SOC_RESET_BITS => 1 SOC_RESET_BITS => 1
) )
port map( port map(
ext_clk => ext_clk, ext_clk => ext_clk,
pll_clk => system_clk, pll_clk => system_clk,
pll_locked_in => '1', pll_locked_in => '1',
ext_rst_in => ext_rst, ext_rst_in => ext_rst,
pll_rst_out => pll_rst, pll_rst_out => pll_rst,
rst_out => open rst_out => open
); );


dram: entity work.litedram_wrapper dram: entity work.litedram_wrapper
generic map( generic map(
DRAM_ABITS => 24, DRAM_ABITS => 24,
DRAM_ALINES => 14, DRAM_ALINES => 14,
PAYLOAD_FILE => RAM_INIT_FILE, PAYLOAD_FILE => RAM_INIT_FILE,
PAYLOAD_SIZE => PAYLOAD_SIZE PAYLOAD_SIZE => PAYLOAD_SIZE
) )
port map( port map(
clk_in => ext_clk, clk_in => ext_clk,
rst => pll_rst, rst => pll_rst,
system_clk => system_clk, system_clk => system_clk,
system_reset => soc_rst, system_reset => soc_rst,
core_alt_reset => core_alt_reset, core_alt_reset => core_alt_reset,
pll_locked => system_clk_locked, pll_locked => system_clk_locked,


wb_in => wb_dram_in, wb_in => wb_dram_in,
wb_out => wb_dram_out, wb_out => wb_dram_out,
wb_ctrl_in => wb_dram_ctrl_in, wb_ctrl_in => wb_dram_ctrl_in,
wb_ctrl_out => wb_dram_ctrl_out, wb_ctrl_out => wb_dram_ctrl_out,
wb_ctrl_is_csr => wb_dram_is_csr, wb_ctrl_is_csr => wb_dram_is_csr,
wb_ctrl_is_init => wb_dram_is_init, wb_ctrl_is_init => wb_dram_is_init,


init_done => dram_init_done, init_done => dram_init_done,
init_error => dram_init_error, init_error => dram_init_error,


ddram_a => ddram_a, ddram_a => ddram_a,
ddram_ba => ddram_ba, ddram_ba => ddram_ba,
ddram_ras_n => ddram_ras_n, ddram_ras_n => ddram_ras_n,
ddram_cas_n => ddram_cas_n, ddram_cas_n => ddram_cas_n,
ddram_we_n => ddram_we_n, ddram_we_n => ddram_we_n,
ddram_cs_n => ddram_cs_n, ddram_cs_n => ddram_cs_n,
ddram_dm => ddram_dm, ddram_dm => ddram_dm,
ddram_dq => ddram_dq, ddram_dq => ddram_dq,
ddram_dqs_p => ddram_dqs_p, ddram_dqs_p => ddram_dqs_p,
ddram_dqs_n => ddram_dqs_n, ddram_dqs_n => ddram_dqs_n,
ddram_clk_p => ddram_clk_p, ddram_clk_p => ddram_clk_p,
ddram_clk_n => ddram_clk_n, ddram_clk_n => ddram_clk_n,
ddram_cke => ddram_cke, ddram_cke => ddram_cke,
ddram_odt => ddram_odt, ddram_odt => ddram_odt,
ddram_reset_n => ddram_reset_n ddram_reset_n => ddram_reset_n
); );


led0_b_pwm <= not dram_init_done; led0_b_pwm <= not dram_init_done;
led0_r_pwm <= dram_init_error; led0_r_pwm <= dram_init_error;
led0_g_pwm <= dram_init_done and not dram_init_error; led0_g_pwm <= dram_init_done and not dram_init_error;


end generate; end generate;


leds_pwm : process(system_clk) leds_pwm : process(system_clk)
begin begin
if rising_edge(system_clk) then if rising_edge(system_clk) then
pwm_counter <= std_ulogic_vector(signed(pwm_counter) + 1); pwm_counter <= std_ulogic_vector(signed(pwm_counter) + 1);
if pwm_counter(8 downto 4) = "00000" then if pwm_counter(8 downto 4) = "00000" then
led0_b <= led0_b_pwm; led0_b <= led0_b_pwm;
led0_r <= led0_r_pwm; led0_r <= led0_r_pwm;
led0_g <= led0_g_pwm; led0_g <= led0_g_pwm;
else else
led0_b <= '0'; led0_b <= '0';
led0_r <= '0'; led0_r <= '0';
led0_g <= '0'; led0_g <= '0';
end if; end if;
end if; end if;
end process; end process;


end architecture behaviour; end architecture behaviour;

@ -78,13 +78,19 @@ begin
RESET_LOW => RESET_LOW, RESET_LOW => RESET_LOW,
SIM => false, SIM => false,
CLK_FREQ => CLK_FREQUENCY, CLK_FREQ => CLK_FREQUENCY,
DISABLE_FLATTEN_CORE => DISABLE_FLATTEN_CORE DISABLE_FLATTEN_CORE => DISABLE_FLATTEN_CORE,
HAS_SPI => false
) )
port map ( port map (
system_clk => system_clk, system_clk => system_clk,
rst => soc_rst, rst => soc_rst,
uart0_txd => uart0_txd, uart0_txd => uart0_txd,
uart0_rxd => uart0_rxd, uart0_rxd => uart0_rxd,
spi0_sck => open,
spi0_cs_n => open,
spi0_sdat_o => open,
spi0_sdat_oe => open,
spi0_sdat_i => '1',
wb_dram_in => wb_dram_in, wb_dram_in => wb_dram_in,
wb_dram_out => wb_dram_out, wb_dram_out => wb_dram_out,
wb_dram_ctrl_in => wb_dram_ctrl_in, wb_dram_ctrl_in => wb_dram_ctrl_in,

@ -16,7 +16,10 @@ entity toplevel is
CLK_FREQUENCY : positive := 100000000; CLK_FREQUENCY : positive := 100000000;
USE_LITEDRAM : boolean := false; USE_LITEDRAM : boolean := false;
NO_BRAM : boolean := false; NO_BRAM : boolean := false;
DISABLE_FLATTEN_CORE : boolean := false DISABLE_FLATTEN_CORE : boolean := false;
SPI_FLASH_OFFSET : integer := 10485760;
SPI_FLASH_DEF_CKDV : natural := 1;
SPI_FLASH_DEF_QUAD : boolean := true
); );
port( port(
ext_clk : in std_ulogic; ext_clk : in std_ulogic;
@ -30,6 +33,13 @@ entity toplevel is
led0 : out std_logic; led0 : out std_logic;
led1 : out std_logic; led1 : out std_logic;


-- SPI
spi_flash_cs_n : out std_ulogic;
spi_flash_mosi : inout std_ulogic;
spi_flash_miso : inout std_ulogic;
spi_flash_wp_n : inout std_ulogic;
spi_flash_hold_n : inout std_ulogic;

-- DRAM wires -- DRAM wires
ddram_a : out std_logic_vector(14 downto 0); ddram_a : out std_logic_vector(14 downto 0);
ddram_ba : out std_logic_vector(2 downto 0); ddram_ba : out std_logic_vector(2 downto 0);
@ -71,6 +81,13 @@ architecture behaviour of toplevel is
-- Control/status -- Control/status
signal core_alt_reset : std_ulogic; signal core_alt_reset : std_ulogic;


-- SPI flash
signal spi_sck : std_ulogic;
signal spi_cs_n : std_ulogic;
signal spi_sdat_o : std_ulogic_vector(3 downto 0);
signal spi_sdat_oe : std_ulogic_vector(3 downto 0);
signal spi_sdat_i : std_ulogic_vector(3 downto 0);

-- Fixup various memory sizes based on generics -- Fixup various memory sizes based on generics
function get_bram_size return natural is function get_bram_size return natural is
begin begin
@ -105,13 +122,30 @@ begin
HAS_DRAM => USE_LITEDRAM, HAS_DRAM => USE_LITEDRAM,
DRAM_SIZE => 512 * 1024 * 1024, DRAM_SIZE => 512 * 1024 * 1024,
DRAM_INIT_SIZE => PAYLOAD_SIZE, DRAM_INIT_SIZE => PAYLOAD_SIZE,
DISABLE_FLATTEN_CORE => DISABLE_FLATTEN_CORE DISABLE_FLATTEN_CORE => DISABLE_FLATTEN_CORE,
HAS_SPI_FLASH => true,
SPI_FLASH_DLINES => 4,
SPI_FLASH_OFFSET => SPI_FLASH_OFFSET,
SPI_FLASH_DEF_CKDV => SPI_FLASH_DEF_CKDV,
SPI_FLASH_DEF_QUAD => SPI_FLASH_DEF_QUAD
) )
port map ( port map (
-- System signals
system_clk => system_clk, system_clk => system_clk,
rst => soc_rst, rst => soc_rst,
uart0_txd => uart_main_tx,
-- UART signals
uart0_txd => uart_main_tx,
uart0_rxd => uart_main_rx, uart0_rxd => uart_main_rx,

-- SPI signals
spi_flash_sck => spi_sck,
spi_flash_cs_n => spi_cs_n,
spi_flash_sdat_o => spi_sdat_o,
spi_flash_sdat_oe => spi_sdat_oe,
spi_flash_sdat_i => spi_sdat_i,

-- DRAM wishbone
wb_dram_in => wb_dram_in, wb_dram_in => wb_dram_in,
wb_dram_out => wb_dram_out, wb_dram_out => wb_dram_out,
wb_dram_ctrl_in => wb_dram_ctrl_in, wb_dram_ctrl_in => wb_dram_ctrl_in,
@ -121,6 +155,32 @@ begin
alt_reset => core_alt_reset alt_reset => core_alt_reset
); );


-- SPI Flash. The SPI clk needs to be fed through the STARTUPE2
-- primitive of the FPGA as it's not a normal pin
--
spi_flash_cs_n <= spi_cs_n;
spi_flash_mosi <= spi_sdat_o(0) when spi_sdat_oe(0) = '1' else 'Z';
spi_flash_miso <= spi_sdat_o(1) when spi_sdat_oe(1) = '1' else 'Z';
spi_flash_wp_n <= spi_sdat_o(2) when spi_sdat_oe(2) = '1' else 'Z';
spi_flash_hold_n <= spi_sdat_o(3) when spi_sdat_oe(3) = '1' else 'Z';
spi_sdat_i(0) <= spi_flash_mosi;
spi_sdat_i(1) <= spi_flash_miso;
spi_sdat_i(2) <= spi_flash_wp_n;
spi_sdat_i(3) <= spi_flash_hold_n;

STARTUPE2_INST: STARTUPE2
port map (
CLK => '0',
GSR => '0',
GTS => '0',
KEYCLEARB => '0',
PACK => '0',
USRCCLKO => spi_sck,
USRCCLKTS => '0',
USRDONEO => '1',
USRDONETS => '0'
);

nodram: if not USE_LITEDRAM generate nodram: if not USE_LITEDRAM generate
signal ddram_clk_dummy : std_ulogic; signal ddram_clk_dummy : std_ulogic;
begin begin

@ -12,8 +12,10 @@
#define SYSCON_BASE 0xc0000000 /* System control regs */ #define SYSCON_BASE 0xc0000000 /* System control regs */
#define UART_BASE 0xc0002000 /* UART */ #define UART_BASE 0xc0002000 /* UART */
#define XICS_BASE 0xc0004000 /* Interrupt controller */ #define XICS_BASE 0xc0004000 /* Interrupt controller */
#define SPI_FCTRL_BASE 0xc0006000 /* SPI flash controller registers */
#define DRAM_CTRL_BASE 0xc0100000 /* LiteDRAM control registers */ #define DRAM_CTRL_BASE 0xc0100000 /* LiteDRAM control registers */
#define DRAM_INIT_BASE 0xf0000000 /* Internal DRAM init firmware */ #define SPI_FLASH_BASE 0xf0000000 /* SPI Flash memory map */
#define DRAM_INIT_BASE 0xff000000 /* Internal DRAM init firmware */


/* /*
* Register definitions for the syscon registers * Register definitions for the syscon registers
@ -24,6 +26,7 @@
#define SYS_REG_INFO_HAS_UART (1ull << 0) #define SYS_REG_INFO_HAS_UART (1ull << 0)
#define SYS_REG_INFO_HAS_DRAM (1ull << 1) #define SYS_REG_INFO_HAS_DRAM (1ull << 1)
#define SYS_REG_INFO_HAS_BRAM (1ull << 2) #define SYS_REG_INFO_HAS_BRAM (1ull << 2)
#define SYS_REG_INFO_HAS_SPI_FLASH (1ull << 3)
#define SYS_REG_BRAMINFO 0x10 #define SYS_REG_BRAMINFO 0x10
#define SYS_REG_BRAMINFO_SIZE_MASK 0xfffffffffffffull #define SYS_REG_BRAMINFO_SIZE_MASK 0xfffffffffffffull
#define SYS_REG_DRAMINFO 0x18 #define SYS_REG_DRAMINFO 0x18
@ -35,6 +38,10 @@
#define SYS_REG_CTRL_CORE_RESET (1ull << 1) #define SYS_REG_CTRL_CORE_RESET (1ull << 1)
#define SYS_REG_CTRL_SOC_RESET (1ull << 2) #define SYS_REG_CTRL_SOC_RESET (1ull << 2)
#define SYS_REG_DRAMINITINFO 0x30 #define SYS_REG_DRAMINITINFO 0x30
#define SYS_REG_SPI_INFO 0x38
#define SYS_REG_SPI_INFO_FLASH_OFF_MASK 0xffffffff




/* /*
* Register definitions for the potato UART * Register definitions for the potato UART
@ -49,5 +56,30 @@
#define POTATO_CONSOLE_CLOCK_DIV 0x18 #define POTATO_CONSOLE_CLOCK_DIV 0x18
#define POTATO_CONSOLE_IRQ_EN 0x20 #define POTATO_CONSOLE_IRQ_EN 0x20


/*
* Register definitions for the SPI controller
*/
#define SPI_REG_DATA 0x00 /* Byte access: single wire transfer */
#define SPI_REG_DATA_DUAL 0x01 /* Byte access: dual wire transfer */
#define SPI_REG_DATA_QUAD 0x02 /* Byte access: quad wire transfer */
#define SPI_REG_CTRL 0x04 /* Reset and manual mode control */
#define SPI_REG_CTRL_RESET 0x01 /* reset all registers */
#define SPI_REG_CTRL_MANUAL_CS 0x02 /* assert CS, enable manual mode */
#define SPI_REG_CTRL_CKDIV_SHIFT 8 /* clock div */
#define SPI_REG_CTRL_CKDIV_MASK 0xff
#define SPI_REG_AUTO_CFG 0x08 /* Automatic map configuration */
#define SPI_REG_AUTO_CFG_CMD_SHIFT 0 /* Command to use for reads */
#define SPI_REG_AUTO_CFG_CMD_MASK 0xff
#define SPI_REG_AUTO_CFG_DUMMIES_SHIFT 8 /* # dummy cycles */
#define SPI_REG_AUTO_CFG_DUMMIES_MASK 0x7
#define SPI_REG_AUTO_CFG_MODE_SHIFT 11 /* SPI wire mode */
#define SPI_REG_AUTO_CFG_MODE_MASK 0x3
#define SPI_REG_AUT_CFG_MODE_SINGLE (0 << 11)
#define SPI_REG_AUT_CFG_MODE_DUAL (2 << 11)
#define SPI_REG_AUT_CFG_MODE_QUAD (3 << 11)
#define SPI_REG_AUTO_CFG_ADDR4 (1u << 13) /* 3 or 4 addr bytes */
#define SPI_REG_AUTO_CFG_CKDIV_SHIFT 16 /* clock div */
#define SPI_REG_AUTO_CFG_CKDIV_MASK 0xff



#endif /* __MICROWATT_SOC_H */ #endif /* __MICROWATT_SOC_H */

@ -49,6 +49,8 @@ filesets:
- xics.vhdl - xics.vhdl
- syscon.vhdl - syscon.vhdl
- sync_fifo.vhdl - sync_fifo.vhdl
- spi_rxtx.vhdl
- spi_flash_ctrl.vhdl
file_type : vhdlSource-2008 file_type : vhdlSource-2008


fpga: fpga:
@ -119,6 +121,7 @@ targets:
- clk_input - clk_input
- clk_frequency - clk_frequency
- disable_flatten_core - disable_flatten_core
- spi_flash_offset=10485760
tools: tools:
vivado: {part : xc7a200tsbg484-1} vivado: {part : xc7a200tsbg484-1}
toplevel : toplevel toplevel : toplevel
@ -132,6 +135,7 @@ targets:
- use_litedram=true - use_litedram=true
- disable_flatten_core - disable_flatten_core
- no_bram - no_bram
- spi_flash_offset=10485760
generate: [dram_nexys_video] generate: [dram_nexys_video]
tools: tools:
vivado: {part : xc7a200tsbg484-1} vivado: {part : xc7a200tsbg484-1}
@ -146,6 +150,7 @@ targets:
- clk_input - clk_input
- clk_frequency - clk_frequency
- disable_flatten_core - disable_flatten_core
- spi_flash_offset=3145728
tools: tools:
vivado: {part : xc7a35ticsg324-1L} vivado: {part : xc7a35ticsg324-1L}
toplevel : toplevel toplevel : toplevel
@ -159,6 +164,7 @@ targets:
- use_litedram=true - use_litedram=true
- disable_flatten_core - disable_flatten_core
- no_bram - no_bram
- spi_flash_offset=3145728
generate: [dram_arty] generate: [dram_arty]
tools: tools:
vivado: {part : xc7a35ticsg324-1L} vivado: {part : xc7a35ticsg324-1L}
@ -173,6 +179,7 @@ targets:
- clk_input - clk_input
- clk_frequency - clk_frequency
- disable_flatten_core - disable_flatten_core
- spi_flash_offset=4194304
tools: tools:
vivado: {part : xc7a100ticsg324-1L} vivado: {part : xc7a100ticsg324-1L}
toplevel : toplevel toplevel : toplevel
@ -186,6 +193,7 @@ targets:
- use_litedram=true - use_litedram=true
- disable_flatten_core - disable_flatten_core
- no_bram - no_bram
- spi_flash_offset=4194304
generate: [dram_arty] generate: [dram_arty]
tools: tools:
vivado: {part : xc7a100ticsg324-1L} vivado: {part : xc7a100ticsg324-1L}
@ -266,3 +274,8 @@ parameters:
description : No internal block RAM (only DRAM and init code carrying payload) description : No internal block RAM (only DRAM and init code carrying payload)
paramtype : generic paramtype : generic
default : false default : false

spi_flash_offset:
datatype : int
description : Offset (in bytes) in the SPI flash of the code payload to run
paramtype : generic

@ -21,20 +21,27 @@ use work.wishbone_types.all;
-- 0xc0000000: SYSCON -- 0xc0000000: SYSCON
-- 0xc0002000: UART0 -- 0xc0002000: UART0
-- 0xc0004000: XICS ICP -- 0xc0004000: XICS ICP
-- 0xc0006000: SPI Flash controller
-- 0xc0100000: LiteDRAM control (CSRs) -- 0xc0100000: LiteDRAM control (CSRs)
-- 0xf0000000: DRAM init code (if any) -- 0xf0000000: Flash "ROM" mapping
-- 0xff000000: DRAM init code (if any) or flash ROM


entity soc is entity soc is
generic ( generic (
MEMORY_SIZE : natural; MEMORY_SIZE : natural;
RAM_INIT_FILE : string; RAM_INIT_FILE : string;
RESET_LOW : boolean; RESET_LOW : boolean;
CLK_FREQ : positive; CLK_FREQ : positive;
SIM : boolean; SIM : boolean;
DISABLE_FLATTEN_CORE : boolean := false; DISABLE_FLATTEN_CORE : boolean := false;
HAS_DRAM : boolean := false; HAS_DRAM : boolean := false;
DRAM_SIZE : integer := 0; DRAM_SIZE : integer := 0;
DRAM_INIT_SIZE : integer := 0 DRAM_INIT_SIZE : integer := 0;
HAS_SPI_FLASH : boolean := false;
SPI_FLASH_DLINES : positive := 1;
SPI_FLASH_OFFSET : integer := 0;
SPI_FLASH_DEF_CKDV : natural := 2;
SPI_FLASH_DEF_QUAD : boolean := false
); );
port( port(
rst : in std_ulogic; rst : in std_ulogic;
@ -52,6 +59,13 @@ entity soc is
uart0_txd : out std_ulogic; uart0_txd : out std_ulogic;
uart0_rxd : in std_ulogic; uart0_rxd : in std_ulogic;


-- SPI Flash signals
spi_flash_sck : out std_ulogic;
spi_flash_cs_n : out std_ulogic;
spi_flash_sdat_o : out std_ulogic_vector(SPI_FLASH_DLINES-1 downto 0);
spi_flash_sdat_oe : out std_ulogic_vector(SPI_FLASH_DLINES-1 downto 0);
spi_flash_sdat_i : in std_ulogic_vector(SPI_FLASH_DLINES-1 downto 0);

-- DRAM controller signals -- DRAM controller signals
alt_reset : in std_ulogic alt_reset : in std_ulogic
); );
@ -96,6 +110,12 @@ architecture behaviour of soc is
signal wb_uart0_out : wb_io_slave_out; signal wb_uart0_out : wb_io_slave_out;
signal uart_dat8 : std_ulogic_vector(7 downto 0); signal uart_dat8 : std_ulogic_vector(7 downto 0);


-- SPI Flash controller signals:
signal wb_spiflash_in : wb_io_master_out;
signal wb_spiflash_out : wb_io_slave_out;
signal wb_spiflash_is_reg : std_ulogic;
signal wb_spiflash_is_map : std_ulogic;

-- XICS0 signals: -- XICS0 signals:
signal wb_xics0_in : wb_io_master_out; signal wb_xics0_in : wb_io_master_out;
signal wb_xics0_out : wb_io_slave_out; signal wb_xics0_out : wb_io_slave_out;
@ -127,12 +147,23 @@ architecture behaviour of soc is
signal rst_core : std_ulogic := '1'; signal rst_core : std_ulogic := '1';
signal rst_uart : std_ulogic := '1'; signal rst_uart : std_ulogic := '1';
signal rst_xics : std_ulogic := '1'; signal rst_xics : std_ulogic := '1';
signal rst_spi : std_ulogic := '1';
signal rst_bram : std_ulogic := '1'; signal rst_bram : std_ulogic := '1';
signal rst_dtm : std_ulogic := '1'; signal rst_dtm : std_ulogic := '1';
signal rst_wbar : std_ulogic := '1'; signal rst_wbar : std_ulogic := '1';
signal rst_wbdb : std_ulogic := '1'; signal rst_wbdb : std_ulogic := '1';
signal alt_reset_d : std_ulogic; signal alt_reset_d : std_ulogic;


-- IO branch split:
type slave_io_type is (SLAVE_IO_SYSCON,
SLAVE_IO_UART,
SLAVE_IO_DRAM_INIT,
SLAVE_IO_DRAM_CSR,
SLAVE_IO_ICP_0,
SLAVE_IO_SPI_FLASH_REG,
SLAVE_IO_SPI_FLASH_MAP,
SLAVE_IO_NONE);
signal slave_io_dbg : slave_io_type;
begin begin


resets: process(system_clk) resets: process(system_clk)
@ -140,6 +171,7 @@ begin
if rising_edge(system_clk) then if rising_edge(system_clk) then
rst_core <= rst or do_core_reset; rst_core <= rst or do_core_reset;
rst_uart <= rst; rst_uart <= rst;
rst_spi <= rst;
rst_xics <= rst; rst_xics <= rst;
rst_bram <= rst; rst_bram <= rst;
rst_dtm <= rst; rst_dtm <= rst;
@ -154,7 +186,7 @@ begin
generic map( generic map(
SIM => SIM, SIM => SIM,
DISABLE_FLATTEN => DISABLE_FLATTEN_CORE, DISABLE_FLATTEN => DISABLE_FLATTEN_CORE,
ALT_RESET_ADDRESS => (27 downto 0 => '0', others => '1') ALT_RESET_ADDRESS => (23 downto 0 => '0', others => '1')
) )
port map( port map(
clk => system_clk, clk => system_clk,
@ -389,14 +421,7 @@ begin
-- IO wishbone slave intercon. -- IO wishbone slave intercon.
-- --
slave_io_intercon: process(wb_sio_out, wb_syscon_out, wb_uart0_out, slave_io_intercon: process(wb_sio_out, wb_syscon_out, wb_uart0_out,
wb_dram_ctrl_out, wb_xics0_out) wb_dram_ctrl_out, wb_xics0_out, wb_spiflash_out)
-- IO branch split:
type slave_io_type is (SLAVE_IO_SYSCON,
SLAVE_IO_UART,
SLAVE_IO_DRAM_INIT,
SLAVE_IO_DRAM_CSR,
SLAVE_IO_ICP_0,
SLAVE_IO_NONE);
variable slave_io : slave_io_type; variable slave_io : slave_io_type;


variable match : std_ulogic_vector(31 downto 12); variable match : std_ulogic_vector(31 downto 12);
@ -405,8 +430,10 @@ begin
-- Simple address decoder. -- Simple address decoder.
slave_io := SLAVE_IO_NONE; slave_io := SLAVE_IO_NONE;
match := "11" & wb_sio_out.adr(29 downto 12); match := "11" & wb_sio_out.adr(29 downto 12);
if std_match(match, x"F----") then if std_match(match, x"FF---") and HAS_DRAM then
slave_io := SLAVE_IO_DRAM_INIT; slave_io := SLAVE_IO_DRAM_INIT;
elsif std_match(match, x"F----") then
slave_io := SLAVE_IO_SPI_FLASH_MAP;
elsif std_match(match, x"C0000") then elsif std_match(match, x"C0000") then
slave_io := SLAVE_IO_SYSCON; slave_io := SLAVE_IO_SYSCON;
elsif std_match(match, x"C0002") then elsif std_match(match, x"C0002") then
@ -415,9 +442,16 @@ begin
slave_io := SLAVE_IO_DRAM_CSR; slave_io := SLAVE_IO_DRAM_CSR;
elsif std_match(match, x"C0004") then elsif std_match(match, x"C0004") then
slave_io := SLAVE_IO_ICP_0; slave_io := SLAVE_IO_ICP_0;
elsif std_match(match, x"C0006") then
slave_io := SLAVE_IO_SPI_FLASH_REG;
end if; end if;
slave_io_dbg <= slave_io;
wb_uart0_in <= wb_sio_out; wb_uart0_in <= wb_sio_out;
wb_uart0_in.cyc <= '0'; wb_uart0_in.cyc <= '0';
wb_spiflash_in <= wb_sio_out;
wb_spiflash_in.cyc <= '0';
wb_spiflash_is_reg <= '0';
wb_spiflash_is_map <= '0';


-- Only give xics 8 bits of wb addr -- Only give xics 8 bits of wb addr
wb_xics0_in <= wb_sio_out; wb_xics0_in <= wb_sio_out;
@ -451,6 +485,17 @@ begin
when SLAVE_IO_ICP_0 => when SLAVE_IO_ICP_0 =>
wb_xics0_in.cyc <= wb_sio_out.cyc; wb_xics0_in.cyc <= wb_sio_out.cyc;
wb_sio_in <= wb_xics0_out; wb_sio_in <= wb_xics0_out;
when SLAVE_IO_SPI_FLASH_MAP =>
-- Clear top bits so they don't make their way to the
-- fash chip.
wb_spiflash_in.adr(29 downto 28) <= "00";
wb_spiflash_in.cyc <= wb_sio_out.cyc;
wb_sio_in <= wb_spiflash_out;
wb_spiflash_is_map <= '1';
when SLAVE_IO_SPI_FLASH_REG =>
wb_spiflash_in.cyc <= wb_sio_out.cyc;
wb_sio_in <= wb_spiflash_out;
wb_spiflash_is_reg <= '1';
when others => when others =>
wb_sio_in.dat <= (others => '1'); wb_sio_in.dat <= (others => '1');
wb_sio_in.ack <= wb_sio_out.stb and wb_sio_out.cyc; wb_sio_in.ack <= wb_sio_out.stb and wb_sio_out.cyc;
@ -467,7 +512,9 @@ begin
BRAM_SIZE => MEMORY_SIZE, BRAM_SIZE => MEMORY_SIZE,
DRAM_SIZE => DRAM_SIZE, DRAM_SIZE => DRAM_SIZE,
DRAM_INIT_SIZE => DRAM_INIT_SIZE, DRAM_INIT_SIZE => DRAM_INIT_SIZE,
CLK_FREQ => CLK_FREQ CLK_FREQ => CLK_FREQ,
HAS_SPI_FLASH => HAS_SPI_FLASH,
SPI_FLASH_OFFSET => SPI_FLASH_OFFSET
) )
port map( port map(
clk => system_clk, clk => system_clk,
@ -503,6 +550,34 @@ begin
wb_uart0_out.dat <= x"000000" & uart_dat8; wb_uart0_out.dat <= x"000000" & uart_dat8;
wb_uart0_out.stall <= '0' when wb_uart0_in.cyc = '0' else not wb_uart0_out.ack; wb_uart0_out.stall <= '0' when wb_uart0_in.cyc = '0' else not wb_uart0_out.ack;


spiflash_gen: if HAS_SPI_FLASH generate
spiflash: entity work.spi_flash_ctrl
generic map (
DATA_LINES => SPI_FLASH_DLINES,
DEF_CLK_DIV => SPI_FLASH_DEF_CKDV,
DEF_QUAD_READ => SPI_FLASH_DEF_QUAD
)
port map(
rst => rst_spi,
clk => system_clk,
wb_in => wb_spiflash_in,
wb_out => wb_spiflash_out,
wb_sel_reg => wb_spiflash_is_reg,
wb_sel_map => wb_spiflash_is_map,
sck => spi_flash_sck,
cs_n => spi_flash_cs_n,
sdat_o => spi_flash_sdat_o,
sdat_oe => spi_flash_sdat_oe,
sdat_i => spi_flash_sdat_i
);
end generate;

no_spi0_gen: if not HAS_SPI_FLASH generate
wb_spiflash_out.dat <= (others => '1');
wb_spiflash_out.ack <= wb_spiflash_in.cyc and wb_spiflash_in.stb;
wb_spiflash_out.stall <= wb_spiflash_in.cyc and not wb_spiflash_out.ack;
end generate;

xics0: entity work.xics xics0: entity work.xics
generic map( generic map(
LEVEL_NUM => 16 LEVEL_NUM => 16

@ -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;

@ -8,13 +8,15 @@ use work.wishbone_types.all;


entity syscon is entity syscon is
generic ( generic (
SIG_VALUE : std_ulogic_vector(63 downto 0) := x"f00daa5500010001"; SIG_VALUE : std_ulogic_vector(63 downto 0) := x"f00daa5500010001";
CLK_FREQ : integer; CLK_FREQ : integer;
HAS_UART : boolean; HAS_UART : boolean;
HAS_DRAM : boolean; HAS_DRAM : boolean;
BRAM_SIZE : integer; BRAM_SIZE : integer;
DRAM_SIZE : integer; DRAM_SIZE : integer;
DRAM_INIT_SIZE : integer DRAM_INIT_SIZE : integer;
HAS_SPI_FLASH : boolean;
SPI_FLASH_OFFSET : integer
); );
port ( port (
clk : in std_ulogic; clk : in std_ulogic;
@ -44,6 +46,7 @@ architecture behaviour of syscon is
constant SYS_REG_CLKINFO : std_ulogic_vector(SYS_REG_BITS-1 downto 0) := "100"; constant SYS_REG_CLKINFO : std_ulogic_vector(SYS_REG_BITS-1 downto 0) := "100";
constant SYS_REG_CTRL : std_ulogic_vector(SYS_REG_BITS-1 downto 0) := "101"; constant SYS_REG_CTRL : std_ulogic_vector(SYS_REG_BITS-1 downto 0) := "101";
constant SYS_REG_DRAMINITINFO : std_ulogic_vector(SYS_REG_BITS-1 downto 0) := "110"; constant SYS_REG_DRAMINITINFO : std_ulogic_vector(SYS_REG_BITS-1 downto 0) := "110";
constant SYS_REG_SPIFLASHINFO : std_ulogic_vector(SYS_REG_BITS-1 downto 0) := "111";


-- Muxed reg read signal -- Muxed reg read signal
signal reg_out : std_ulogic_vector(63 downto 0); signal reg_out : std_ulogic_vector(63 downto 0);
@ -52,6 +55,7 @@ architecture behaviour of syscon is
constant SYS_REG_INFO_HAS_UART : integer := 0; constant SYS_REG_INFO_HAS_UART : integer := 0;
constant SYS_REG_INFO_HAS_DRAM : integer := 1; constant SYS_REG_INFO_HAS_DRAM : integer := 1;
constant SYS_REG_INFO_HAS_BRAM : integer := 2; constant SYS_REG_INFO_HAS_BRAM : integer := 2;
constant SYS_REG_INFO_HAS_SPIF : integer := 3;


-- BRAMINFO contains the BRAM size in the bottom 52 bits -- BRAMINFO contains the BRAM size in the bottom 52 bits
-- DRAMINFO contains the DRAM size if any in the bottom 52 bits -- DRAMINFO contains the DRAM size if any in the bottom 52 bits
@ -64,6 +68,12 @@ architecture behaviour of syscon is
constant SYS_REG_CTRL_CORE_RESET : integer := 1; constant SYS_REG_CTRL_CORE_RESET : integer := 1;
constant SYS_REG_CTRL_SOC_RESET : integer := 2; constant SYS_REG_CTRL_SOC_RESET : integer := 2;


-- SPI Info register bits
--
-- Top 32-bit is flash offset which is the amount of flash
-- reserved for the FPGA bitfile if any
constant SYS_REG_SPI_INFO_IS_FLASH : integer := 0;

-- Ctrl register -- Ctrl register
signal reg_ctrl : std_ulogic_vector(SYS_REG_CTRL_BITS-1 downto 0); signal reg_ctrl : std_ulogic_vector(SYS_REG_CTRL_BITS-1 downto 0);
signal reg_ctrl_out : std_ulogic_vector(63 downto 0); signal reg_ctrl_out : std_ulogic_vector(63 downto 0);
@ -74,10 +84,13 @@ architecture behaviour of syscon is
signal reg_draminfo : std_ulogic_vector(63 downto 0); signal reg_draminfo : std_ulogic_vector(63 downto 0);
signal reg_dramiinfo : std_ulogic_vector(63 downto 0); signal reg_dramiinfo : std_ulogic_vector(63 downto 0);
signal reg_clkinfo : std_ulogic_vector(63 downto 0); signal reg_clkinfo : std_ulogic_vector(63 downto 0);
signal reg_spiinfo : std_ulogic_vector(63 downto 0);
signal info_has_dram : std_ulogic; signal info_has_dram : std_ulogic;
signal info_has_bram : std_ulogic; signal info_has_bram : std_ulogic;
signal info_has_uart : std_ulogic; signal info_has_uart : std_ulogic;
signal info_has_spif : std_ulogic;
signal info_clk : std_ulogic_vector(39 downto 0); signal info_clk : std_ulogic_vector(39 downto 0);
signal info_fl_off : std_ulogic_vector(31 downto 0);
begin begin


-- Generated output signals -- Generated output signals
@ -93,10 +106,12 @@ begin
info_has_uart <= '1' when HAS_UART else '0'; info_has_uart <= '1' when HAS_UART else '0';
info_has_dram <= '1' when HAS_DRAM else '0'; info_has_dram <= '1' when HAS_DRAM else '0';
info_has_bram <= '1' when BRAM_SIZE /= 0 else '0'; info_has_bram <= '1' when BRAM_SIZE /= 0 else '0';
info_has_spif <= '1' when HAS_SPI_FLASH else '0';
info_clk <= std_ulogic_vector(to_unsigned(CLK_FREQ, 40)); info_clk <= std_ulogic_vector(to_unsigned(CLK_FREQ, 40));
reg_info <= (0 => info_has_uart, reg_info <= (SYS_REG_INFO_HAS_UART => info_has_uart,
1 => info_has_dram, SYS_REG_INFO_HAS_DRAM => info_has_dram,
2 => info_has_bram, SYS_REG_INFO_HAS_BRAM => info_has_bram,
SYS_REG_INFO_HAS_SPIF => info_has_spif,
others => '0'); others => '0');
reg_braminfo <= x"000" & std_ulogic_vector(to_unsigned(BRAM_SIZE, 52)); reg_braminfo <= x"000" & std_ulogic_vector(to_unsigned(BRAM_SIZE, 52));
reg_draminfo <= x"000" & std_ulogic_vector(to_unsigned(DRAM_SIZE, 52)) when HAS_DRAM reg_draminfo <= x"000" & std_ulogic_vector(to_unsigned(DRAM_SIZE, 52)) when HAS_DRAM
@ -105,6 +120,9 @@ begin
else (others => '0'); else (others => '0');
reg_clkinfo <= (39 downto 0 => info_clk, reg_clkinfo <= (39 downto 0 => info_clk,
others => '0'); others => '0');
info_fl_off <= std_ulogic_vector(to_unsigned(SPI_FLASH_OFFSET, 32));
reg_spiinfo <= (31 downto 0 => info_fl_off,
others => '0');


-- Control register read composition -- Control register read composition
reg_ctrl_out <= (63 downto SYS_REG_CTRL_BITS => '0', reg_ctrl_out <= (63 downto SYS_REG_CTRL_BITS => '0',
@ -119,6 +137,7 @@ begin
reg_dramiinfo when SYS_REG_DRAMINITINFO, reg_dramiinfo when SYS_REG_DRAMINITINFO,
reg_clkinfo when SYS_REG_CLKINFO, reg_clkinfo when SYS_REG_CLKINFO,
reg_ctrl_out when SYS_REG_CTRL, reg_ctrl_out when SYS_REG_CTRL,
reg_spiinfo when SYS_REG_SPIFLASHINFO,
(others => '0') when others; (others => '0') when others;
wishbone_out.dat <= reg_out(63 downto 32) when wishbone_in.adr(2) = '1' else wishbone_out.dat <= reg_out(63 downto 32) when wishbone_in.adr(2) = '1' else
reg_out(31 downto 0); reg_out(31 downto 0);

Loading…
Cancel
Save