Skip to content
This repository has been archived by the owner on Apr 15, 2023. It is now read-only.
/ wb_spi_bridge Public archive

πŸŒ‰ A transparent Wishbone-to-SPI bridge supporting Execute-In-Place (XIP).

License

Notifications You must be signed in to change notification settings

stnolting/wb_spi_bridge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

16 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸŒ‰ Transparent Wishbone-to-SPI Bridge

License DOI

Overview

This is a Wishbone to SPI bridge that converts Wishbone transactions from a host into SPI-memory transactions. It can be used to map a SPI flash, EEPROM or RAM into the address space of a processor. The bridge operates transparently, so it behaves like a memory-mapped read/write memory as it hides the SPI protocol - at the cost of additional latency. Hence, the bridge supports Execute-In-Place (XIP) allowing a processor to directly access (and execute) instructions and data located in the SPI memory.

ℹ️ This is another "spin-off" project of the NEORV32 RISC-V Processor. The bridge might be added as XIP module to the processor someday.

Key features

  • Wishbone b4 compatible, classic or pipelined single-access transfers
  • supports execute-in-place (XIP)
  • single clock domain
  • configurable SPI clock speed, clock mode (0,1,2,3) and address size
  • supports 32-bit, 16-bit and 8-bit write accesses
  • little-endian byte-order
  • technology, vendor and platform agnostic
  • single VHDL file, no additional libraries required
  • FPGA-proven

TODOs / Ideas / Help-Wanted

  • add burst support
  • add dSPI/qSPI support
  • check SPI flash status register during write
  • ...

Top Entity

The bridge is based on a single VHDL file rtl/wb_spi_bridge.vhd. The top entity is wb_spi_bridge, which can be instantiated directly without the need for any special libraries.

