1 - Testbench Guide For OVM
1 - Testbench Guide For OVM
1 - Testbench Guide For OVM
w w w.m e n t o r.c o m
W H I T E P A P E R
UVM/OVM Documentation - Copyright (c) 2011 Mentor Graphics Corporation - http://uvm.mentor.com PDF generated at: Fri, 08 Jul 2011 17:30:57 PST
Table of Contents
Articles
Ovm/Testbench/Overview Ovm/Testbench/Build Ovm/Testbench/Blocklevel Ovm/Testbench/IntegrationLevel Ovm/Agent OVM Phases OVM Factory 1 6 13 21 28 33 36
Datestamp:
- This document is a snapshot of dynamic content from the Online Methodology Cookbook - Created from http://uvm.mentor.com on Fri, 08 Jul 2011 10:30:58 PDT
Ovm/Testbench/Overview
Ovm/Testbench/Overview
You are reading the OVM version of this article. You can find the new UVM version at [[]]
Ovm/Testbench/Overview
#10; C.print(); C = null; // C has been deferenced, object can be garbage collected end endmodule: tb
The OVMPackage
The OVM package contains a class library that comprises three main types of classes, ovm_components which are used to construct a class based hierarchical testbench structure, ovm_objects which are used as data structures for configuration of the testbench and ovm_transactions which are used in stimulus generation and analysis. An OVM testbench will always have a top level module which contains the DUT and the testbench connections to it. The process of connecting a DUTto an OVMclass based testbench is described in the article on DUT- testbench connections. The top level module will also contain an initial block which willcontain a call to the OVM run_test()method. Thismethod starts the execution of the OVMphases, which controls the orderin which the testbench is built, stimulus isgenerated andthenreports onthe results of the simulation.
Ovm/Testbench/Overview The Agent Most DUTs have a number of different signal interfaces, each of which have their own protocol. The OVM agent collects together a group of ovm_components focused around a specific pin-level interface. The purpose of the agent is to provide a verification component which allows users to generate and monitor pin level transactions. A SystemVerilog packageis used to gather all the classes associated with an agent together in one namespace. The contents of an agent package will usually include: A Sequence_item -The agent will have one or more sequence items which are used to either define what pin level activity will be generated by the agent or to report on what pin level activity has been observed by the agent.
Active agent.gif
A Driver - The driver is responsible for converting the data inside a series of sequence_items into pin level transactions. A Sequencer - The role of the sequencer is to route sequence_items from a sequence where they are generated to/from a driver. A Monitor - The monitor observes pin level activity and converts its observations into sequence_items which are sent to components such as scoreboards which use them to analyse what is happening in the testbench. Configuration object - A container object, used to pass information to the agent which affects what it does and how it is built and connected. Each agent should have a configuration object, this will contain a reference to the virtual interface which the driver and the monitor use to access pin level signals. The configuration object will also contain other data members which will control which of the agents sub-components are built, and it may also contain information that affects the behaviour of the agents components (e.g. error injection, or support for a protocol variant) The agent configuration object contains an active bit which can be used to select whether the agent is passive - i.e. the driver and sequencer are not required, or active. It may also contain other fields which control whether other sub-component classes such as functional coverage monitors or scoreboards get built or not. Other classes thatmight be included inan agent package: Functional coverage monitor - to collect protocol specific functional coverage information Scoreboard - usually of limited use A responder - A driver that responds to bus events rather than creating them (i.e. a slave version of the driver rather than a master version). (API) Sequences - Utility sequences likely to be of general use, often implementing an APIlayer for the driver.
Ovm/Testbench/Overview The env The environment, or env, is a container component for grouping together sub-components orientated around a block, or around a collection of blocks at higher levels of integration. Block Level env In a block level OVM testbench, the environment, or env, is used to collect together the agents needed for the DUTs interfaces together. Like the agent, the different classes associated with the env are organised into a SystemVerilog package, which will import the agent packages. In addition to the agents the env will also contain some or all of the following types of components: Configuration object - The env will have a configuration object that enables the test writer to control which of the environments sub-components are built. The env configuration object should also contain a handle for the configuration object for each of the agents that it contains. These are then assigned to the envs agents using set_config_object. Scoreboards - A scoreboard is an analysis component that checks that the DUT is behaving correctly. OVM scoreboards use analysis transactions from the monitors implemented inside agents. A scoreboard will usually compare transactions from at least two agents, which is why it is usually present in the env. Predictors - A predictor is a component that computes the response expected from the stimulus, it is generally used in conjunction with other components such as the scoreboard. Functional Coverage Monitors - A functional coverage monitor analysis component contains one or more covergroups which are used to gather functional coverage information relating to what has happened in a <span style="background-color: navy; color: white;" />testbench during a test case. A functional coverage monitor is usually specific to a DUT. Virtual Sequencers - A virtual sequencer is used in the stimulus generation process to allow a single sequence to control activity via several agents. The diagram shows a block level testbench which consists of a series of tests which build an env which contains several analysis components and two agents.
Ovm/Testbench/Overview Integration Level env When blocks are integrated to create a sub-system, vertical reuse can beachieved byreusing the envs used in each of the block level testbenches merged together into a higher levelenv. The block level envs provide all of the structures required to test each block, but as a result of the integration process, not all the block level interfaces are exposed at the boundary and so some of the functionality of the block level envs will be redundant. The integration level env then needs to be configured to make agents connected to internal interfaces passive, or possibly even not to include an agent at all. This configuration is done in the test, and the configuration object for each sub-env is nested inside the configuration object for the env at the next level of hiearchy. As an illustration, the diagram shows a first level of integration where two block level environments have been merged together to Integration level ovm hierarchy.gif test the peripheral file. The 'greyed out' components in the envs are components that are no longer used in the integration level envrionment. The configuration object for the integration level contains the rest of the configuration objects nested inside it. Furtherlevels of integration can be accomodated by layering multiple integration level envs inside eachother.
Ovm/Testbench/Build
Ovm/Testbench/Build
You are reading the OVM version of this article. You can find the new UVM version at [[]]
The first phase of an OVMtestbench is the build phase. During this phase the ovm_component classes that make up the testbench hierarchy are constructed into objects. The construction process works top-downwith each level of the hierarchy being constructed before the next level is configured and constructed. This approach to construction is referred to as deferred construction. The OVM testbench is is activated when the run_test() method is called inan initial block in the top level test module. This method is an OVM static method, and it takes a string argument or a string passed in from the +OVM_TESTNAME plusarg that defines the test to be run and constructs it via the factory. The +OVM_TESTNAME takes precedence over a string argument passed into run_test(). Then the OVM infrastructure starts the build phase by calling the test classes build method During the execution of the tests build phase, the testbench component configuration objects are prepared and assignments to the testbench module interfaces are made to the virtual interface handles in the configuration objects. The next step is for the configuration objects to be put into the testsconfiguration table. Finallythe next level of hierarchy is built. At the nextlevel of hierarchy the configuration object prepared by the test is "got"andfurtherconfiguration may take place,before the configuration object is used to guidethe configuration and conditional construction of the next level of hierarchy. This conditional construction affects the topology or hierarchical structure of the testbench. The build phase works top-down and so the processis repeated for theeach successivelevel of the testbench hiearchy until the bottom of the hierarchical tree is reached. After the build phase has completed, the connect phase is used to ensure that all intra-component connections are made. The connect phase works from the bottom to the top of the testbench hierarchy. Following the connect phase, the rest of the OVMphases run to completion before control is passed back to the testbench module.
Ovm/Testbench/Build
For a given design verification environment most of the work done in the build method will be the same for all the tests, so it is recommended that a test base class is created which can be easily extended for each of the test cases. To help explain how the test build process works, a block level verification environment will be referred to. This example is an environment for an SPImaster interface DUT and it contains two agents, one for its APBbus interface and one for its SPI interface. A detailed account of the build and connect processes for this example can be found in the Block Level Testbench Examplearticle.
Factory Overrides
The OVMfactory allows an OVMclass to be substituted with another derived class at the point of construction. Thisfacility can be useful for changingor updating componentbehaviour or for extending a configuration object. The factory override must be specified beforethe target object is constructed, so it is convenientto do itat the start of thebuild process.
Ovm/Testbench/Build
// OVM Factory Registration Macro // `ovm_component_utils(spi_test_base) //-----------------------------------------// Data Members //-----------------------------------------//-----------------------------------------// Component Members //-----------------------------------------// The environment class spi_env m_env; // Configuration objects spi_env_config m_env_cfg; apb_agent_config m_apb_cfg; spi_agent_config m_spi_cfg; //-----------------------------------------// Methods //-----------------------------------------// Standard OVM extern function extern function extern funciton extern funciton Methods: new(string name = "spi_test_base", ovm_component parent = null); void build(); void configure_env(spi_env_config cfg); void configure_apb_agent(apb_agent_config cfg);
endclass: spi_test_base function spi_test_base::new(string name = "spi_test_base", ovm_component parent = null); super.new(name, parent); endfunction // Build the env, create the env configuration // including any sub configurations and assigning virtural interfaces function void spi_test_base::build(); // Create env configuration object m_env_cfg = spi_env_config::type_id::create("m_env_cfg"); // Call function to configure the env configure_env(m_env_cfg); // Create apb agent configuration object m_apb_cfg = apb_agent_config::type_id::create("m_apb_cfg"); // Call function to configure the apb_agent configure_apb_agent(m_apb_cfg); // More to follow endfunction: build // // Convenience function to configure the env // // This can be overloaded by extensions to this base class function void spi_test_base::configure_env(spi_env_config cfg); cfg.has_functional_coverage = 1; cfg.has_reg_scoreboard = 0; cfg.has_spi_scoreboard = 1; endfunction: configure_apb_agent // // Convenience function to configure the apb agent // // This can be overloaded by extensions to this base class function void spi_test_base::configure_apb_agent(apb_agent_config cfg); cfg.active = OVM_ACTIVE; cfg.has_functional_coverage = 0; cfg.has_scoreboard = 0; endfunction: configure_apb_agent `endif // SPI_TEST_BASE
Ovm/Testbench/Build
Ovm/Testbench/Build
10
// // Configuration object for the spi_env: // `ifndef SPI_ENV_CONFIG `define SPI_ENV_CONFIG // // Class Description: // // class spi_env_config extends ovm_object; // OVM Factory Registration Macro // `ovm_object_utils(spi_env_config) //-----------------------------------------// Data Members //-----------------------------------------// Whether env analysis components are used: bit has_functional_coverage = 1; bit has_reg_scoreboard = 0; bit has_spi_scoreboard = 1; // Configurations for the sub_components apb_config m_apb_agent_cfg; spi_agent_config m_spi_agent_cfg; //-----------------------------------------// Methods //-----------------------------------------// Standard OVM Methods: extern function new(string name = "spi_env_config"); endclass: spi_env_config function spi_env_config::new(string name = "spi_env_config"); super.new(name); endfunction `endif // SPI_ENV_CONFIG // // Inside the spi_test_base class, the agent config handles are assigned: // // The build method from before, adding the apb agent virtual interface assignment // Build the env, create the env configuration // including any sub configurations and assigning virtural interfaces function void spi_test_base::build(); // Create env configuration object m_env_cfg = spi_env_config::type_id::create("m_env_cfg");
Ovm/Testbench/Build
// Call function to configure the env configure_env(m_env_cfg); // Create apb agent configuration object m_apb_cfg = apb_agent_config::type_id::create("m_apb_cfg"); // Call function to configure the apb_agent configure_apb_agent(m_apb_cfg); // Adding the apb virtual interface: m_apb_cfg.APB = ovm_container #(virtual apb_if)::get_value_from_config(this, "APB_vif"); // Assign the apb_angent config handle inside the env_config: m_env_cfg.m_apb_agent_cfg = m_apb_cfg; // Repeated for the spi configuration object m_spi_cfg = spi_agent_config::type_id::create("m_spicfg"); configure_spi_agent(m_spi_cfg); m_spi_cfg.SPI = ovm_container #(virtual spi_if)::get_value_from_config(this, "SPIvif"); m_env_cfg.m_spi_agent_cfg = m_spi_cfg; // Now env config is complete set it into config space: set_config_object("*", "spi_env_config", m_env_cfg, 0); // Now we are ready to build the spi_env: m_env = spi_env::type_id::create("m_env", this); endfunction: build
11
Ovm/Testbench/Build
12
Examples
The build process is best illustrated by looking at some examples to illustrate how different types of component hierarchy are built up: A block level testbench containing an agent An integration level testbench
Ovm/Testbench/Blocklevel
13
Ovm/Testbench/Blocklevel
You are reading the OVM version of this article. You can find the new UVM version at [[]]
As an example of a block level test bench, we are going to consider a test bench builtto verifya SPIMaster DUT. In this case, the OVMenvironment has two agents - an APB agent to handle bus transfers on its APBslave port, and a SPIagent to handle SPIprotocol transfers on its SPIport. The structure of the overall OVMverification environment is as illustrated in the block diagram. We shallgo througheach layer of the test bench and describe how it is put together from the top down.
Block level ovm hierarchy.gif
Ovm/Testbench/Blocklevel
.PREADY(APB.PREADY), .PSLVERR(), .PWRITE(APB.PWRITE), // Interrupt output .IRQ(INTR.IRQ), // SPI signals .ss_pad_o(SPI.cs), .sclk_pad_o(SPI.clk), .mosi_pad_o(SPI.mosi), .miso_pad_i(SPI.miso) ); // OVM initial block: // Virtual interface wrapping & run_test() initial begin ovm_container #(virtual apb_if)::set_value_in_global_config("APB_vif" , APB); ovm_container #(virtual spi_if)::set_value_in_global_config("SPI_vif" , SPI); ovm_container #(virtual intr_if)::set_value_in_global_config("INTR_vif", INTR); run_test(); $finish; end // // Clock and reset initial block: // initial begin PCLK = 0; PRESETn = 0; repeat(8) begin #10ns PCLK = ~PCLK; end PRESETn = 1; forever begin #10ns PCLK = ~PCLK; end end endmodule: top_tb
14
The Test
The next phase in the OVMconstruction process is the build phase. For the SPIblock level example this means building the spi_env component, having first created and prepared all of the configuration objects that are going to be used by the environment. The configuration and build process is likely to be common to most test cases, so it is usually good practice to create a test base class that can be extended to create specific tests. In SPI example, the configuration object for the spi_env contains handles for the SPI and APB configuration objects, this allows the env configuration object to be used to pass all of the configuration objects to the env. The build method in the spi_env is then responsible for passing on these sub-configurations. This "RussianDoll" approach to nesting configurations is used since it is scalable for many levels of hierarchy. Before the configuration objects for the agents are assigned to their handles in the env configuration block, they are constructed, have their virtual interfaces assigned, using the ovm_container get_value_from_config() method, and then they are configured. The APBagentmay well be configured differently between test cases and so its configuration process has been split out into a separate virtual method in the base class, this allows inheriting test classes to overload this method and configure the APBagent differently. The following code is for the spi_test_base class:
`ifndef SPI_TEST_BASE `define SPI_TEST_BASE // // Class Description:
Ovm/Testbench/Blocklevel
// // class spi_test_base extends ovm_test; // OVM Factory Registration Macro // `ovm_component_utils(spi_test_base) //-----------------------------------------// Data Members //-----------------------------------------//-----------------------------------------// Component Members //-----------------------------------------// The environment class spi_env m_env; // Configuration objects spi_env_config m_env_cfg; apb_agent_config m_apb_cfg; spi_agent_config m_spi_cfg; // Register map spi_register_map spi_rm; //-----------------------------------------// Methods //-----------------------------------------extern virtual function void configure_apb_agent(apb_agent_config cfg); // Standard OVM Methods: extern function new(string name = "spi_test_base", ovm_component parent = null); extern function void build(); endclass: spi_test_base function spi_test_base::new(string name = "spi_test_base", ovm_component parent = null); super.new(name, parent); endfunction // Build the env, create the env configuration // including any sub configurations and assigning virtural interfaces function void spi_test_base::build(); m_env_cfg = spi_env_config::type_id::create("m_env_cfg"); // Register map - Keep reg_map a generic name for vertical reuse reasons spi_rm = new("reg_map", null); m_env_cfg.spi_rm = spi_rm; m_apb_cfg = apb_agent_config::type_id::create("m_apb_cfg"); configure_apb_agent(m_apb_cfg); m_apb_cfg.APB = ovm_container #(virtual apb_if)::get_value_from_config(this, "APB_vif"); m_env_cfg.m_apb_agent_cfg = m_apb_cfg; // The SPI is not configured as such m_spi_cfg = spi_agent_config::type_id::create("m_spi_cfg"); m_spi_cfg.SPI = ovm_container #(virtual spi_if)::get_value_from_config(this, "SPI_vif"); m_spi_cfg.has_functional_coverage = 0; m_env_cfg.m_spi_agent_cfg = m_spi_cfg; // Insert the interrupt virtual interface into the env_config: m_env_cfg.INTR = ovm_container #(virtual intr_if)::get_value_from_config(this, "INTR_vif"); set_config_object("*", "spi_env_config", m_env_cfg, 0); m_env = spi_env::type_id::create("m_env", this); // Override for register adapter: register_adapter_base::type_id::set_inst_override(apb_register_adapter::get_type(), "spi_bus.adapter"); endfunction: build
15
// // Convenience function to configure the apb agent // // This can be overloaded by extensions to this base class function void spi_test_base::configure_apb_agent(apb_agent_config cfg); cfg.active = OVM_ACTIVE; cfg.has_functional_coverage = 0; cfg.has_scoreboard = 0; // SPI is on select line 0 for address range 0-18h cfg.no_select_lines = 1; cfg.start_address[0] = 32'h0;
Ovm/Testbench/Blocklevel
cfg.range[0] = 32'h18; endfunction: configure_apb_agent `endif // SPI_TEST_BASE
16
To create a specific test case the spi_test_base class is extended, and this allows the test writer to take advantage of the configuration and build process defined in the parent class and means that he only needs to add a run method. In the following (simplistic and to be updated) example, the run method instantiates a virtual sequence and starts it on the virtual sequencer in the env. All of the configuration process is carried out by the super.build() method call in the build method.
`ifndef SPI_TEST `define SPI_TEST // // Class Description: // // class spi_test extends spi_test_base; // OVM Factory Registration Macro // `ovm_component_utils(spi_test) //-----------------------------------------// Methods //-----------------------------------------// Standard OVM Methods: extern function new(string name = "spi_test", ovm_component parent = null); extern function void build(); extern task run(); endclass: spi_test function spi_test::new(string name = "spi_test", ovm_component parent = null); super.new(name, parent); endfunction // Build the env, create the env configuration // including any sub configurations and assigning virtural interfaces function void spi_test::build(); super.build(); endfunction: build task spi_test::run; check_reset_seq reset_test_seq = check_reset_seq::type_id::create("rest_test_seq"); send_spi_char_seq spi_char_seq = send_spi_char_seq::type_id::create("spi_char_seq"); reset_test_seq.start(m_env.m_v_sqr.apb); spi_char_seq.start(m_env.m_v_sqr.apb); #100ns; global_stop_request(); endtask: run `endif // SPI_TEST
Ovm/Testbench/Blocklevel
17
The env
The next level in the SPIOVMenvironment is the spi_env. This class contains a number of sub-components, namely the SPI and APB agents, a scoreboard, a functional coverage monitor and a virtual sequencer. Which of thesesub-components gets built is determined by variables in the spi_env configuration object. In this case, the spi_env configuration object also contains a virtual interface and a method for detecting an interrupt. This will be used by sequences running on the virtual sequencer. The contents of the spi_env_config class are as follows:
`ifndef SPI_ENV_CONFIG `define SPI_ENV_CONFIG // // Class Description: // // class spi_env_config extends ovm_object; localparam string s_my_config_id = "spi_env_config"; localparam string s_no_config_id = "no config"; localparam string s_my_config_type_error_id = "config type error"; // OVM Factory Registration Macro // `ovm_object_utils(spi_env_config) // Interrupt Virtual Interface - used in the wait for interrupt task // virtual intr_if INTR; //-----------------------------------------// Data Members //-----------------------------------------// Whether env analysis components are used: bit has_functional_coverage = 0; bit has_spi_functional_coverage = 1; bit has_reg_scoreboard = 0; bit has_spi_scoreboard = 1; // Whether the various agents are used: bit has_apb_agent = 1; bit has_spi_agent = 1; // Whether the virtual sequencer is used: bit has_virtual_sequencer = 1; // Configurations for the sub_components apb_agent_config m_apb_agent_cfg; spi_agent_config m_spi_agent_cfg; // SPI Register model ovm_register_map spi_rm; //-----------------------------------------// Methods //-----------------------------------------extern static function spi_env_config get_config( ovm_component c); extern task wait_for_interrupt; extern function bit is_interrupt_cleared; // Standard OVM Methods: extern function new(string name = "spi_env_config"); endclass: spi_env_config function spi_env_config::new(string name = "spi_env_config"); super.new(name); endfunction // // Function: get_config // // This method gets the my_config associated with component c. We check for // the two kinds of error which may occur with this kind of
Ovm/Testbench/Blocklevel
// operation. // function spi_env_config spi_env_config::get_config( ovm_component c ); ovm_object o; spi_env_config t; if( !c.get_config_object( s_my_config_id , o , 0 ) ) begin c.ovm_report_error( s_no_config_id , $psprintf("no config associated with %s" , s_my_config_id ) , OVM_NONE , `ovm_file , `ovm_line ); return null; end if( !$cast( t , o ) ) begin c.ovm_report_error( s_my_config_type_error_id , $psprintf("config %s associated with config %s is not of type my_config" , o.sprint() , s_my_config_id ) , OVM_NONE , `ovm_file , `ovm_line ); end return t; endfunction // This task is a convenience method for sequences waiting for the interrupt // signal task spi_env_config::wait_for_interrupt; @(posedge INTR.IRQ); endtask: wait_for_interrupt // Check that interrupt has cleared: function bit spi_env_config::is_interrupt_cleared; if(INTR.IRQ == 0) return 1; else return 0; endfunction: is_interrupt_cleared `endif // SPI_ENV_CONFIG
18
In this example, there are build configuration field bits for each sub-component. This gives the env the ultimate flexibility for reuse. During the spi_envs build phase, a pointer to the spi_env_config is retrieved from the tests configuration table using get_config(). Then the build process tests the various has_<sub_component> fields in the configuration object to determine whether to build a sub-component. In the case of the APBand SPI agents, there is an additional step which is to unpack the configuration objects for each of the agents from the envs configuration object and then to set the agentconfiguration objects in the envs configuration table after any local modification. In the connect phase, the spi_env configuration object is again used to determine which TLMconnections to make andwhich pointers to assign to the sequencer handles in the virtual sequencer.
`ifndef SPI_ENV `define SPI_ENV // // Class Description: // // class spi_env extends ovm_env; // OVM Factory Registration Macro // `ovm_component_utils(spi_env) //-----------------------------------------// Data Members //-----------------------------------------apb_agent m_apb_agent;
Ovm/Testbench/Blocklevel
spi_agent m_spi_agent; spi_env_config m_cfg; spi_register_coverage m_reg_cov_monitor; spi_reg_functional_coverage m_func_cov_monitor; spi_virtual_sequencer m_v_sqr; spi_scoreboard m_scoreboard; //-----------------------------------------// Constraints //-----------------------------------------//-----------------------------------------// Methods //-----------------------------------------// Standard OVM extern function extern function extern function endclass:spi_env function spi_env::new(string name = "spi_env", ovm_component parent = null); super.new(name, parent); endfunction function void spi_env::build(); m_cfg = spi_env_config::get_config(this); if(m_cfg.has_apb_agent) begin set_config_object("m_apb_agent*", "apb_agent_config", m_cfg.m_apb_agent_cfg, 0); m_apb_agent = apb_agent::type_id::create("m_apb_agent", this); end if(m_cfg.has_spi_agent) begin set_config_object("m_spi_agent*", "spi_agent_config", m_cfg.m_spi_agent_cfg, 0); m_spi_agent = spi_agent::type_id::create("m_spi_agent", this); end if(m_cfg.has_virtual_sequencer) begin m_v_sqr = spi_virtual_sequencer::type_id::create("m_v_sqr", this); end if(m_cfg.has_functional_coverage) begin m_reg_cov_monitor = spi_register_coverage::type_id::create("m_reg_cov_monitor", this); end if(m_cfg.has_spi_functional_coverage) begin m_func_cov_monitor = spi_reg_functional_coverage::type_id::create("m_func_cov_monitor", this); end if(m_cfg.has_spi_scoreboard) begin m_scoreboard = spi_scoreboard::type_id::create("m_scoreboard", this); end endfunction:build function void spi_env::connect(); if(m_cfg.has_virtual_sequencer) begin if(m_cfg.has_spi_agent) begin m_v_sqr.spi = m_spi_agent.m_sequencer; end if(m_cfg.has_apb_agent) begin m_v_sqr.apb = m_apb_agent.m_sequencer; end end if(m_cfg.has_functional_coverage) begin m_apb_agent.ap.connect(m_reg_cov_monitor.analysis_export); end if(m_cfg.has_spi_functional_coverage) begin m_apb_agent.ap.connect(m_func_cov_monitor.analysis_export); end if(m_cfg.has_spi_scoreboard) begin m_apb_agent.ap.connect(m_scoreboard.apb.analysis_export); m_spi_agent.ap.connect(m_scoreboard.spi.analysis_export); m_scoreboard.spi_rm = m_cfg.spi_rm; end endfunction: connect `endif // SPI_ENV Methods: new(string name = "spi_env", ovm_component parent = null); void build(); void connect();
19
Ovm/Testbench/Blocklevel
20
The Agents
Since the OVMbuild process is top down, theSPI and APBagents are constructed next. The article onthe agent build process describes how the APBagent is configured and built, the SPI agentfollows the same process. The components within the agents are at the bottom of the test bench hierarchy, so the build process terminates there. ( for source code example download visit us online at http://uvm.mentor.com ).
Ovm/Testbench/IntegrationLevel
21
Ovm/Testbench/IntegrationLevel
You are reading the OVM version of this article. You can find the new UVM version at [[]]
This test bench example is one that takes two block level verification environments and shows how they can be reused at a higher level of integration. The principles that are illustrated in the example are applicable to repeated rounds of vertical resue. The example takes the SPI block level example and integrates it with another block level verification environment fora GPIO DUT. The hardware for the two blocks has been integrated into a Peripheral Sub-System (PSS) which uses an AHB to APB bus bridge to interface with the APBinterfaces on the SPI and GPIO blocks. The environments from the block level are encapsulated by the pss_env, which also includes an AHB agent to drive the exposed AHB bus interface. In this configuration, the block level APB bus interfaces are no longer exposed, and so the APB agents are put into passive mode to monitor the APB traffic. The stimulus needs to drive the AHB interface and register layering enables reuse of block level stimulus at the integration level. We shall now go through the test bench and the build process from the top down, starting with the top level test benchmodule.
Ovm/Testbench/IntegrationLevel
// logic HCLK; logic HRESETn; // // Instantiate the interfaces: // apb_if APB(HCLK, HRESETn); // APB interface - shared between passive agents ahb_if AHB(HCLK, HRESETn); // AHB interface spi_if SPI(); // SPI Interface intr_if INTR(); // Interrupt gpio_if GPO(); gpio_if GPI(); gpio_if GPOE(); icpit_if ICPIT(); serial_if UART_RX(); serial_if UART_TX(); modem_if MODEM(); // Binder binder probe(); // DUT Wrapper: pss_wrapper wrapper(.ahb(AHB), .spi(SPI), .gpi(GPI), .gpo(GPO), .gpoe(GPOE), .icpit(ICPIT), .uart_rx(UART_RX), .uart_tx(UART_TX), .modem(MODEM)); // OVM initial block: // Virtual interface wrapping & run_test() initial begin ovm_container #(virtual apb_if)::set_value_in_global_config("APB_vif" , APB); ovm_container #(virtual ahb_if)::set_value_in_global_config("AHB_vif" , AHB); ovm_container #(virtual spi_if)::set_value_in_global_config("SPI_vif" , SPI); ovm_container #(virtual intr_if)::set_value_in_global_config("INTR_vif", INTR); ovm_container #(virtual gpio_if)::set_value_in_global_config("GPO_vif" , GPO); ovm_container #(virtual gpio_if)::set_value_in_global_config("GPOE_vif" , GPOE); ovm_container #(virtual gpio_if)::set_value_in_global_config("GPI_vif" , GPI); ovm_container #(virtual icpit_if)::set_value_in_global_config("ICPIT_vif" , ICPIT); ovm_container #(virtual serial_if)::set_value_in_global_config("UART_RX_vif" , UART_RX); ovm_container #(virtual serial_if)::set_value_in_global_config("UART_TX_vif" , UART_TX); ovm_container #(virtual modem_if)::set_value_in_global_config("MODEM_vif" , MODEM); run_test(); end // // Clock and reset initial block: // initial begin HCLK = 0; HRESETn = 0; repeat(8) begin #10ns HCLK = ~HCLK; end HRESETn = 1; forever begin #10ns HCLK = ~HCLK; end end // Clock assignments: assign GPO.clk = HCLK; assign GPOE.clk = HCLK; assign GPI.clk = HCLK; assign ICPIT.PCLK = HCLK; endmodule: top_tb
22
Ovm/Testbench/IntegrationLevel
23
The Test
Like the block level test, the integration level test should havethe commonbuild and configuration process captured in a base class that subsequent test cases can inherit from. As can be seen from the example, there is more configuration to do and so the need becomes more compelling. The configuration object for the pss_env contains handles for the configuration objects for the spi_env and the gpio_env. In turn, the sub-env configuration objects contain handles for their agent sub-component configuration objects. The pss_env is responsible for unnesting the spi_env and gpio_env configuration objects and setting them in its configuration table, making any local changes necessary. In turn the spi_env and the gpio_env put their agent configurations into their configuration table. The pss test base class is as follows:
`ifndef PSS_TEST_BASE `define PSS_TEST_BASE // // Class Description: // // class pss_test_base extends ovm_test; // OVM Factory Registration Macro // `ovm_component_utils(pss_test_base) //-----------------------------------------// Data Members //-----------------------------------------//-----------------------------------------// Component Members //-----------------------------------------// The environment class pss_env m_env; // Configuration objects pss_env_config m_env_cfg; spi_env_config m_spi_env_cfg; gpio_env_config m_gpio_env_cfg; //uart_env_config m_uart_env_cfg; apb_agent_config m_spi_apb_agent_cfg; apb_agent_config m_gpio_apb_agent_cfg; ahb_agent_config m_ahb_agent_cfg; spi_agent_config m_spi_agent_cfg; gpio_agent_config m_GPO_agent_cfg; gpio_agent_config m_GPI_agent_cfg; gpio_agent_config m_GPOE_agent_cfg; // Register map pss_register_map pss_rm;
//-----------------------------------------// Methods //-----------------------------------------//extern function void configure_apb_agent(apb_agent_config cfg); // Standard OVM Methods: extern function new(string name = "spi_test_base", ovm_component parent = null); extern function void build(); extern virtual function void configure_apb_agent(apb_agent_config cfg, int index, logic[31:0] start_address, logic[31:0] range); extern task run(); endclass: pss_test_base function pss_test_base::new(string name = "spi_test_base", ovm_component parent = null); super.new(name, parent); endfunction
Ovm/Testbench/IntegrationLevel
24
// Build the env, create the env configuration // including any sub configurations and assigning virtural interfaces function void pss_test_base::build(); m_env_cfg = pss_env_config::type_id::create("m_env_cfg"); // Register map - Keep reg_map a generic name for vertical reuse reasons pss_rm = new("reg_map", null); m_env_cfg.pss_rm = pss_rm; // SPI Sub-env configuration: m_spi_env_cfg = spi_env_config::type_id::create("m_spi_env_cfg"); m_spi_env_cfg.spi_rm = pss_rm; // apb agent in the SPI env: m_spi_env_cfg.has_apb_agent = 1; m_spi_apb_agent_cfg = apb_agent_config::type_id::create("m_spi_apb_agent_cfg"); configure_apb_agent(m_spi_apb_agent_cfg, 0, 32'h0, 32'h18); m_spi_apb_agent_cfg.APB = ovm_container #(virtual apb_if)::get_value_from_config(this, "APB_vif"); m_spi_env_cfg.m_apb_agent_cfg = m_spi_apb_agent_cfg; // SPI agent: m_spi_agent_cfg = spi_agent_config::type_id::create("m_spi_agent_cfg"); m_spi_agent_cfg.SPI = ovm_container #(virtual spi_if)::get_value_from_config(this, "SPI_vif"); m_spi_env_cfg.m_spi_agent_cfg = m_spi_agent_cfg; m_env_cfg.m_spi_env_cfg = m_spi_env_cfg; set_config_object("*", "spi_env_config", m_spi_env_cfg, 0); // GPIO env configuration: m_gpio_env_cfg = gpio_env_config::type_id::create("m_gpio_env_cfg"); m_gpio_env_cfg.gpio_rm = pss_rm; m_gpio_env_cfg.has_apb_agent = 1; // APB agent used m_gpio_apb_agent_cfg = apb_agent_config::type_id::create("m_gpio_apb_agent_cfg"); configure_apb_agent(m_gpio_apb_agent_cfg, 1, 32'h100, 32'h124); m_gpio_apb_agent_cfg.APB = ovm_container #(virtual apb_if)::get_value_from_config(this, "APB_vif"); m_gpio_env_cfg.m_apb_agent_cfg = m_gpio_apb_agent_cfg; m_gpio_env_cfg.has_functional_coverage = 1; // Register coverage no longer valid // GPO agent m_GPO_agent_cfg = gpio_agent_config::type_id::create("m_GPO_agent_cfg"); m_GPO_agent_cfg.GPIO = ovm_container #(virtual gpio_if)::get_value_from_config(this, "GPO_vif"); m_GPO_agent_cfg.active = OVM_PASSIVE; // Only monitors m_gpio_env_cfg.m_GPO_agent_cfg = m_GPO_agent_cfg; // GPOE agent m_GPOE_agent_cfg = gpio_agent_config::type_id::create("m_GPOE_agent_cfg"); m_GPOE_agent_cfg.GPIO = ovm_container #(virtual gpio_if)::get_value_from_config(this, "GPOE_vif"); m_GPOE_agent_cfg.active = OVM_PASSIVE; // Only monitors m_gpio_env_cfg.m_GPOE_agent_cfg = m_GPOE_agent_cfg; // GPI agent - active (default) m_GPI_agent_cfg = gpio_agent_config::type_id::create("m_GPI_agent_cfg"); m_GPI_agent_cfg.GPIO = ovm_container #(virtual gpio_if)::get_value_from_config(this, "GPI_vif"); m_gpio_env_cfg.m_GPI_agent_cfg = m_GPI_agent_cfg; // GPIO Aux agent not present m_gpio_env_cfg.has_AUX_agent = 0; m_gpio_env_cfg.has_functional_coverage = 1; m_gpio_env_cfg.has_virtual_sequencer = 0; m_gpio_env_cfg.has_reg_scoreboard = 0; m_gpio_env_cfg.has_out_scoreboard = 1; m_gpio_env_cfg.has_in_scoreboard = 1; m_env_cfg.m_gpio_env_cfg = m_gpio_env_cfg; set_config_object("*", "gpio_env_config", m_gpio_env_cfg, 0); // AHB Agent m_ahb_agent_cfg = ahb_agent_config::type_id::create("m_ahb_agent_cfg"); m_ahb_agent_cfg.AHB = ovm_container #(virtual ahb_if)::get_value_from_config(this, "AHB_vif"); m_env_cfg.m_ahb_agent_cfg = m_ahb_agent_cfg; // Add in interrupt line m_env_cfg.ICPIT = ovm_container #(virtual icpit_if)::get_value_from_config(this, "ICPIT_vif"); set_config_object("*", "pss_env_config", m_env_cfg, 0); m_env = pss_env::type_id::create("m_env", this); // Override for register adapters: register_adapter_base::type_id::set_inst_override(ahb_register_adapter::get_type(), "spi_bus.adapter"); register_adapter_base::type_id::set_inst_override(ahb_register_adapter::get_type(), "gpio_bus.adapter"); endfunction: build // // Convenience function to configure the apb agent // // This can be overloaded by extensions to this base class function void pss_test_base::configure_apb_agent(apb_agent_config cfg, int index, logic[31:0] start_address, logic[31:0] range); cfg.active = OVM_PASSIVE;
Ovm/Testbench/IntegrationLevel
cfg.has_functional_coverage = 0; cfg.has_scoreboard = 0; cfg.no_select_lines = 1; cfg.apb_index = index; cfg.start_address[0] = start_address; cfg.range[0] = range; endfunction: configure_apb_agent
25
`endif // SPI_TEST_BASE
Again, a test case that extends this base class would populate its run method to define a virtual sequence that would be run on the virtual sequencer in the env.. If there was non-default configuration to be done, then this could be done by populating or overloading the build method or any of the configuration methods.
`ifndef PSS_TEST `define PSS_TEST // // Class Description: // // class pss_test extends pss_test_base; // OVM Factory Registration Macro // `ovm_component_utils(pss_test) //-----------------------------------------// Methods //-----------------------------------------// Standard OVM Methods: extern function new(string name = "pss_test", ovm_component parent = null); extern function void build(); extern task run(); endclass: pss_test function pss_test::new(string name = "pss_test", ovm_component parent = null); super.new(name, parent); endfunction // Build the env, create the env configuration // including any sub configurations and assigning virtural interfaces function void pss_test::build(); super.build(); endfunction: build task pss_test::run; bridge_basic_rw_vseq t_seq = bridge_basic_rw_vseq::type_id::create("t_seq"); $display("Starting PSS test"); repeat(10) begin t_seq.start(m_env.m_vsqr.ahb); end global_stop_request(); endtask: run `endif // PSS_TEST
Ovm/Testbench/IntegrationLevel
26
Ovm/Testbench/IntegrationLevel
if(m_cfg.has_virtual_sequencer) begin if(m_cfg.has_spi_env) begin m_vsqr.spi = m_spi_env.m_spi_agent.m_sequencer; end if(m_cfg.has_gpio_env) begin m_vsqr.gpi = m_gpio_env.m_GPI_agent.m_sequencer; end if(m_cfg.has_ahb_agent) begin m_vsqr.ahb = m_ahb_agent.m_sequencer; end end endfunction: connect `endif // PSS_ENV
27
Ovm/Agent
28
Ovm/Agent
You are reading the OVM version of this article. You can find the new UVM version at [[]]
An OVMagent can be thought of as a verification component kit for aspecific logical interface. The agent is developed as package that includes a SystemVerilog interface for connecting to the signal pins of a DUT, and a SystemVerilog package that includes the classes that make up the overall agent component. The agent class itself is a top level container class for a driver, a sequencer and a monitor, plus any other verification components such as functional coverage monitors or scoreboards. The agent also has an analysis port which is connected to the analysis port on the monitor,making it possiblefor a user to connect external analysis Active agent ovm.gif components to the agent without having to know how the agent has been implemented.The agent is the lowest level hierarchical block ina testbench and its exactstructure is dependent on its configuration which can be varied from one test to another via the agent configuration object. In order to illustrate the design and implementation of an agent we shall take an APBbus agent and show how it is packaged, configured, built and connected. The APBagentusesan interface which is called apb_if and will be stored in a file called apb_if.sv. The various class template files for the agent are collected together in a SystemVerilog package which is saved in a file called apb_agent_pkg.sv. Any component, such as an env, that uses files from this package will import this package.
package apb_agent_pkg; import ovm_pkg::*; `include "ovm_macros.svh" `include `include `include `include `include `include `include "apb_seq_item.svh" "apb_agent_config.svh" "apb_driver.svh" "apb_coverage_monitor.svh" "apb_monitor.svh" "apb_sequencer.svh" "apb_agent.svh"
Ovm/Agent
29
Ovm/Agent
// Methods //-----------------------------------------extern static function apb_agent_config get_config( ovm_component c ); // Standard OVM Methods: extern function new(string name = "apb_agent_config"); endclass: apb_agent_config function apb_agent_config::new(string name = "apb_agent_config"); super.new(name); endfunction // // Function: get_config // // This method gets the my_config associated with component c. We check for // the two kinds of error which may occur with this kind of // operation. // function apb_agent_config apb_agent_config::get_config( ovm_component c ); ovm_object o; apb_agent_config t; if( !c.get_config_object( s_my_config_id , o , 0 ) ) begin c.ovm_report_error( s_no_config_id , $sformatf("no config associated with %s" , s_my_config_id ) , OVM_NONE , `ovm_file , `ovm_line ); return null; end if( !$cast( t , o ) ) begin c.ovm_report_error( s_my_config_type_error_id , $sformatf("config %s associated with config %s is not of type my_config" , o.sprint() , s_my_config_id ) , OVM_NONE , `ovm_file , `ovm_line ); end return t; endfunction `endif // APB_AGENT_CONFIG
30
Ovm/Agent
31
Ovm/Agent
apb_monitor m_monitor; apb_sequencer m_sequencer; apb_driver m_driver; apb_coverage_monitor m_fcov_monitor; //-----------------------------------------// Methods //-----------------------------------------// Standard OVM extern function extern function extern function Methods: new(string name = "apb_agent", ovm_component parent = null); void build(); void connect();
32
endclass: apb_agent function apb_agent::new(string name = "apb_agent", ovm_component parent = null); super.new(name, parent); endfunction function void apb_agent::build(); m_cfg = apb_agent_config::get_config(this); // Monitor is always present m_monitor = apb_monitor::type_id::create("m_monitor", this); // Only build the driver and sequencer if active if(m_cfg.active == OVM_ACTIVE) begin m_driver = apb_driver::type_id::create("m_driver", this); m_sequencer = apb_sequencer::type_id::create("m_sequencer", this); end if(m_cfg.has_functional_coverage) begin m_fcov_monitor = apb_coverage_monitor::type_id::create("m_fcov_monitor", this); end endfunction: build function void apb_agent::connect(); m_monitor.APB = m_cfg.APB; ap = m_monitor.ap; // Only connect the driver and the sequencer if active if(m_cfg.active == OVM_ACTIVE) begin m_driver.seq_item_port.connect(m_sequencer.seq_item_export); m_driver.APB = m_cfg.APB; end if(m_cfg.has_functional_coverage) begin m_monitor.ap.connect(m_fcov_monitor.analysis_export); end endfunction: connect `endif // APB_AGENT
The build process for the APB agent can be followed in the block level testbench example: ( for source code example download visit us online at http://uvm.mentor.com ).
OVM Phases
33
OVM Phases
You are reading the OVM version of this article. You can find the new UVM version at [[]]
In order to have a consistent execution flow, the OVM uses phases which are ordered to allow the testbench component objects to stay in step as the testbench isbuilt and configured and connected. Once the testbench hierarchy is available the simulation run phase is executed, after which the report phases occur. The defined phases allow OVM verification components developed by different teams to be mixed freely, since it is clear what happens in each phase. The different OVM phases are illustrated in order in the flow diagram. The ovm_component base class contains virtual methods for each of the phases which arepopulated by the testbench creator according to the requirements of the component. run_test() -> new To start an OVM testbench the run_test() method has to be called from the static part of the testbench. It is usually called from within an initial block in the top level module of the testbench. Calling run_test() initiates the OVM phasing by constructing the root component of the OVM class structure. The method can be passed a string argument containing the default type name of an ovm_component which will be constructed as the root node of the OVMtestbench.However, run_test()checks a command line plusarg Ovm phases.gif called OVM_TESTNAME and uses that plusarg string as a lookup for an ovm_component if there is one present, overriding the default type name. By convention, the root node will be derived from an ovm_test component, but it can be derived from any ovm_component. For instance - In order to signify the test component 'my_big_test' as the root class the Questa command line would look like this: vsim tb_top +OVM_TESTNAME=my_big_test
OVM Phases build In an OVM testbench only thetest or root nodecomponent is constructed directly using the new method. After that, the rest of the testbench hierarchy is built top-down during the build phase. Construction is deferred so that the structure and configuration of each level of the component hierarchy can be controlled by the level above. During the build method components are indirectly constructed through a factory based creation process. connect Once the testbench component hierarchy has been put in place during the build method, the connect phase begins. The connect phase works from the bottom up and is used to make TLM connections between components or to make references to testbench resources. end_of_elaboration This phase can be used to make any final enhancements to the environment after it has been built and its inter-component connections made. start_of_simulation The start_of_simulation phase occurs just before the run phase. It may be a convenient point in the OVM phases to print banner information or testbench configuration status information. run - Terminated by global_stop_request The run method is where the stimulus generation and checking activities of the testbench are defined. Unlike the other OVM phases, the run method is a task which means that it consumes time. All components that are actively participating in the testbench will have a populated run method and all of these run methods are run in parallel. The run phase is terminated by a call to the global_stop_request() method. This shuts down all the executing threads and terminates the run phase. The call to global_stop_request() can either be made explicitly by the user, or if the OVMobjection mechanism is being used it will be called automatically when all objections have been lowered. extract The extract phase is intended to be used to extract test results and statistics together with functional coverage information from different components in the testbench such as scoreboards and functional coverage monitors. check During the check phase, data collected during the previous extract phase is checked and the overall result of the testbench is calculated. report The report phase is the final phase and is used to report the results of the test case, this is either via messages to the simulator transcript or by writing to files. When the report phase has completed, the OVM testbench terminates and, by default, makes a $finish system call.
34
OVM Phases
35
end_of_elaboration Bottom-Up function start_of_simulation Bottom-Up function run extract check report Bottom-Up task Bottom-Up function Bottom-Up function Bottom-Up function
Deprecated Phases
There are a number of phases which are a legacy from the AVM, URMand earlier versions of the OVM. These phases are still supported in the current version of the OVM, but may disapear in future versions and will not be supported in the UVM. Users should avoid using the deprecated phases, and they should consider porting any verification components using them to align with the supported set of OVMphases. The deprecated phases and their recommended replacement phases are shown in the following table:
Deprecated Phase post_new export_connections import_connections pre_run configure Replacement Phase build connect connect start_of_simulation end_of_elaboration
OVM Factory
36
OVM Factory
You are reading the OVM version of this article. You can find the new UVM version at [[]]
The OVMFactory
The purpose of the OVMfactory is to allow an object of one type to be substituted with an object of a derived type without having to change thestructure of the testbench or edit the testbench code. Themechanism used is refered to as an override and the overridecan be by instance or type. This functionality is useful for changing sequence functionality or for changing one version of a component for another. Any components which are to be swapped must be polymorphically compatible. This includes having all the same TLM interfaces handles exist and TLM objects be created by the new replacement component. Additionally, in order to take advantage of the factory certain coding conventions need to be followed.
The registration code has a regular pattern and can be safely generated with one of a set of four factory registration macros:
// For a component class my_component extends ovm_component; // Component factory registration macro `ovm_component_utils(my_component) // For a parameterised component class my_param_component #(int ADD_WIDTH=20, int DATA_WIDTH=23) extends ovm_component; typedef my_param_component #(ADD_WIDTH, DATA_WIDTH) this_t; // Parameterised component factory registration macro
OVM Factory
`ovm_component_param_utils(this_t) // For a class derived from an object (ovm_object, ovm_transaction, ovm_sequence_item, ovm_sequence etc) class my_item extends ovm_sequence_item; `ovm_oject_utils(my_item) // For a parameterised object class class my_item #(int ADD_WIDTH=20, int DATA_WIDHT=20) extends ovm_sequence_item; typedef my_item #(ADD_WIDTH, DATA_WIDTH) this_t `ovm_object_param_utils(this_t)
37
OVM Factory
// Object and parameterised ojbect create examples test_seq = my_seq::type_id::create("test_seq"); p_test_seq = my_param_seq #(32,32)::type_id::create("p_test_seq"); // .... endtask: run
38
w w w.m e n t o r.c o m
2010 Mentor Graphics Corporation, all rights reserved. This document contains information that is proprietary to Mentor Graphics Corporation and may be duplicated in whole or in part by the original recipient for internal business purposes only, provided that this entire notice appears in all copies. In accepting this document, the recipient agrees to make every reasonable effort to prevent unauthorized use of this information. All trademarks mentioned are this document are trademarks of their respective owners.
MGC 06-10
TECH9050-w