Semi-Complex RTL Record Based SPI Interface Use Case
Introduction
The SPI bus having existed since slightly before the wheel, it presents a familiar use case to introduce a slightly more complex block and interface structure to examine the use of the new VHDL record concepts.
Syntax remains extremely preliminary, and is presented only as to provide a concrete idea of the underlying concepts. A compressed tarball with the VHDL files (both 2008 version and according to the proposal) can be found
http://www.eda-twiki.org/twiki/pub/P1076/Ballots/SpiExample/../InterfaceAndBundleEnhancements/SPI_Bus.tar.gz.
Model
The case being modeled will be of an RTL FPGA containing a general purpose QSPI master block (quad SPI, transmitting a nibble at a time).
This FPGA is harnessed in a testbench simulating a board level design in which the QSPI interface connects to three devices on the PCB:
- a QSPI serial flash (read/write)
- a conventional SPI DAC (write-only)
- a conventional SPI ADC (read/write)
Each of these will connect to one active-low chip select (SSEL[3:0]) from the FPGA. MOSI[3:0] and SCLK drive from the master to all 3 slaves (with conventional SPI using MOSI[0] only) and MISO[3:0] has weak pullups when not being driven by any of the slaves (with conventional SPI using MISO[0] only).
SPI Bus Package
In order to share our definitions across the design, they are defined within a package:
package spi_bus_pkg is
generic(
CHIP_SELECTS : positive
);
subtype ssel_rng is natural range CHIP_SELECTS-1 downto 0;
subtype ssel_vst is std_logic_vector(ssel_rng);
end package spi_bus_pkg;
Main Wiring Bundle
The wiring bundle itself contains all the signals for the SPI bus:
type spi_r is record
mosi : std_logic_vector(3 downto 0); -- Data from master to slave
miso : std_logic_vector(3 downto 0); -- Data from slave to master
sclk : std_logic; -- Serial clock
ssel : ssel_vst; -- Chip selects
end record spi_r;
Master View
The master has a view onto the entire signal bundle:
record view qspi_master_view of spi_r is
element(
mosi : out;
miso : in;
sclk : out;
ssel : out
);
end record view qspi_master_view;
Slave View
Whether QSPI or conventional, an SPI needs only one chip select, so one main goal of the view is to pluck the correct one from the bundle:
record view qspi_slave of spi_r is
generic(
IDX : ssel_rng
);
element(
mosi : in;
miso : out;
sclk : in;
cs : in std_logic is ssel(IDX)
);
end record view qspi_slave;
record view spi_slave of spi_r is
generic(
IDX : ssel_rng
);
element(
din : in std_logic is mosi(0);
dout : out std_logic is miso(0);
sclk : in;
cs : in std_logic is ssel(IDX)
);
end record view spi_slave;
Question: Generic Selection in a Named Mode View. --
Brent Hayhoe - 2015-09-10
Procedures
Thinking ahead to our simulated SPI slaves; we'll want some simple procedures to enable us to work with the SPI bus in behavioral code:
procedure spi_bits(
view spi : spi_slave;
bits : in positive;
xmit : in std_logic_vector;
recv : out std_logic_vector;
Tpd : in time;
Ts : in time
) is
variable xd : std_logic_vector(bits-1 downto 0);
variable rd : std_logic_vector(bits-1 downto 0);
begin
xd := xmit;
for i in range 0 to bits-1 loop
wait until spi.sclk = '1';
spi.dout <= xd(bits-i-1) after Tpd;
rd(i) := spi.din'delayed(Ts);
end loop;
recv := rd;
end procedure spi_bits;
A similar procedure qspi_bits will take a qspi_slave.
Note: the SPI parameter has to be declared to be a view in the same syntactic place as it would otherwise be declared a signal, variable, or constant, because it's not any of those things: it's a view.
Question: Mode View Equivalence to Signals, Variables & Constants. --
Brent Hayhoe - 2015-09-10
Note: this points out some of the difficulties we're going to have with incorporating timing delays into the bundle syntax.
Note: there's still the IDX generic on the view to deal with somehow.
Implementing the (RTL) Master
Instantiate the interface package based on the 3 slaves as discussed:
package spi_bus_3slv is new spi_bus_pkg
generic map (
CHIP_SELECTS : 3
);
Create the SPI master complete with the bundle:
entity spi_master is
port(
clk : in std_logic;
rst : in std_logic;
...
spi : view spi_bus_3slv.qspi_master_view
);
end entity spi_master
architecture RTL of spi_master is
FOOBAR: process(clk, rst)
begin
...
spi.ssel <= (others => '1');
if cs_asserted then
spi.ssel(active_cs) <= '0';
end if;
if shiftdata then
spi.mosi <= sr(sr'high downto sr'high-3);
sr(3 downto 0) := spi.miso;
end if;
if makerisingedge then
spi.sclk <= '1';
elsif makefallingedge then
spi.sclk <= '0';
end if;
...
end process FOOBAR;
end architecture RTL;
And place it inside of an entire design:
entity FPGA is
port(
clk : in std_logic;
rst : in std_logic;
...
spi : view spi_bus_3slv.qspi_master_view
);
end entity FPGA
architecture Structural of FPGA is
...
begin
SPIMST: entity work.spi_master
port map (
clk => clk,
rst => rst,
...
spi => spi
);
...
end architecture Structural;
Note: do we want to force FPGA to bind to a view on the bundle, or should we allow FPGA to bind to the unqualified bundle?
Implementing the Simulation Slaves
The slaves use the defined slave views:
-- TODO: Get the generic package instantiation correct.
entity qspi_flash
generic (
pkg : spi_bus_pkg;
CS : pkg.ssel_rng;
Tpd : time := 1 ns;
Ts : time := 1 ns
);
port (
spi : view pkg.qspi_slave
generic map (IDX => CS);
wren : in std_logic := 1
);
end entity qspi_flash;
architecture Behavioral of qspi_flash is
begin
SIM: process
variable cmd : std_logic_vector(7 downto 0);
begin
spi.dout <= 'Z';
wait until cs = '0';
qspi_bits(spi, 8, x"00", cmd, Tpd, Ts);
case cmd is
when x"80" =>
...
end process SIM;
end architecture Behavioral;
entity spi_adc
generic (
CS : spi_bus_3slv.ssel_rng;
VREF: real;
Tpd : time := 5 ns;
Ts : time := 5 ns
);
port (
spi : view spi_bus_3slv.spi_slave
generic map (IDX => CS);
ain : in real
);
end entity spi_adc;
architecture Behavioral of spi_adc is
begin
SIM: process
variable adcdata : std_logic_vector(15 downto 0);
variable cmd : std_logic_vector(15 downto 0);
begin
spi.dout <= 'Z'
wait until cs = '0';
adcdata := STD_LOGIC_VECTOR(TO_UNSIGNED(INTEGER(ain * 65536.0 / VREF), 16));
spi_bits(spi, 16, adcdata, cmd, Tpd, Ts);
case cmd is
...
end process SIM;
end architecture Behavioral;
entity spi_dac
generic (
CS : spi_bus_3slv.ssel_rng;
VREF: real;
Ts : time := 5 ns
);
port (
spi : view spi_bus_3slv.spi_slave
generic map (IDX => CS);
aout : out real
);
end entity spi_dac;
architecture Behavioral of spi_dac is
begin
SIM: process
variable dacdata : std_logic_vector(15 downto 0);
constant dout : std_logic_vector(15 downto 0) := (others => 'Z');
begin
wait until cs = '0';
spi_bits(spi, 16, dout, dacdata, 0 ns, Ts);
aout <= REAL(TO_INTEGER(UNSIGNED(data))) * VREF/65536.0;
end process SIM;
end architecture Behavioral;
Note: avoiding having to create another view for the simulation DAC, complete with an associated procedure that doesn't drive dout, required creation of a dummy output that was always 'Z'. Not horrific, but not particularly elegant either.
Note: as implemented above, the binding of a specific SPI slave to a chip select requires that the selection mechanism be a generic of the slave itself. That doesn't seem right; the selection of which chip select goes to what should be entirely encapsulated in the wiring; the individual slave shouldn't even contain that as a concept. We'll re-examine this in `Proposal 2`.
Comment: Generics Used for Instantiation Identification. --
Brent Hayhoe - 2015-09-10
Top-Level Harness
At the testbench level, we need to connect together the FPGA (which contains the SPI master) with the three slaves:
entity Testbench
end entity Testbench;
architecture TB of Testbench is
signal clk : std_logic;
signal rst : std_logic;
signal spi : spi_bus_3slv.spi_r;
begin
DUT: entity work.FPGA
port map (
clk => clk,
rst => rst,
...
-- Because qspi_master is a view onto the spi_r, this
-- mapping is allowable.
spi => spi,
);
MEMORY: entity work.qspi_flash
generic map (
CS => 0
) port map (
-- This mapping is where the chip select is selected
-- using the CS generic above.
spi => spi
);
ADC: entity work.spi_adc
generic map (
CS => 1,
VREF => 3.0
) port map (
spi => spi,
adc => monitored_voltage
);
DAC: entity work.spi_dac
generic map (
CS => 2,
VREF => 3.0
) port map (
spi => spi,
aout => driven_voltage
);
-- And our resistive pullups.
spi.miso <= (others => 'Z');
end architecture TB;
Note: if the physical bundle is defined using the signal keyword, that may cause issues with trying to incorporate terminals, variables, etc.
Possible Refinements
Promoting view to a declaration
To try to get around some of the problems with the view, we'll upgrade views to be equivalent to signals, variables, or files in the syntax.
This syntax would probably give view definitions looking more like:
view qspi_master_view of spi_r : record is
element(
mosi : out;
miso : in;
sclk : out;
ssel : out
);
end view qspi_master_view;
In declarations, a view becomes something that contrasts a signal:
entity FPGA is
port(
clk : in std_logic;
rst : in std_logic;
...
view spi : spi_bus_3slv.qspi_master_view
);
end entity FPGA
And at the hierarchical level containing the bundle, the views would be instantiated at this level:
entity Testbench
end entity Testbench;
architecture TB of Testbench is
signal clk : std_logic;
signal rst : std_logic;
signal spi : spi_bus_3slv.spi_r;
alias spi_to_flash : qspi_slave is qspi_slave view of spi
generic map (IDX => 0)
begin
DUT: entity work.FPGA
port map (
clk => clk,
rst => rst,
...
-- Because qspi_master is a view onto the spi_r, this
-- mapping is allowable.
spi => qspi_master view of spi
);
MEMORY: entity work.qspi_flash
port map (
-- This mapping is where the chip select is selected
-- using the CS generic above.
spi => spi_to_flash
);
ADC: entity work.spi_adc
generic map (
VREF => 3.0
) port map (
spi => spi_slave view of spi generic map (IDX=> 1),
adc => monitored_voltage
);
DAC: entity work.spi_dac
generic map (
VREF => 3.0
) port map (
spi => spi_slave view of spi generic map (IDX=> 2),
aout => driven_voltage
);
-- And our resistive pullups.
spi.miso <= (others => 'Z');
end architecture TB;
This is still wrong because it requires spi to be defined as a signal, but feels closer to right conceptually.
Back to bundles
One possible further refinement would be to make a view both the top-level element and the slice of the element. View is obviously the wrong keyword at that point, so we'll go back to bundle, and try defining the package all over again:
bundle spi_r is
element (
-- These are all implicitly signals, but could be explicitly
-- variables, files, terminals, or other bundles.
--
mosi : std_logic_vector(3 downto 0); -- Data from master to slave
miso : std_logic_vector(3 downto 0); -- Data from slave to master
sclk : std_logic; -- Serial clock
ssel : ssel_vst -- Chip selects
);
end bundle spi_r;
subtype qspi_master_view is spi_r
element (
mosi : out;
miso : in;
sclk : out;
ssel : out
);
end subtype qspi_master_view;
subtype qspi_slave is spi_r
generic(
IDX : ssel_rng
);
element(
mosi : in;
miso : out;
sclk : in;
cs : in std_logic is ssel(IDX)
);
end subtype qspi_slave;
subtype spi_slave is spi_r
generic(
IDX : ssel_rng
);
element(
din : in std_logic is mosi(0);
dout : out std_logic is miso(0);
sclk : in;
cs : in std_logic is ssel(IDX)
);
end subtype spi_slave;
Now an spi_r is a bundle and every view, being a subtype, is a bundle as well, which means that the top level becomes:
architecture TB of Testbench is
signal clk : std_logic;
signal rst : std_logic;
bundle spi : spi_bus_3slv.spi_r;
alias spi_to_flash : bundle is spi(qspi_slave)
generic map (IDX => 0);
begin
DUT: entity work.FPGA
port map (
clk => clk,
rst => rst,
...
-- Because qspi_master is a view onto the spi_r, this
-- mapping is allowable.
spi => spi(qspi_master)
);
MEMORY: entity work.qspi_flash
port map (
-- This mapping is where the chip select is selected
-- using the CS generic above.
spi => spi_to_flash
);
ADC: entity work.spi_adc
generic map (
VREF => 3.0
) port map (
spi => spi(spi_slave) generic map (IDX => 1),
adc => monitored_voltage
);
DAC: entity work.spi_dac
generic map (
VREF => 3.0
) port map (
spi => spi(spi_slave) generic map (IDX => 2),
aout => driven_voltage
);
-- And our resistive pullups. These are not signal assignment
-- because they go to spi, they are signal assignement because
-- the miso element is a signal.
spi.miso <= (others => 'Z');
end architecture TB;
-
RobGaddi - 2015-09-03
Comments & Questions
Question: Generic Selection in a Named Mode View
With reference to your slave named
view and the
generic selection of a single chip select: what happens with the other chip select elements of the 'ssel' vector when that
view is applied, i.e. what mode do they default to in the
port connection of the composite vector type?
--
Brent Hayhoe - 2015-09-10
No connect. Specifically, the simpler lines in that definition are nothing other than syntactic sugar. The simple line
sclk : in;
is actually default case shorthand for
sclk : in std_logic is sclk;
But if it's not explicitly in the view list, it's not there.
--
RobGaddi - 2015-09-14
-- Return back to source.
Question: Mode View Equivalence to Signals, Variables & Constants
Where you equate a
view instantiation syntax as needing to be in the same syntactical place as the
signal/variable/constant part of the interface list structure: how do you differentiate a
view of a
signal with a composite type, to that of a
variable with a composite type.
--
Brent Hayhoe - 2015-09-10
That's one of thing I came around to as a problem in all this. If we want to allow for bundles of composite types (and I know Ernst at least has identified a need for terminal as an option), then a bundle is by definition NOT a signal, even if it's a bundle containing only signals. This is one of the issues that needs some serious talking about, and is what I was trying to get at in the "Back to Bundles" section.
--
RobGaddi - 2015-09-14
-- Return back to source.
Comment: Generics Used for Instantiation Identification
The
generic identification value is moot point. Should an instance be identified by the wiring to it, or should it be via a unique identification within the instance. Both may be valid concepts and have differing advantages.
--
Brent Hayhoe - 2015-09-10
Return back to source.