entity wb_spi_bridge is
  generic (
    SPI_CLK_DIV  : positive;   -- clock divider: f(spi_clk_o) = f(clk_i) / 2*SPI_CLK_DIV, min 2
    SPI_ABYTES   : positive;   -- number of address bytes in SPI protocol, 1..4
    SPI_CPHA     : std_ulogic; -- clock phase
    SPI_CPOL     : std_ulogic; -- idle polarity
    WB_ADDR_BASE : std_ulogic_vector(31 downto 0); -- module base address, size-aligned
    WB_ADDR_SIZE : positive    -- module address space in bytes
  );
  port (
    -- wishbone host interface --
    wb_clk_i   : in  std_ulogic; -- clock
    wb_rstn_i  : in  std_ulogic; -- reset, async, low-active
    wb_srstn_i : in  std_ulogic; -- reset, SYNC, low-active
    wb_adr_i   : in  std_ulogic_vector(31 downto 0); -- address
    wb_dat_i   : in  std_ulogic_vector(31 downto 0); -- read data
    wb_dat_o   : out std_ulogic_vector(31 downto 0); -- write data
    wb_we_i    : in  std_ulogic; -- read/write
    wb_sel_i   : in  std_ulogic_vector(03 downto 0); -- byte enable
    wb_stb_i   : in  std_ulogic; -- strobe
    wb_cyc_i   : in  std_ulogic; -- valid cycle
    wb_ack_o   : out std_ulogic; -- transfer acknowledge
    wb_err_o   : out std_ulogic; -- transfer error
    -- 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 wb_spi_bridge;

The top entity instantiates two in-file entities:

  • wb_spi_bridge_link handles the SPI memory protocol (read, write, write-enable)
  • wb_spi_bridge_phy handles the actual serial peripheral interface protocol

⚠️ The module features two resets: an asynchronous one (wb_rstn_i) and a synchronous one (wb_srstn_i), which are both are low-active. The asynchronous reset is required to bring the bridge into a defined state. The synchronous reset is optional and can be used to reset the bridge from the application logic. Tie this reset to 1 if it is not used.

Configuration

The application-specific configuration of the Wishbone-to-SPI bridge is done using the generics of the top entity.

SPI Clock

The SPI clock frequency is configures using the SPI_CLK_DIV generic. It defines a scaler that is applies to the clock input clk_i to obtain the actual SPI clock. The SPI clock is defined by f_spi = f_main / (2 * SPI_CLK_DIV). The minimum allowed value for the clock scaler is 2 resulting in a maximal SPI clock speed of 1/4 of the input clock speed.

The actual clock mode is configured via SPI_CPHA and SPI_CPOL. The combination of these two generics allow to configure any of the four standard SPI clock modes. For more information regarding the clock modes see the SPI Wikipedia article.

SPI Memory

The address size of the SPI memory, which is defined by it's capacity, is configured by the SPI_ABYTES generic. Allowed values are 1, 2, 3 and 4. Most SPI EEPROMs use an address size of 16-bit resulting in SPI_ABYTES = 2. Many standard SPI flash memories use 24-bit for the addressing resulting in SPI_ABYTES = 3.

The bridge uses only three standard memory commands. If the commands of a specific SPI memory are different, you can adapt them in the VHDL source file:

-- spi memory opcodes -----------------------------------------------------------
constant cmd_write_c : std_ulogic_vector(7 downto 0) := x"02"; -- write data
constant cmd_read_c  : std_ulogic_vector(7 downto 0) := x"03"; -- read data
constant cmd_wren_c  : std_ulogic_vector(7 downto 0) := x"06"; -- write enable
-- ------------------------------------------------------------------------------

Wishbone Access

The 32-bit base address of the bridge is defined by the WB_ADDR_BASE generic. The size of occupied address space, which is defined the SPI memory's capacity, is configured by the WB_ADDR_SIZE generic. The address space size has to be a power of two and the base address has to be naturally aligned to the this size.

Example: SPI flash with 64kB (64*1024 bytes):

WB_ADDR_BASE => 0x"FFFF0000",
WB_ADDR_SIZE => 64*1024

❌ In this case a base address of 0xFFFF8000 would be invalid as it is not naturally aligned to a 64kB boundary.

Interface

The host interface is based on the Wishbone b4 specification. It supports classic and pipelined single-access transfers. Burst transfer are not supported yet.

Classic-mode read access example Pipelined-mode write access example
classic_rd pipelined_wr
  • classic-mode: stb and cyc both stay asserted until the transfer is completed.
  • pipelined-mode: cyc stays asserted until the transfer is completed. stb is asserted only for one cycle right at the beginning of the transfer. |

A transfer is completed when the acknowledge signal ack is set (active for one cycle). This indicates a successful termination. A transfer is also completed when the error signal err is set (active for one cycle) indicating an erroneous termination. The bridge will raise the error signal only if an unsupported byte-enable mask is set (see below).

Access Granularity

The bridge supports 32-bit, 16-bit and 8-bit write accesses. Read accesses always return a full 32-bit word. Note that the bridge accesses memory data in little-endian byte-order. The sel byte mask signal defines the data quantity of the according access. The following byte masks are supported:

sel r/w Data quantity
1111 read full 32-bit word
1111 write full 32-bit word
1100 write upper 16-bit half-word
0011 write lower 16-bit half-word
0001 write byte 0
0010 write byte 1
0100 write byte 2
1000 write byte 3

All remaining byte mask combination will raise a transfer error (err is set for one cycle instead of ack).

Throughput

The bridge always sends a complete command sequence to the SPI memory for any kind of Wishbone transfer (bursts are not supported yet). This means that the memory opcode, the address bits and the actual data are transferred for every single transaction. The following table shows the number of bit to transfer via SPI for each type of transaction. Each bit requires one clock of the SPI clock.

r/w Data size CMD bits Address bits Data bits Total bits
read word (32-bit) 8 8*SPI_ABYTES 32 8 + 8*SPI_ABYTES + 32
write word (32-bit) 8 + 8 8*SPI_ABYTES 32 8 + 8 + 8*SPI_ABYTES + 32
write half-word (16-bit) 8 + 8 8*SPI_ABYTES 16 8 + 8 + 8*SPI_ABYTES + 16
write byte (8-bit) 8 + 8 8*SPI_ABYTES 8 8 + 8 + 8*SPI_ABYTES + 8

For example a word-wide write access to a SPI flash that uses 24-bit addressing requires 8 + 8 + 8*3 + 32 = 72 SPI clock cycles. One SPI clock cycle equals 2 * SPI_CLK_DIV cycles of the main clock clk_i.

ℹ️ Any kind of write access requires to set the memory's write enable latch, which is done sending a single 8-bit command.

⚠️ Due to the high latency, processors should use an instruction cache when executing programs directly from SPI storage (XIP) to increase performance.

⚠️ Note that there is no check of the memory's status register yet (to check a write access has been completed). Hence, writing a SPI memory at very high clock speeds should be avoided to ensure data is actually written to the memory. Another "safe" option is to read-back every written data right after the write access to ensure it has actually been written to memory.

Simulation

The projects provides a simple testbench sim/wb_spi_bridge_tb.vhd, which can be simulated by GHDL via the provides script:

wb_spi_bridge/sim$ sh ghdl.sh

The simulation will run for 1ms using a 100MHz clock. During this time all supported (and one illegal) Wishbone accesses are triggered. Note that this testbench is intended for manual waveform inspection - it is not self-checking! The waveform data is stored to sim/wb_spi_bridge.vcd, which can be viewed by using gtkwave:

wb_spi_bridge/sim$ gtkwave wb_spi_bridge.vcd

Verification

The bridge is FPGA-proven. It has been tested by directly connecting it to the Wishbone interface port of the NEORV32 RISC-V Processor (no Wishbone interconnect) interfacing a 25LC512 SPI EEPROM.

The NEORV32 software examples provides a "bus explorer" program that allows to perform arbitrary Wishbone accesses via a UART terminal. The bridge can successfully handle all read and write operations. Furthermore, the NEORV32 can successfully execute in place programs from the SPI module.

Hardware Utilization

Exemplary mapping results for the following configuration :

SPI_CLK_DIV  => 32,
SPI_ABYTES   => 2, -- 16-bit addressing
SPI_CPHA     => '0',
SPI_CPOL     => '0', -- clock mode 0
WB_ADDR_BASE => x"F0000000",
WB_ADDR_SIZE => 64*1024
Intel Cyclone IV EP4CE22F17C6N @100MHz
Hierarchy                                      Logic Cells   Logic Registers
----------------------------------------------------------------------------
wb_spi_bridge:wb_spi_bridge_inst               233 (115)     174 (88)
  wb_spi_bridge_link:wb_spi_bridge_link_inst   118  (16)      86  (8)
    wb_spi_bridge_phy:wb_spi_bridge_phy_inst   102 (102)      78 (78)