VHDL-2019 Interfaces & OSVVM Interfaces
Ever wanted to encapsulate all the signals of an interface (such as AXI4Lite, I2C, …) into a single record only to be confounded by not being able to specify the direction for the elements of the record?
In this article we will explore how to do this with either VHDL-2019 interfaces or OSVVM interfaces.
Step 1: A Record is the foundation
VHDL-2019 interfaces start with a record type declaration. If we encapsulated an AXI4 Lite Write Address interface into a record, it might look like Axi4LiteWriteAddressType shown below.
type Axi4LiteWriteAddressType is record AWAddr : std_logic_vector ; AWProt : std_logic_vector ; AWValid : std_logic ; AWReady : std_logic ; end record Axi4LiteWriteAddressType ;
Note that we did not size AWAddr or AWProt. This is a VHDL-2008 feature and we will return to how to size it later.
To complete the AXI4Lite bus, we would create similar records for the Write Data (Axi4LiteWriteDataType), Write Response (Axi4LiteWriteResponseType), Read Address (Axi4LiteReadAddressType), and Read Data (Axi4LiteReadDataType). From these we create a record that encapsulates the entire AXI4Lite interface (Axi4LiteType shown below).
type Axi4LiteType is record WriteAddress : Axi4LiteWriteAddressType ; WriteData : Axi4LiteWriteDataType ; WriteResponse : Axi4LiteWriteResponseType ; ReadAddress : Axi4LiteReadAddressType ; ReadData : Axi4LiteReadDataType ; end record Axi4LiteType ;
Step 2: Create a Mode View Declaration
A mode view declaration provides the IO direction of each element of the record. Axi4LiteWriteAddressMasterView shown below is a mode view declaration for the AXI4Lite Write Address interface.
view Axi4LiteWriteAddressMasterView of Axi4LiteWriteAddressType is AWAddr : out ; AWProt : out ; AWValid : out ; AWReady : in ; end view Axi4LiteWriteAddressMasterView ;
The mode view declaration for the AXI4Lite interface (Axi4LiteMasterView shown below) is composed by using the mode view declarations of the lower level interfaces.
view Axi4LiteMasterView of Axi4LiteType is WriteAddress : view Axi4LiteWriteAddressMasterView ; WriteData : view Axi4LiteWriteDataMasterView ; WriteResponse : view Axi4LiteWriteResponseMasterView ; ReadAddress : view Axi4LiteReadAddressMasterView ; ReadData : view Axi4LiteReadDataMasterView ; end view Axi4LiteMasterView ;
Step 3: Derive the Partner’s Model View
For an Axi4Lite Responder, we could write a mode view declaration that is exactly opposite as the one above. Alternately, we can derive it using the ‘converse attribute as shown below. Hey who said VHDL was verbose?
view Axi4LiteWriteAddressResponderView is Axi4LiteWriteAddressMasterView'converse ; view Axi4LiteResponderView is Axi4LiteMasterView'converse ;
Step 4: Use the Interface
On the interface, the mode (in, out, inout) is replaced by the mode view. If the mode view is unambiguous, it can be used by itself, as shown for AxiMaster1 below. The associated subtype can also be included, as shown for AxiMaster2.
entity ZYNQ is port ( Clk : in ; nReset : in ; AxiMaster1 : view Axi4LiteMasterView ; AxiMaster2 : view Axi4LiteMasterView of Axi4LiteType ; AxiResponder1 : view Axi4LiteResponderView ; AxiResponder2 : view Axi4LiteResponderView ; . . . ) ; end entity ZYNQ ;
For ZYNQ component, we may wish to have arrays of Axi4LiteType and then use this on the interface.
type Axi4LiteArrayType is array (natural range <>) of Axi4LiteType ;
Now our ZYNQ entity interface will be as follows.
entity ZYNQ is port ( Clk : in ; nReset : in ; AxiMasters : view (Axi4LiteMasterView) of Axi4LiteArrayType(1 to 2); AxiResponders : view (Axi4LiteResponderView) of Axi4LiteArrayType(1 to 4); . . . ) ; end entity ZYNQ ;
Step 5: Instantiate the Design
The object that connects to an interface is a regular signal. Here is where we need to constrain the elements of Axi4LiteType. This can be accomplished directly in signal declaration (shown in Axi4Master1 below) or by using a subtype declaration (shown in Axi4LiteType_A16_D32 below).
architecture structural of top is use work.ZynqComponentPkg.all ; signal Axi4Master1, Axi4Master2 : Axi4LiteType ( WriteAddress( AWAddr(15 downto 0) ), WriteData( WData(31 downto 0), WStrb(3 downto 0) ), ReadAddress( ARAddr(15 downto 0) ), ReadData( RData(31 downto 0) ) ) ; subtype Axi4LiteType_A16_D32 is Axi4LiteType( WriteAddress( AWAddr(15 downto 0) ), WriteData( WData(31 downto 0), WStrb(3 downto 0) ), ReadAddress( ARAddr(15 downto 0) ), ReadData( RData(31 downto 0) ) ) ; signal Axi4Responder1, Axi4Responder2, Axi4Responder3, Axi4Responder4 : Axi4LiteType_A16_D32 ; begin Zynq_1 : Zynq Port map ( Clk => Clk, nReset => nReset, AxiMasters(1) => Axi4Master1, AxiMasters(2) => Axi4Master2, AxiResponders(1) => Axi4Responder1, AxiResponders(2) => Axi4Responder2, AxiResponders(3) => Axi4Responder3, AxiResponders(4) => Axi4Responder4, . . . ) ;
Ready to start using this feature now? For verification models, OSVVM Interfaces allow you to emulate VHDL-2019 interfaces.
Step 1: Record + Elements with Resolved Types
Instead of mode views, OSVVM interfaces use records whose elements are a resolved type from the OSVVM package ResolutionPkg.
type AddressBusMasterTransactionRecType is record Rdy : bit_max ; Ack : bit_max ; Address : std_logic_vector_max ; AddrWidth : integer_max ; DataToModel : std_logic_vector_max ; DataFromModel : std_logic_vector_max ; . . . end record AddressBusMasterTransactionRecType ;
Types in ResolutionPkg use “maximum” as a resolution function and add the suffix “_max” to the type names. Maximum resolution picks the largest value driven on each element of signal object that has multiple drivers. The largest value is the right most value of a type (‘-‘ for std_ulogic). This works in conjunction with the left most value being the smallest and the default value for an object of that type.
ResolutionPkg provides the following (as subtypes): std_logic_max, std_logic_vector_max, unsigned_max, signed_max, bit_max, bit_vector_max, integer_max, integer_vector_max, time_max, time_vector_max, real_max, real_vector_max, character_max, string_max, boolean_max, and boolean_vector_max.
Step 2: Use Mode InOut on Interfaces
If all of the elements of the record are a type from ResolutionPkg, then the interface signal can have mode InOut and nothing more is needed. If an element of the record is not driven, then the value it contributes is the left most value of the type – which again is the smallest value.
entity Axi4LiteMaster is port ( -- Globals Clk : in ; nReset : in ; -- Testbench Transaction Interface TransRec : inout AddressBusMasterTransactionRecType ; -- AxiLite Interface AxiLiteBus : inout Axi4LiteRecType -- ** ) ; end entity Axi4LiteMaster ;
On the OSVVM Axi4LiteMaster, we handle the AxiLiteBus using std_logic_1164 types instead. In this case, all elements of Axi4Lite that are not directly driven by Axi4LiteMaster are driven to ‘Z’ to resolve them.
Step 3: Instantiate the Design
This is exactly identical to what was done when using VHDL-2019 interfaces above.
Step 4: Work Arounds
Not all simulators support the VHDL-2008 method of applying an element resolution function to an array (as done by vector subtypes of the form “_max”). As a result, ResolutionPkg provides a type of the form “_max_c”. It is simply an array whose element is of the form “_max”.
OSVVM verification components all use “_max_c” because it allows our models to work on a wider range of simulators. There is a small cost. Since types like std_logic_vector_max_c are types, when assigning between them and std_logic_vector, a type conversion, such as the following are required.
-- assigning a std_logic_vector to a std_logic_vector_max_c TransactionRec.DataToModel <= std_logic_vector_max_c(Data) ; -- assigning a std_logic_vector_max_c to a std_logic_vector ReadData := std_logic_vector(TransactionRec.DataFromModel) ;
Subtypes like std_logic_vector_max and std_logic_vector do not require a conversion function since they are both subtypes of std_ulogic_vector (since VHDL-2008), and hence, they convert automatically.
Like what you see in VHDL-2019 interfaces? Please look for the article on linked-in and “like” it. We want to use this to demonstrate to vendors the level of support for this language feature. Also make sure your vendors (simulation and synthesis) know you expect them to implement VHDL-2019 interfaces in their tools.
In the meantime, for your verification models, you can use OSVVM interfaces. Using OSVVM interfaces will make it simple to transition to VHDL-2019 interfaces. We have been using and teaching versions of OSVVM interfaces for 20+ years in SynthWorks Advanced VHDL Testbenches and Verification class.
In November of 2020, I edited this article to change the word Slave to Responder. In the US, #BLM is a big deal. The word Slave here never made sense. I had discussions with other Open Source developers about what to change the word to. No consensus was reached. There were numerous good proposals, some fun (minion), some good (target, …). I chose Responder because I could use it in the definition of what the model does – very simply a Responder responds to transactions issued to it by the Master. As you can see currently we are still using Master. It is a less offensive word and is commonly used in many other contexts. However, over time it too may change.
Some may disagree with me over whether this change was necessary or even good. I am ok with that. However, my other job/hobby is Yoga – I currently have invested around 500 hours of teacher training. As such if changing the name can make one less person uncomfortable or offended, I am in.