TWiki
>
P1076 Web
>
Vhdl2019CollectedRequirements
>
InterfaceAndBundleEnhancements
>
SpiExample
(2016-08-20,
RobGaddi
)
(raw view)
E
dit
A
ttach
---+ Semi-Complex RTL Record Based SPI Interface Use Case %TOC% ---++ 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 [[%ATTACHURL%/../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: 1 <span style="background-color: transparent;">a QSPI serial flash (read/write)</span> 1 <span style="background-color: transparent;">a conventional SPI DAC (write-only)</span> 1 <span style="background-color: transparent;">a conventional SPI ADC (read/write)</span> 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: <verbatim>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;</verbatim> ---+++ Main Wiring Bundle The wiring bundle itself contains all the signals for the SPI bus: <verbatim> 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;</verbatim> ---+++ Master View The master has a view onto the entire signal bundle: <verbatim> record view qspi_master_view of spi_r is element( mosi : out; miso : in; sclk : out; ssel : out ); end record view qspi_master_view;</verbatim> ---+++ 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: <verbatim> 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;</verbatim> #BAHQuestion1Return [[#BAHQuestion1Anchor][Question: Generic Selection in a Named Mode View.]] -- [[Main.BrentHahoe][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: <verbatim> 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;</verbatim> 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. #BAHQuestion2Return [[#BAHQuestion2Anchor][Question: Mode View Equivalence to Signals, Variables & Constants.]] -- [[Main.BrentHahoe][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: <verbatim> package spi_bus_3slv is new spi_bus_pkg generic map ( CHIP_SELECTS : 3 );</verbatim> Create the SPI master complete with the bundle: <verbatim> 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;</verbatim> And place it inside of an entire design: <verbatim> 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;</verbatim> 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: <verbatim>-- 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;</verbatim> 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`. #BAHComment3Return [[#BAHComment3Anchor][Comment: Generics Used for Instantiation Identification.]] -- [[Main.BrentHahoe][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: <verbatim> 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;</verbatim> 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: <verbatim> view qspi_master_view of spi_r : record is element( mosi : out; miso : in; sclk : out; ssel : out ); end view qspi_master_view;</verbatim> In declarations, a view becomes something that contrasts a signal: <verbatim> entity FPGA is port( clk : in std_logic; rst : in std_logic; ... view spi : spi_bus_3slv.qspi_master_view ); end entity FPGA</verbatim> And at the hierarchical level containing the bundle, the views would be instantiated at this level: <verbatim> 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;</verbatim> 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: <verbatim> 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;</verbatim> 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: <verbatim> 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;</verbatim> - Main.RobGaddi - 2015-09-03 ---++ Comments & Questions #BAHQuestion1Anchor ---+++!! 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? -- [[Main.BrentHahoe][Brent Hayhoe]] - 2015-09-10 No connect. Specifically, the simpler lines in that definition are nothing other than syntactic sugar. The simple line <verbatim> sclk : in;</verbatim> is actually default case shorthand for <verbatim> sclk : in std_logic is sclk;</verbatim> But if it's not explicitly in the view list, it's not there. -- Main.RobGaddi - 2015-09-14 %COMMENT{above}% [[#BAHQuestion1Return][-- Return back to source.]] #BAHQuestion2Anchor ---+++!! 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. -- [[Main.BrentHahoe][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. -- Main.RobGaddi - 2015-09-14 %COMMENT{above}% [[#BAHQuestion2Return][-- Return back to source.]] #BAHComment3Anchor ---+++!! 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. -- [[Main.BrentHahoe][Brent Hayhoe]] - 2015-09-10 %COMMENT{above}% [[#BAHComment3Return][Return back to source.]]
E
dit
|
A
ttach
|
P
rint version
|
H
istory
: r12
<
r11
<
r10
<
r9
<
r8
|
B
acklinks
|
V
iew topic
|
Ra
w
edit
|
M
ore topic actions
Topic revision: r12 - 2016-08-20 - 22:02:10 -
RobGaddi
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