TWiki
>
P1076 Web
>
Interfaces
>
SpiRevised
(revision 2) (raw view)
Edit
Attach
---+ Revised SPI Example ---++ What is SPI? SPI refers to any number of variations on a 4 wire serial bus. The master asserts the chip select (usually active-low) and then performs a full duplex data transfer, presenting one bit at a time on MOSI (master out, slave in), accepting one bit at a time on MISO (master in, slave out), and toggling the SCLK (serial clock) to qualify the data. These names change to basically every possible variation, as does how the slave is supposed to relate MOSI, MISO, and SCLK, but the rule always remains that you only pay attention on MOSI and drive MISO when you are chip-selected. Most often, the rising edge of SCLK is used to tell the slave to 1) accept the MOSI data and 2) present a new bit of MISO data. To connect a single master to multiple slaves, the master will support multiple chip selects. Only the device that is chip selected is active; the others ignore the transaction. SPI is frequenly used to communicate to DACs, ADCs, EEPROMs, flash, and other fairly low bandwidth devices. The QSPI examples have been removed from this discussion, they complicated things while providing no value. ---++ Why SPI? SPI is a trivial interface and commonly used. There's a bit of asymmetry between the master and slave sides of the link, but there are no complicated remappings or fancy math involved; the master has a bunch of chip-selects; each slave only sees one. Any workable interface concept _must_ be able to handle such a simple case. For that matter, in keeping with the design principle of "simple things should be simple", the syntactic load of designing SPI interface packages should be fairly low. If this is hard, taking on a major challenge like AXI would become mind-boggling. ---++ Trying interfaces I reworked the SPI example a bit to work in Lieven's latest cut at the interfaces concept. To start, here's the revised package structure. <verbatim> ------------------------------------------------------------------------ -- Unconstrained vectors, rather than generics, form the basis for -- connecting together the various pieces of the puzzle. ------------------------------------------------------------------------ package spi_bus_pkg is -- Underlying types. Masters use an unconstrained vector for -- chip selects; actual instantiations of the master interface -- will have to deal with making these concrete. type spi_master_r is record mosi : std_logic; -- Data from master to slave miso : std_logic; -- Data from slave to master sclk : std_logic; -- Serial clock ssel : std_logic_vector -- Chip selects (active-low) end record spi_master_r; type spi_slave_r is record mosi : std_logic; -- Data from master to slave miso : std_logic; -- Data from slave to master sclk : std_logic; -- Serial clock ssel : std_logic; -- Chip select (active-low) end record spi_slave_r; port view master of spi_master_r is mosi => out; miso => in; sclk => out; ssel => out; end port view master; port view slave of spi_slave_r is mosi => in; miso => out; sclk => in; ssel => in; end port view slave; end package spi_bus_pkg; package body spi_bus_pkg is end package body spi_bus_pkg; ------------------------------------------------------------------------ -- Using entities rather than mapfunctions to connect masters/slaves -- to the physical wiring on the PCB, since they exist right now and -- mapfunctions don't. -- -- The use of the spaceship operator (<=>) below is intended to make -- these connections without introducing delta cycles. ------------------------------------------------------------------------ -- Connect an SPI slave to an SPI bus. Since the bus is of type -- spi_master_r, but drives into us, we need to use the reverse of a -- master interface (or an anti-master) on this entity, and an -- anti-slave on the other side to connect to the slave. -- -- FPGA spi_slave_link ADC -- ---------- -------------------- --------- -- Master |-------| Anti- Anti- |----| Slave | -- | | Master Slave | | | -- ---------- -------------------- --------- -- use work.spi_bus_pkg.all; entity spi_slave_link is generic ( CHIP_SELECT : natural ); port ( mst : bus spi_master_r(master)'reversed; slv : bus spi_slave_r(slave)'reversed; ); end entity spi_slave_link; architecture Behavioral of spi_slave_link is begin slv.mosi <=> slv.mosi; -- <---- slv.miso <=> slv.miso; -- ----> slv.sclk <=> slv.sclk; -- <---- slv.ssel <=> mst.ssel(CHIP_SELECT); -- <---- end architecture Behavioral; </verbatim> This can be used to create our synthesizable code: <verbatim> -- Read 16 bits at a time from 3 ADCs, present them broadside. -- Write 16 bits to 1 DAC. -- The spi port declaration below is the only place that the number -- of chip selects is ever defined; everything else inherits that from -- this single point. SSEL(2 downto 0) are ADCs, SSEL(3) is the DAC. -- use work.spi_bus_pkg.all; entity spi_communicator is port ( spi : bus spi_master_r(master)(ssel(3 downto 0)); dac : in std_logic_vector(15 downto 0); adc0 : out std_logic_vector(15 downto 0); adc1 : out std_logic_vector(15 downto 0); adc2 : out std_logic_vector(15 downto 0); clk : in std_logic; rst : in std_logic ); end entity spi_communicator; architecture Behavioral of spi_communicator is begin MACHINE: process variable data : std_logic_vector(15 downto 0); variable bitn : integer range data'range; variable dev : integer range spi_ssel'range; type state is (RESET, SSEL_GO, SCLK_FALL, SCLK_RISE, SSEL_STOP); begin wait until rising_edge(clk); case state is when RESET => spi.mosi <= 'U'; spi.ssel <= (others => '1'); spi.sclk <= '1'; adc0 <= (others => 'U'); adc1 <= (others => 'U'); adc2 <= (others => 'U'); state := SSEL_GO; dev := 0; when SSEL_GO => spi.ssel(dev) <= '0'; state := SCLK_FALL; bitn := data'high; when SCLK_FALL => spi.sclk <= '0'; state := SCLK_RISE; when SCLK_RISE => spi.sclk <= '1'; spi.mosi <= dac(bitn); data(bitn) := spi.miso; if bitn = 0 then state := SSEL_STOP; else state := SCLK_FALL; bitn := bitn - 1; end if; when SSEL_STOP => spi.ssel <= (others => '1'); case adc is when 0 => adc0 <= data; when 1 => adc1 <= data; when 2 => adc2 <= data; when 3 => null; end case; adc := 0 when adc = 3 else (adc + 1); state := SSEL_GO; end case; if rst then state := RESET; end if; end process MACHINE; end architecture spi_communicator; ---------------------------------------------------------------------- -- Wrap it in an FPGA that does, presumably, other things as well. use work.spi_bus_pkg.all; entity FPGA is port( -- Number of chip selects here is undefined; the spi_communicator is -- sufficient to provide a definite size. spi : bus spi_master_r(master); -- ... clk : in std_logic; rst : in std_logic ); end entity FPGA architecture Structural of FPGA is -- ... begin SPIMST: entity work.spi_communicator port map ( spi => spi, adc0 => intl_adc0, adc1 => intl_adc1, adc2 => intl_adc2, dac => dac, clk => clk, rst => rst ); -- ... end architecture Structural; </verbatim> And a testbench around it: <verbatim> ---------------------------------------------------------------------- -- Simulation models of ADC/DAC use ieee.numeric_std.all; use work.spi_bus_pkg.all; entity adc is port ( spi : bus spi_slave_r(slave); vin : in real; ); end entity adc; architecture Behavioral of adc is signal intl_miso : std_logic_vector := 'Z'; begin FAKEIT: process variable data : unsigned(15 downto 0); variable bitn : integer range data'high downto -1; begin intl_miso <= 'Z'; wait until falling_edge(spi.ssel); data := TO_UNSIGNED(vin / 5.0 * 65536, data'length); bitn := data'high; loop if bitn = -1 then intl_miso <= 'U'; else intl_miso <= data(bitn); bitn := bitn - 1; end if; wait until rising_edge(spi.sclk) or rising_edge(spi.ssel); exit when spi.ssel'event; end loop; end process FAKEIT; spi.miso <= intl_miso after 10 ns; end architecture Behavioral; use ieee.numeric_std.all; use work.spi_bus_pkg.all; entity dac is port ( spi : bus spi_slave_r(slave); vout : out real := 0.0; ); end entity dac; architecture Behavioral of dac is begin FAKEIT: process variable data : unsigned(15 downto 0); variable bitn : integer range data'high downto -1; begin spi.miso <= 'Z'; wait until falling_edge(spi.ssel); data := (others => '0'); loop wait until rising_edge(spi.sclk) or rising_edge(spi.ssel); exit when spi.ssel'event; data := data(14 downto 0) & spi.mosi; end loop; vout <= REAL(TO_INTEGER(data)) * 5.0 / 65536.0 after 10 ns; end process FAKEIT; end architecture Behavioral; ---------------------------------------------------------------------- -- And the complete testbench use work.spi_bus_pkg.all; entity Testbench end entity Testbench; architecture TB of Testbench is signal clk : std_logic; signal rst : std_logic; -- Notice that SPI is just a normal signal here; the top level has no -- interest in the whole interfaces concept. Size is defined by the FPGA, -- which gets it from the spi_communicator. signal spi : spi_master_r; signal daclnk : spi_slave_r; ... begin DUT: entity work.FPGA port map ( clk => clk, rst => rst, spi => spi ); ADCS: for i in 0 to 2 generate signal lnk : spi_slave_r; begin -- Translate the master bus to a slave bus. RIPPER: entity spi_slave_link generic map ( CHIP_SELECT => i ) port map ( mst => spi, slv => lnk ); -- And hook the ADC to that slave bus. ADC: entity work.adc port map ( spi => lnk, vin => analog_voltage(i) ); end generate ADCs; DACRIPPER: entity spi_slave_link generic map ( CHIP_SELECT => 3 ) port map ( mst => spi, slv => daclnk ); DAC: entity work.dac port map ( spi => daclnk, vout => driven_voltage ); -- And a resistive pullup for the tri-state MISO line. spi.miso <= 'H'; end architecture TB; </verbatim> The use of unconstrained arrays, here used for the SPI SSEL lines, allows all this to be done without the use of generic packages, nor adding genericizers to the underlying record concept. This allows everything in the package to see everything else, and avoids the incompatibilities I had previously been concerned about. Entities are usable as mapfunctions, without having to add anything new to the language. That said, they sure ain't elegant. A functional syntax could be substantially cleaner, as they could avoid having to create and name intermediate signals. Especially if we also do FunctionKnowsVectorSize. -- %USERSIG{RobGaddi - 2016-08-31}% ---++ Comments <br />%COMMENT%
Attachments
Attachments
I
Attachment
Action
Size
Date
Who
Comment
vhd
FPGA.vhd
manage
3.4 K
2016-08-31 - 19:48
RobGaddi
vhd
spi_bus_pkg.vhd
manage
3.0 K
2016-08-31 - 19:48
RobGaddi
vhd
testbench.vhd
manage
3.4 K
2016-08-31 - 19:48
RobGaddi
Edit
|
Attach
|
P
rint version
|
H
istory
:
r5
<
r4
<
r3
<
r2
<
r1
|
B
acklinks
|
V
iew topic
|
Raw edit
|
More topic actions...
Topic revision: r1 - 2016-08-31 - 19:48:54 -
TWikiGuest
P1076
Log In
or
Register
P1076 Web
Create New Topic
Index
Search
Changes
Notifications
RSS Feed
Statistics
Preferences
Webs
Main
P1076
Ballots
LCS2016_080
P10761
P1647
P16661
P1685
P1734
P1735
P1778
P1800
P1801
Sandbox
TWiki
VIP
VerilogAMS
Copyright © 2008-2025 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki?
Send feedback