Skip to Content

Offsite blog roll

Posted on  by  from the site Verification Martial Arts
Amit Sharma, Synopsys Typically, fields and registers are assumed to be implemented in individual, dedicated hardware structures with a constant and permanent physical location such as a set of D flip-flops. However for some registers which are typically large in number, implementing them in  memory or RAM instead of individual flip-flops is most efficient.  These are the virtual fields and virtual registers, and as they are implemented in a RAM, their physical location and layout is created by an agreement between the hardware and the software, not by their physical implementation. Virtual fields and registers can be modeled using RAL by creating a logical overlay on a RAL memory model that can be accessed as if they were real physical fields and registers. Virtual fields are contained in virtual registers.   Virtual registers are defined as continuous locations within a memory and can span across multiple memory locations. They are always composed of entire memory locations and not fractions and are modeled arrays associated with a memory. The association of a virtual register array with a memory can be static or dynamic, but the structure of these registers should be specified in the RALF file. ******************************************************************************** Static virtual registers are associated with a specific memory and are located at specific offsets within this memory. This association should be specified in the RALF file as shown in the following example. This association is permanent and cannot be broken at runtime. Static Virtual Register Array: Example 1 :             memory ram1 {               size 32; bits 8; access rw; initial 0;               }             block dut {                bytes 2;                memory ram1@0×0000                virtual register vreg[3] ram1 @0×5 +2 {                    bytes 2;                    field f1 { bits 4; };                    field f2 { bits 4; };                    field f3 { bits 8; };                }             }         An array of new virtual registers with array size as 3 is associated with the memory ‘ram1′ starting at the offset 5. The increment value is specified as 2 as minimum 2 locations in the memory are required to implement 1 virtual register. The 3 virtual registers are associated with the ram1 as shown:     vreg[0] is associated at offset 0×5 of ram1.     vreg[1] is associated at offset 0×7(0×5 +2) of ram1.     vreg[2] is associated at offset 0×9(0×7 +2) of ram1.     vreg2[2] is associated at offset 0×24(0×22 +2) of ram1. ******************************************************************************** Dynamic virtual registers are associated with a user-specified memory and are located at user-specified offsets within that memory. The association is done at runtime. The dynamic allocation of virtual register arrays can also be performed randomly by a Memory Allocation Manager instance. The number of virtual registers in the array and its association with the memory is specified in the SystemVerilog code and must be correctly implemented by the user. Dynamic virtual registers arrays can be relocated or resized at runtime. Dynamic Virtual Register Array: Example 1:             memory ram1 {               size 64; bits 8; access rw; initial 0;              }             virtual register vreg {              bytes 2;              field f1 { bits 4; };              field f2 { bits 4; };              field f3 { bits 8; };             }             block dut {              bytes 2;              memory ram1@0×0000              virtual register vreg=vreg1;              virtual register vreg=vreg2;                        }            Here, the virtual register structure is specified in the RALF file, but the association is done during runtime (in the env or testcase) in one of the following ways: a. Dynamic specification:    env.ral_model.vreg1.implement(4,env.ral_model.ram1,’h20,2);    Virtual register array of size 4 is implemented starting at the offset ‘h20 of the memory ram1 and ‘2′ is the increment value. The virtual registers will be associated as:     vreg1[0] is associated at offset 0×20 of ram1.     vreg1[1] is associated at offset 0×22(0×20 +2) of ram1.     vreg1[2] is associated at offset 0×24(0×22 +2) of ram1.     vreg1[3] is associated at offset 0×26(0×24 +2) of ram1. b. Randomly implemented dynamic specification:      env.ral_model.dut.vreg2.allocate(5,env.ral_model.dut.ram1.mam);    Virtual register array of size 5 is allocated randomly by the memory allocation manager (MAM). The allocated region is randomly selected in the   address space managed by the specified MAM. ******************************************************************************** Hope this post would help you to model and verify the registers and fields implemented in the DUT more efficiently. Please look out for the next blog post where I will talk about the different modes through which you can access these registers and fields ...
Posted on  by  from the site Verification Martial Arts
Puja Sethia, ASIC Verification Technical Lead, eInfochips With the increase in Internet usage, consumer appetite for high-bandwidth content such as streaming video and peer-to-peer file sharing continues to grow. The quality-of-service and throughput requirements of such content bring concerns about network congestion. WRED (Weighted Random Early Detection) is one of the network congestion avoidance mechanisms. WRED Verification challenges span the injection of transactions to the creation of various test scenarios, to the prediction and checking of WRED results, to the end of report generation etc. To address this complexity, it is important to choose the right technique and methodology when architecting the verification environment. WRED Stimulus Generation – Targeting real network traffic scenarios There are two major WRED stimulus generation requirements: 1. Normal Traffic Generation – Interleaved for different traffic queues (class) and targeting regions below minimum threshold for the corresponding queue. 2. Congested Traffic Generation – Interleaved for different traffic queues and targeting regions below mininum threshold, above maximum threshold and range between minimum and maximum threshold for the corresponding queue. As shown in the diagram above, WRED stimulus generation requirements can be implemented in three major steps: 1. Identify test requirements such as the traffic patterns for different regions 2. Identify packet requirements for each interface to generate the required test scenario 3. Generate packets as per the provided constraints and pass it on to transcators This demarcation helps in ensuring that the flow is intuitive, concise and flexible while planning the stimulus generation strategy. To achieve this hierarchical and stacked requirement, we used VMM scenarios. VMM Single Stream scenarios which creates randomized list of transactions based on constraints can be directly mapped to the 3rd step. For the first two steps, we need the capability to drive, control and access more than one channel and we also need to create a hierarchy of scenarios. The VMM multi-stream scenarios provide the capability to control and access more than one channel. Higher level multi-stream scenarios can also instantiate other multi-stream scenarios. Separate multi-stream scenarios were thus defined to generate different traffic patterns which are used in the test multi-stream scenario to interleave all different types of traffic. As the requirement is to interleave different types of traffic coming from multiple multi-stream scenarios there are multiple sources of transactions and single destination. The VMM scheduler helps to funnel transactions from multiple sources into one output channel. It uses a user configurable scheduling algorithm to select and place an input transaction on the output channel. The snippet above shows how traffic from CLASS_0 and CLASS_1 is easily interleaved with each of them targeting a different region. Predicting, Debugging and Checking WRED Results using VMM with-loss scoreboard It is hard to predict if and what packets that might be lost and this makes it difficult to debug or verify the WRED results. The VMM DataStream Scoreboard helps in addressing this. The “expect_with_losses” option, when leveraged with a mechanism based on the configured probability, can help to overcome the challenge of predicting and verifying detailed WRED results. As shown in the above diagram, the DS scoreboard identifies which packets are lost if they do not appear in the output stream and posts the lost packets into a separate queue. At the same time, it verifies the correctness of all the others packets received at the output. It generates the different statistics based on packets matched and lost and this information can be utilized to represent the actual WRED results. By linking these results to the stimulus generator, the actual WRED results can be categorized based on the pass/lost packet count for each region. This actual WRED results represented in terms of lost and pass packet count for each region can than be compared with the predicted lost and pass packet count calculated based on the configured drop probability for each region to know pass/fail result. For more information on the WRED verification challenges and usage of VMM scenarios and VMM DS Scoreboarding techniques to overcome WRED verification challenges, refer to paper on “Verification of Network Congestion Avoidance Algorithm like WRED using VMM” on https://www.synopsys.com/news/pubs/snug/india2010/TA2.3_%20Sethia_Paper.pdf. ...
Posted on  by  from the site Verification Martial Arts
S. Prashanth, Verification & Design Engineer, LSI Logic To accommodate changing specifications and to support different clusters/subsystems which would have multiple processors/memory connected through a bridge), I am building a reusable environment which can support any number of masters and slaves of any standard bus protocols.  The environment requires high level of configurability since it should work for different DUTs. So, I decided to use vmm_opts not just to set the switches globally/hierarchically, but also to specify the ranges (like setting address ranges in scenarios) from the test cases/command line. I created a list of controls/switches that need to be provided as global options (like simulation timeout, scoreboard/coverage enable, etc)  and hierarchical options  to control instance specific behaviors (like number of transactions to be generated ,  burst enables, transaction speed, address ranges to be generated by a specific instance, set of instances, etc).  Let’s see how these options can be used and what all things are required for it through examples. Global Options Step 1: Declare an integer, say simulation_timeout in the environment class or wherever it is required and use vmm_opts::get_int(..) method as shown below. Specify a default value as well just in case, if the variable is not set anywhere. simulation_timeout = vmm_opts::get_int(“TIMEOUT”, 10000); Step 2: Then either at the test case or/and at the command line, I can override the default value using vmm_opts::set_int  or +vmm_opts+ runtime option. Override from the test case. vmm_opts::set_int(“%*:TIMEOUT”, 50000); Override from the command line. ./simv +vmm_opts+TIMEOUT=80000 Options can also be overridden from a command file, if it is difficult to specify all options in the command line. Also, options can be a string or Boolean as well. Hierarchical Options In order to use hierarchical options, I am building the environment with parent/child hierarchy which is a very useful feature of vmm_object. This is required since the hierarchy specified externally (from test case/command line) will be used to map the hierarchy in the environment. Step 1: Declare options, say burst_enable and num_of_trans in a subenv class and use vmm_opts::get_object_bit(..) and vmm_opts::get_object_int(..) to retrieve appropriate values passing current hierarchy and default values as arguments. Also, for setting ranges, declare min_addr and max_addr, and use get_object_range(). class master_subenv extends vmm_subenv; bit burst_enable; int num_of_trans; int min_addr; int max_addr; function void configure(); bit is_set; //to determine if the default value is overridden burst_enable = vmm_opts::get_object_bit(is_set, this, “BURST_ENABLE”); num_of_trans = vmm_opts::get_object_int(is_set, this, “NUM_TRANS”, 100); vmm_opts::get_object_range(is_set, this, “ADDR_RANGE”, 0, 32’hFFFF_FFFF); …. endfunction endclass Step 2: Build the environment with parent/child hierarchy either by passing the parent handle through the constructor to every child, or using vmm_object::set_parent_object() method. This is easy as almost all base classes are extended from vmm_object by default. class  dut_env extends vmm_env; virtual function build(); ….. mst0 = new(“MST0”, ….); mst0.set_parent_object(this); mst1 = new(“MST1”, …); mst1.set_parent_object(this); …. endfunction endclass Step 3: From the test case, I can override the default value using vmm_opts::set_bit or vmm_opts::set_int(..) specifying the hierarchy. I can use pattern matching as well to avoid specifying to every instance vmm_opts::set_int(“%*:MST0:NUM_TRANS”, 50); vmm_opts::set_int(“%*:MST1:NUM_TRANS”, 100); vmm_opts::set_bit(“%*:burst_enable); vmm_opts::set_range(“%*:MST1:ADDR_RANGE”, 32’h1000_0000, 32’h1000_FFFF); I can also override the default value from the command line as well. ./simv +vmm_opts+NUM_TRANS=50@%*:MST0+NUM_TRANS=100@%*:MST1+burst_enable@%* In summary, vmm_opts provides a powerful and user friendly way of configuring the environment from the command line or the test case. User can provide explanation of each of the options while calling get_object_*() and get_*() methods which will be displayed when vmm_opts::get_help() is called. ...
Posted on  by  from the site Verification Martial Arts
As microprocessor designs have grown considerably in complexity, generating microcode stimuli has become increasingly challenging.  An article by AMD and Synopsys engineers in EE Times explores using a hierarchical constrained-random approach to accelerate generation and reduce memory consumption, while providing optimal distribution and biasing to hit corner cases using the Synopsys VCS constraint solver. You can find the full article in PDF here. ...
Posted on  by  from the site Verification Martial Arts
As I am sure most of you already know, Accellera has been working on a new industry-standard verification methodology. That new standard, the Universal Verification Methodology, is still a work in progress: there are many features that need to be defined and implemented before its first official release, such as a register abstraction package and OSCI-style TLM2 interfaces. However, an Early Adopter release is now available if you wish to start using the UVM today. The UVM distribution kit contains a User Guide, a Reference Manual and an open source reference implementation of the work completed to date. It is supported by VCS (you will need version 2009.12-3 or later or version 2010.06 or later). Synopsys is fully committed to supporting UVM. As such, a version of the UVM library, augmented to support additional features provided by VCS and DVE, will be included in the VCS distribution, starting with the 2009.12-7 and 2010.06-1 patch releases. It will have the same simple use model as VMM: just specify the “-ntb_opts uvm” command line argument and voila: the UVM library will be automatically compiled, ready to be used. But if you can’t wait for these patch releases, you can download the Early Adopter kit from the Accellera website and use it immediately. What about VMM? Synopsys continues to be fully committed to VMM! All of the powerful applications that continue to make you more productive will be available on top of the UVM base class and methodology. A UVM/VMM interoperability kit is available from Synopsys (and will be included in the mentioned VCS patch releases) that will make it easy to integrate UVM verification IP into a VMM testbench (or vice-versa). Contact your VMM support AC/CAE to obtain the UVM/VMM interoperability package. What about support? If you need help adopting UVM with VCS, our ACs will be more than happy to help you: they have many years of expertise supporting customers using advanced verification methodologies. UVM is an Accellera effort. If you have a great idea for a new feature, you should submit your request to the Technical Subcommittee. Better yet! You should join the Subcommittee and help us implement it! ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
  John Aynsley, CTO, Doulos Before diving into more technical detail concerning VMM 1.2, let’s take some time to review a basic concept of transaction-level communication that often causes confusion, particularly for people more familiar with HDLs like Verilog and VHDL than with object-oriented software programming. This is the idea of the transaction-level interface. A transaction-level interface is a software interface that permits software components to communicate using a specific set of function calls (also known as method calls). In the case of VMM, the software components in question are VMM transactors, and the function calls are the VMM TLM methods such as b_transport, introduced in previous posts on this blog. Such transaction-level interfaces are often depicted diagrammatically as shown here: Ports and exports are depicted as if they were pins on the periphery of a component, which is accurate in a metaphorical sense, but misleading if taken too literally. A port is a structured way of representing the fact that the Producer transactor above makes a call to a specific function, and thus requires an implementation of that function in order to compile and run. On the other side, an export is a structured way of representing the fact that the Consumer transactor provides an implementation of a specific function. So although the diagram may appear to show two components with a structural connection between them, it actually shows the Producer making a call to a function implemented by the Consumer. What may appear to be a hardware connection turns out to be an object-oriented software dependency between Producer and Consumer. When it comes to combining multiple transactors, the types of the transaction-level interfaces have to be respected. The declarations of ports and exports are each parameterized with the type of the transaction object to be passed as a function argument: vmm_tlm_b_transport_port #(Producer, transaction) port; vmm_tlm_b_transport_export #(Consumer, transaction) export; The port, which requires a transaction-level interface of a given type, must be bound to an export that provides an interface of the same type. The type in question is provided by the second parameter transaction. The tlm_bind method effectively seals a contract between the transactor that requires the interface and the provider of the interface: producer.port.tlm_bind( consumer.export ); One benefit of transaction-level interfaces is that this connection is strongly typed, so the SystemVerilog compiler will catch any mismatch between the types of the port and the export. As well as binding a port to an export peer-to-peer, it is also possible to bind chains of ports or exports going up or down the component hierarchy, as shown diagrammatically below: Child-to-parent port bindings carry the function call up through the component hierarchy to the left, while parent-to-child export bindings carry the function call down through the component hierarchy to the right. A port-to-export binding is only permitted at the top level. At run-time, a method call to the appropriate function is made through the child port: port.b_transport(tx, delay); This will result in the corresponding function implementation being called directly, with no intervening channel to store the transaction en route. Transaction-level interfaces are fast, robust, and simple to use, which is why they have been incorporated into VMM. ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
This is a temporary post that was not deleted. Please delete this manually. (33310968-211f-4ca3-b0c6-8c359efdb988 – 3bfe001a-32de-4114-a6b4-4005b770f6d7) ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
Dr. Ambar Sarkar, Chief Verification Technologist, Paradigm Works Inc. Let’s start with a quick and easy quiz. Imagine you are verifying a NxN switch where N is configurable, and it supports any configuration from 1×1 to NxN. Assume that for each port, you instantiate one set of verification components such as monitors, bfms, scoreboards. For example, if this was a PCIe switch with 2 ingress and 3 egress ports, you would instantiate 2 ingress and 3 egress instances of PCIe verification components. So, for N = 5, how many environments you should actually write code for and maintain? The answer better be 1 J™. Maintaining NxN = 25 distinct environments will be quixotic at best. What you want is to write code that creates the desired number and configurations of component instances based on some runtime parameters. This is an example of what is known as “structural” configuration, where you are configuring the structure of your environment. In VMM1.2 parlance, this means that you want to make sure you can communicate to your build_ph() phase the required number of ingress and egress verification component instances. The build phase can then construct the configured number of components. This way, you get to reuse your verification environment code in multiple topologies, and reduce the number of unique environments that need to be separately created and maintained. Hopefully, this establishes why structural configuration is important. So the questions that arise next are: How do you declare the structural configuration parameters? How do you specify the structural configuration values? How do you use the structural configuration values in your code? Declaring the configurable parameters Structural configuration declarations should sit in a class that derives from vmm_unit. A set of convenient macros are provided, with the prefix `vmm_unit_config_xxx where xxx stands for the data type of the parameter. For now, xxx can be int, boolean, or string. For example, for integer parameters, you have: `vmm_unit_config_int    (     <name of the int data member>,     <describe the purpose of this parameter>,     <default value>,     <name of the enclosing class derived from vmm unit> );  If you want to declare the number of ports as configurable, you can declare a variable num_ports as shown below:class switch_env extends vmm_group;    `vmm_typename(switch_env)?     int     num_ports; // Will be configured at runtime     vip bfm[$]; // Will need to create num_port number of instances     …     function new(string inst, vmm_unit parent = null);         super.new(get_typename(), inst, parent); // NEVER FORGET THIS!!     endfunction     // Declare the number of ingress ports. Default is 1.    `vmm_unit_config_int(num_ports,”Number of ingress ports”, 1, switch_env);    … endclass Specifying the configuration values There are two main options:       1.  From code using vmm_opts.             The basic rule is that you need to specify it *before* the build phase gets called, where the construction of the components take place.  A good place to do so is in vmm_test::set_config().             function my_test::set_config();                 ….                 // Override default with 4. my_env is an instance of switch_env class.                 vmm_opts::set_int(“my_env:num_ports”, 4);             endfunction       2.  From command line or external option file. Here is how the number of ingress ports could be set to 5.             ./simv +vmm_opts+num_ports=5@my_env The command line supersedes option set within code as shown in 1. Do note that one can specify these options for specific instances or hierarchically using regular expressions. Using the configuration values This is the easy part. As long as you declare the parameter using `vmm_unit_config_xxx , you are all set. It will be set to the correct value when the containing object derived from vmm_unit is created.     function switch_env::build_ph();   ?        // Just use the value of num_ports       for (i = 0; i< num_ports; i++) begin           bfm[i] = new ($psprintf(“bfm%0d”, i), this);        end     endfunction  This article is the 6th in the Verification in the trenches series. Hope you found this article useful. If you would like to hear about any other related topic, please comment or drop me a line at ambar.sarkar@paradigm-works.com. Also, if you are starting out fresh, please check out the free VMM1.2 environment generator at http://resourceworks.paradigm-works.com/svftg/vmm . ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
by Asif Jafri, verification engineer, Verilab Atomic Generators Atomic generators select and randomize transactions based on user constraints. If you do not care about what sequence the transactions are generated, then atomic generators are a good choice. The code below shows a pcie_config class which defines the read and write transactions. It also has two macros defined that create a channel and the atomic generator to drive the channel. // filename: pcie_config.sv class pcie_config extends vmm_data;   typedef enum {Read, Write} kind_e;   rand kind_e instruction;   rand bit [31:0] address;   rand bit [31:0] data; … endclass: pcie_config `vmm_channel(pcie_config) `vmm_atomic_gen(pcie_config, “PCIE Configuration Atomic Generator”) If you are running functional coverage, there are often corner cases or a sequence of events that will require multiple simulation cycles to be covered. The time to get to these specific points in the state space can be drastically reduced by using scenario generators to generate the specific sequence of events. Scenario Generators Scenario generators are useful if you need to constrain your generator to generate a specific sequence of transactions. Let’s say to configure your PCIE device you need to follow a series of steps. 1.    Read the status register 2.    Read the control register and 3.    Write to the control register In this case you can use your pcie_config class once again but now define a macro for its scenario generator instead of the atomic generator. // filename: pcie_config.sv `vmm_channel(pcie_config) `vmm_scenario_gen(pcie_config, “ PCIE Configuration Scenario Generator”) Here again a single channel will be connected to this scenario generator. We now need to define the scenario as shown below. // filename: pcie_gen.sv class pcie_control_scenario extends pcie_config_scenario; // Variable to identify the scenario int pcie_control_scenario_id constraint pcie_control_scenario_items {   if($void(scenario_kind) == pcie_control_scenario_id) {     // number of elements in the scenario     length == 3;     // run scenario more than once        repeated == 0;                             foreach(item[i])     if(i==0)       this.items[i].instruction == pcie_config:: Read(status_address);       else if(i==1)       this.items[i].instruction == pcie_config:: Read(control_address);       else if(i==2)       this.items[i].instruction == pcie_config:: Write(control_address);    } } endclass: pcie_gen These are also called single stream scenarios.  Scenario generators work well for block level testbenches, but if we need to control multiple blocks in the system level testbench to generate specific interactions to improve coverage multi-stream scenario generators should be used. Multi-stream Scenario (MSS) Generators In a typical chip there will be more than one type of peripheral. Ex: PCIE, USB, Ethernet. Each needs to be controlled separately with its own scenario generator. But there is often a time when the interface on the PCIE and the USB need to be controlled together. This is where MSS generators are useful. MSS generators can feed transactions to multiple channels, unlike single stream scenario generators and atomic generators which can feed only one. Figure 1: Multi-Stream Scenarios                   Let’s try to create a MSS with the PCIE scenario and the USB scenario together. 1.    Use the macros to generate  the pcie_config channel and scenario // filename: pcie_config.sv `vmm_channel(pcie_config) `vmm_scenario_gen(pcie_config, “PCIE Configuration Scenario Generation”) 2.    Use the macros to generate  the usb_config channel and scenario // filename: usb_config.sv `vmm_channel(usb_config) `vmm_scenario_gen(usb_config, “USB Configuration Scenario Generation”) 3.    Define scenarios for the PCIE and USB that you want to control. An example for the PCIE control scenario is shown in the previous section. 4.    Next extend your scenario from vmm_ms_scenario to put the above defined scenarios together and in the execute task define how to use the above defined scenarios.  Directed stimulus for the ETH module can be reused from the block level testbench by encapsulating it in the MSS. The directed stimulus can be cut-and-pasted into the MSS or the MSS could directly call an external function to execute the stimulus. // filename: my_ms_scenario.sv class my_ms_scenario extends vmm_ms_scenario;   pcie_control_scenario pcie_control_scenario;   usb_control_scenario  usb_control_scenario;   task execute();     fork begin       pcie_config_channel out_chan;       pcie_control_scenario.apply(out_chan);     end     begin       usb_config_channel out_chan;       usb_control_scenario.apply(out_chan);     end     begin       // Directed test code goes here     end     join   endtask: execute endclass: my_ms_scenario 5.    Of course in your top level testbench don’t forget to instantiate your multi-stream scenario and register the various channels that it will use to talk to the PCIE and USB peripherals. You will also need to register the multi-stream scenario generator. // filename: top_tb.sv vmm_ms_scenario_gen mss_gen; my_ms_scenario my_ms_scenario; pcie_config_chan pcie_config_chan; usb_config_chan usb_config_chan; mss_gen = new(“Multi-stream scenario generator”); my_ms_scenario = new(); pcie_config_chan = new(“PCIE CONFIGURATION CHANNEL”, pcie_config_chan); USB_config_chan = new(“USB CONFIGURATION CHANNEL”, usb_config_chan); mss_gen.register_channel(“PCIE_SCENARIO_CHANNEL”, pcie_config_channel); mss_gen.register_channel(“USB_SCENARIO_CHANNEL”, usb_config_channel); mss_gen.register_ms_scenario(“MSS_SCENARIO”, my_ms_scenario); mss_gen.stop_after_n_scenarios = 10; As can be seen in Figure 1, you can combine scenarios to make multi-stream scenarios, or you can also have higher level multi-stream scenario generators calling other multi-stream scenario generators and directed tests. ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
by Jason Sprott Jason Sprott is CTO at Verilab. Often times we find ourselves with some configurable RTL to verify. The amount of configuration can vary from a few bus width parameters, or a configurable IP block with optional features and performance related control parameters, to a whole chip with optional interfaces. This can make our life as verification engineers that bit more complicated. We not only have to verify the multitude of scenarios for a specific implementation, we have to somehow handle building and running a testbench for the various RTL configurations. Especially in the case of standalone IP, it’s often the case that different configurations are included in our verification space. Configurations may affect the physical interface between the testbench and RTL, for example: •    Bus widths •    Number of interrupts •    Number of instances of an external interfaces such as USB or Ethernet Or, the configurations might control internal behavior, which doesn’t affect the physical interface, but does affect the way the test bench has to interact with the DUT functionally. Examples might include: •    FIFO Depth – which may affect data pattern generation to hit corner cases, or performance related water marks •    QoS algorithms – where different algorithms implementations are selected depending on requirements, which could affect traffic generation, checking, or functional coverage The VMM has a new RTL configuration feature which can make life a bit easier. RTL configurations can now be encapsulated in a testbench object that can be used to generate an output file. This output file can be shared between the RTL and testbench. The steps in the process go something like this: STEP 1: Compile the RTL (with some default configuration) and testbench. This is needed to run the configuration file generation only. Tests will not be run in the simulation. STEP 2: Run simulation to generate configuration file ./simv +vmm_rtl_config=<PREFIX> +vmm_gen_rtl_config … The format of the output file can be customized by extending a companion format class. This determines the output format written and also the parsing of the file back into the testbench. The (simple) format that comes out of the box looks like this: num_of_mems : 4 has_buffer  : 1 This obviously isn’t RTL code, so if we want our Verilog to understand it, for example converting it to assign parameter values, we have to perform the next step. STEP 3: Convert output configuration file to verilog params file (or format of your choice, such as IP-XACT) A script is provided as a demonstration, in the memsys_cntrlr std_lib VMM example. ./cfg2param <config_file> STEP 4: Re-compile the RTL using the new parameters generated by the configuration, e.g. using -parameters switch in VCS. STEP 5: Run the simulation executing tests, passing the configuration into the testbench ./simv +vmm_rtl_config=<PREFIX> … The code encapsulating the configuration parameters in the testbench is encapsulated using the vmm_rtl_config class. The code that implements output to the configuration file for each variable is done using macros. The example below shows implementation for class member variables. class my_cfg extends vmm_rtl_config;    rand int num_of_mems;    rand bit has_buffer;    constraint valid_c {       num_of_mems inside {1,2,4,8};    }    `vmm_rtl_config_begin(my_cfg)       `vmm_rtl_config_int(num_of_mems,num_of_mems)       `vmm_rtl_config_boolean(has_buffer,has_buffer)    `vmm_rtl_config_end(my_cfg)    … endclass:my_cfg On the testbench side the configuration is set using the vmm_opts::set_object() in the initial block of the program block of your testbench and can be retrieved through the vmm_opts::get_object(). The beauty of using this method for RTL configuration is that constrained randomization and functional coverage collection can be used for RTL configurations. In projects where highly configurable IP has to be verified, it’s a convenient way to control and observe progress of verification across, not only the modes of operation, but also those modes relating to specific RTL configurations. ShareThis...
Uncategorized