Featured post

Top 5 books to refer for a VHDL beginner

VHDL (VHSIC-HDL, Very High-Speed Integrated Circuit Hardware Description Language) is a hardware description language used in electronic des...

Showing posts with label Tricks n Tips. Show all posts
Showing posts with label Tricks n Tips. Show all posts

Monday, 10 August 2020

Useful Vim plug-in for efficient coding in Perl

Think of a plug-in with which you can do the following while coding a Perl script:

  • Auto addition of file header
  • Easy addition of function/frame comment
  • Quick inclusion of default code snippet
  • Performing syntax check
  • Reading documentation about a function
  • Converting a full code block to comment, and vice versa
  • Help to speed up the code writing with consistency in coding.
One-stop solution for all these things is a perl-support vim plug-in

The Perl-Support Vim Plugin – Perl-IDE offers the easiest way to do all of the above, saving a lot of time and keystrokes.

We have already discussed in an earlier article regarding Use of Scripting languages in VLSI

We will be covering the following in this article

1. How to install perl-support plugin to use it with VIM.

2. Powerful features of the Perl-support plugin.


Steps to install Perl-Support VIM Plug-in


1. Download the plugin from the vim.org website.

Click here to download to go to the download page

Alternatively use below command

cd /usr/src/
wget http://www.vim.org/scripts/download_script.php?src_id=9701

2. Copy the zip archive perl-support.zip to $HOME/.vim and run below command

  unzip perl-support.zip

This command will create following files:

  $HOME/.vim/autoload/mmtemplates/...

  $HOME/.vim/doc/...

  $HOME/.vim/plugin/perl-support.vim

3. Loading of plug-in files must be enabled in $HOME/.vimrc. If not use previously

  filetype plugin on

Create .vimrc if there is none or use the files in $HOME/.vim/perl-support/rc as a starting point.


After done with installation lets get to know about

The Powerful Features of Perl-support


1. Add Automatic Header to *.pl file whenever you create a new file

2. Insert statements

3. Insert frequently used statements

4. Insert special variables

5. insert code snippets and manage templates

6. Run a profiler

7.  Run the script, check the syntax, start the debugger

8. Make integration


Details of all these can be found at 



Sunday, 13 July 2014

Calculations using command-line

calculatorMany of you do most of your work from the command-line, using vim to edit files, mutt for e-mails, cd/ls/mv/find/etc instead of a file manager, then you may get annoyed by having to fire up a GUI calculator to make (what may sometimes be) a single calculation.

One useful feature of calculating on the command-line is that you can see what you've typed. For instance, sometimes when I'm entering a long, complex calculation on a calculator (either the GUI or the solid, hold-in-your-hand type), I sometimes forget if I've actually typed in all those numbers or made the calculations in the right order. Maybe it's just me ...

This article shows how to quickly perform standard calculations on the command line including addition, subtraction, multiplication, division, square root, powers, conversion from decimal to hex, decimal to binary, hex to decimal, and binary to decimal. It also briefly introduces using bc in interactive mode and how to write files for use with bc for frequently repeated operations. There is a mention of using Google for performing calculations. It finishes with a little challenge to test the power of your CPU.

Other advantages of using bc include:

  • bc is included with (almost?) all Linux distributions as standard, as well as (again, almost?) all Unix.
  • Results from calculations in some proprietary flavours of bc have up to 99 decimal digits before and after the decimal point. This limit has been greatly surpassed in GNU bc. I don't know what that limit is, but it's at least many, many tens of thousands. Certainly it's more than any GUI-based calculators (I've used) could accommodate.
  • You may also find yourself working in an environment where you simply don't have access to a GUI.
  • The syntax for basic sums is almost identical to Google's calculator function, so you can learn how to use two utilities in one go!

bc is a pre-processor for dc. The useful thing about bc is that it accepts input from files and from standard input. This allows us to pipe data to it for quick calculations.

  • addition
  • subtraction
  • multiplication
  • scale
  • division
  • square root
  • power
  • parentheses
  • obase and ibase
  • convert from decimal to hexadecimal
  • convert from decimal to binary
  • convert from binary to decimal
  • convert from hexadecimal to decimal
  • a brief introduction to interactive mode
  • using bc with shell scripts
  • a brief introduction to using bc with files
  • a quick challenge for your PC (GNU bc only)

Most of these examples follow a simple formula.

addition

$ echo '57+43' | bc
100

subtraction

$ echo '57-43' | bc
14

multiplication

$ echo '57*43' | bc
2451

scale
The scale variable determines the number of digits which follow the decimal point in your result. By default, the value of the scale variable is zero. (Unless you use the l option in which case it defaults to 20 decimal places. More about l later.) This can be set by declaring scale before your calculation, as in the following division example:

division

$ echo 'scale=25;57/43' | bc
1.3255813953488372093023255

square root

$ echo 'scle=30;sqrt(2)' | bc
1.414213562373095048801688724209
This beats Google's calculator function which only calculates the result to 8 decimal places! ;-) Although Google's calculator function has this 8 decimal places limitation, it will allow imaginary numbers as answers.

power

$ echo '6^6' | bc
46656

parentheses
If you have read Robert Heinlein's The Number of the Beast, you may recall that the number of parallel universes in the story equals (six to the power of six) to the power of six. If you should try to calculate that like this:

$ echo '6^6^6' | bc
You will get a screen full of numbers (some 37374 digits), not the
10314424798490535546171949056

that you might expect.

If you're running a non-GNU version of bc, you'll most likely get something like:

exp too big
empty stack
save:args

The Google Calculator balks at '6^6^6' as well. Good ol' GNU.

That's because you typed the wrong question. You need to type:

$ echo '(6^6)^6' | bc

Whereas what you did type was interpreted as:

$ echo '6^(6^6)' | bc

which is an entirely different number altogether. So the positioning of parentheses (brackets to you and me!) is very important. I use brackets to separate the different components of my sums whenever possible, just eliminate any possible doubt that I may get the wrong answer. Consider the following calculations:

$ echo '7+(6*5)' | bc

$ echo '7+6*5' | bc

$ echo '6*5+7' | bc

They all give the same answer, 37, but I would have typed the first calculation, unless of course, I meant:

$ echo '(7+6)*5' | bc

Or to put it another way:

$ echo '13*5' | bc

which is 65.

obase and ibase

obase and ibase are special variables which define output and input base.

Legitimate obase values range from 2 to 999, although anything beyond 16 is wasted on me!

Legitimate ibase values range from 2 to 16.

Some examples will explain all this better.

convert from decimal to hexadecimal

Here we're converting 255 from base 10 to base 16:

$ echo 'obase=16;255' | bc
FF

convert from decimal to binary

And here we're converting the number 12 from base 10 to base 2:

$ echo 'obase=2;12' | bc
1100

Which reminds me of the old joke:

There are only 10 types of people in the world -- those who understand binary, and those who don't.

Which leads us neatly onto the next example:

convert from binary to decimal

Here we're converting the binary number 10 to a base 10 (decimal) number.

$ echo 'ibase=2;obase=A;10' | bc
2

Note that the obase is "A" and not "10". Sorry, you've got to learn some hex. The reason for this is you've set the ibase to "2", so if you now had tried to use "10" as the value for the obase, it would stay as "2", because "10" in base 2 is "2". So you need to use hex to "break out" of binary mode.

Well, that was just to explain the joke; now something a bit more challenging:

$ echo 'ibase=2;obase=A;10000001' | bc
129

convert from hexadecimal to decimal

$ echo 'ibase=16;obase=A;FF' | bc
255

Again, note the use of "A" to denote base 10. That is because "10" in hex (base 16 - the ibase value) is 16.

a brief introduction to interactive mode
You can also run bc in interactive mode:

$ bc

If you're running GNU bc, you should get the following notice:

bc 1.06
Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
Followed by an uninviting blank prompt. Otherwise you'll just get an uninviting blank prompt.
If you wish to get straight to the uninviting blank prompt, use the -q option, which runs bc in quiet mode, preventing the normal GNU bc welcome from being printed:
$ bc -q

Using the basics we've been through from the examples above, enter a calculation:

scale=5
57/43
1.32558


Type quit to exit bc interactive mode.


using bc with shell scripts
You can use shell variables with bc, which is very useful in shell scripts:


$ FIVE=5 ; echo "$FIVE^2" | bc
25


Note the use of double-quotes to preserve the value of the variable $FIVE.

a brief introduction to using bc with files

Using bc with files allows complex calculations to be repeated, again and again, a bit like using a spreadsheet to run the same calculations on changing figures ... but faster.


Here is a simple example:

scale=2

/* C-style comments
are allowed, as are spaces */

print "\nConvert Fahrenheit to Celsius\n\n"
print "Temperature in Fahrenheit: " ; fah = read()
print "\n"
print "Equivalent Temperature in Celsius is: "
(fah - 32.0) * 5.0 / 9.0
quit

Create and save the file, then run it like this:

$ bc -q filename
Convert Fahrenheit to Celsius
Temperature in Fahrenheit: 61
Equivalent Temperature in Celsius is: 16.11


Note that this example has only been tested with GNU bc. Other (proprietary) versions of bc may have more stringent syntax requirements. Some bcs don't allow the use of print or read, for example, so you have to edit your file before each calculation. Not very useful.


a quick challenge for your PC (GNU bc only)
If you wish to test the comparative speed of your PC, try this challenge: use bc to calculate Pi to 5000 decimal places. The idea for this challenge came from a great article at Geekronomicon.
If you really want to tie up your machine for an hour (or more), you could try the "Pi to 25000 decimal places" challenge from the aforementioned Geekronomicon.

First, to put things in perspective, here is some information about my CPU:

$ cat /proc/cpuinfo | egrep "model name|MHz"
model name      : AMD Athlon(tm) 64 Processor 3500+
cpu MHz         : 2211.346


Note the use (below) of the command bc –l -q.
-l loads the math library which is required for the use of the "2" (arctangent) in the calculation for Pi. You can learn more about the math library functions in the
bc command manual.
I'm not sure what effect the -q option (quiet, no welcome message printed) has on our test, but I guess it can't harm.

$ time echo "scale=5000; 4*a(1)" | bc -l -q
3.141592653589793238462643383279502884197169399375105820974944592307\
...
...
...
73774418426312986080998886874132604720


real    0m44.164s
user 0m44.099s
sys 0m0.008s
44.099 seconds! Not bad. :-) I imagine that some Gentoo folks may be interested to see what difference their compile-time optimisations make to the speed of bc. FWIW, my distro of choice is Arch Linux.

Tuesday, 31 December 2013

Run-time configurations in OVM

Overriding or changing the existing configuration of VE components from testcases/sequences is something needed every now and then while working in system verilog based environment.  Also in many circumstances we need to share some variables/flags between multiple components of env(ovm_env) not necessarily through the TLMs.

Some of the examples are like,

· Enabling/disabling specific streams of scoreboard.

· Enabling/disabling specific checkers after specific sequences are executed.

· Informing driver/sequencer to inject error into some Nth packet  etc...

Let’s see and understand how this could be achieved in OVM based system-verilog environment. OVM’s configuration table and its methods provide easy way to achieve this.

Let’s assume that one needs to disable the specific stream of scoreboard using OVM’s configuration table from testcases.

Step 1: Define a configuration class containing all the parameters/configuration fields which need to be changed run-time,

class xyz_config extends ovm_object;

  bit sb_stream_disable;

  `ovm_object_utils_begin(xyz_config)

    `ovm_field_int(sb_stream_disable, OVM_ALL_ON)

  `ovm_object_utils_end

  //Constructor

  ….

endclass : xyz_config

Based on the complexity of the environment you can also create multiple configuration classes to configure specific agents or specific set of components.

Step 2: Use the configuration class in scoreboard to disable the applicable data-stream.

class xyz_scoreboard extends ovm_scoreboard;

  xyz_config cfg;

  `ovm_component_utils_begin(xyz_scoreboard)

    `ovm_object_utils(cfg, OVM_ALL_ON)

  `ovm_component_utils_end

  //Constructor

  //Run thread

  task run();

  //Use the cfg.disable_sb_stream to disable the scoreboard data stream

  ....

  endtask : run

endclass : xyz_scoreboard

As it is specified here, `ovm_object_utils is must here. It 'gets' the configuration instance from the OVM’s configuration table for ‘this` class.

Step 3: From the OVM based testcases disable the scoreboard at required point of time in the simulation.

class test extends ovm_test;

  xyz_cfg cfg = new();

  function void build();

    super.build();

    //Sharing the config. instance at target path.

    set_config_obect(“env.scoreboard”,”cfg”,cfg,,0);

  endfunction : build

  task run();

    //Some test code

    //After some time disable the scoreboard stream

    cfg.disable_sb_stream = 1;

   //Other code

  endtask : run

endclass : test

As you can see, one needs to ‘new’ the config. instance and share it with required classes using set_config_object method of ovm. The usage of set_config_object function with proper arguments is one of the crucial point here.
Let’s understand what arguments should be passed to this function,

1st argument is a string which specifies the target path where configuration needs to be shared. One could also use wildcard expressions here as env.*

2nd argument specifies the string-instance name of the object.

3rd argument is the object handle.

4th argument specifies whether object needs to be cloned before passing it to targeted component. For run-time passing set it to 0. This means all the components in env. which attempt to get the "cfg" from configuration table will use the same object throughout the simulation.

In cases where you have only few fields to share, you can also use OVM’sset_field_int and get_field_int methods.

The only additional step needed to pass the config. runtime is to call theapply_config_settings() method on the targeted component, as explained inthis thread under OVM forum.

Enjoy Configuring!!

Wednesday, 17 July 2013

Guidelines for Successful SoC Verification in OVM/UVM

uvm-logo-web1 With the increasing adoption of OVM/UVM, there is a growing demand for guidelines and best practices to ensure successful SoC verification. It is true that the verification problems did not change but the way the problems are approached and the structuring of the solutions, i.e. verification environments, depends much on the methodology. There are two key categories for SoC verification guidelines: process, enabled by tools, and methodology. The process guidelines are about what you need to do and in what order, while the methodology guidelines are about how to do it. This paper will first describe the basic tenets of OVM/UVM, and then it tries to summarize key guidelines to maximize the benefits of using state of the art verification methodology such as OVM/UVM.

The BASIC TENETS of OVM/UVM

1. Functionality encapsulation

OVM [1] promotes composition and reuse by encapsulating functionality in a basic block called ovm_component. This basic block contains a run task, i.e a functional block that can consume time that acts as an execution thread responsible for implementing functionality as simulation progress.

2. Transaction-Level Modeling (TLM)

OVM/UVM uses TLM standard to describe communication between verification components in an OVM/UVM environment. Because OVM/UVM standardizes the way components are connected, components are interchangeable as long as they provide and require the same interfaces. One of the main advantages of using TLM is in abstracting the pin and timing details. A transaction, the unit of information exchange between TLM components, encapsulates the abstract view of stimulus that can be expanded by a lower-level component. One the pitfalls that can undermine the value of TLM is adding excessive timing details by generating transaction and delivering them on each clock cycle.

3. Using sequences for stimulus generation

The transactions need to be generated by an entity in the verification environment. Relying on a component to generate the transactions is limiting because it will require changing the component each time a different sequence of transactions is required. Instead OVM/UVM allows for flexibility by introducing ovm_sequence. ovm_sequence is a wrapper object around a function called body(). It is very close to an OOP pattern called "functor" that wraps a function in an object to allow it to be passed as a parameter but SystemVerilog does not support operator overloading [1]. ovm_sequence when started, register itself with an ovm_sequencer which is an ovm_component that acts as the holder of different sequences and can connect to other ovm_components. The ovm_sequence and ovm_sequencer duo provides the flexibility of running different streams of transactions without having to change the component instantiation.

4. Configurability

Configurability, an enabler to productivity and reuse, is a key element in OVM/UVM. In OVM/UVM, user can change the behavior of an already instantiated component by three means: configuration API, Factory overrides and callbacks.

5. Layering

Layering is a powerful concept in which every level takes care of the details at specific layers. OVM layering can be applied to components, which can be called hierarchy and composition, and to configuration and to stimulus. Typically there is a correspondence between layering of components and objects. Layering stimulus, on the other hand, can reduce the complexity of stimulus generation.

6. Emphasis on reuse (vertical and horizontal)

All the tenets mentioned above lead to another important goal which is reuse. Extensibility, configurability and layering facilitate reuse. Horizontal reuse refers to reusing Verification IPs (VIPs) across projects and vertical reuse describes the ability to use block-level VIPs in cluster and chip level verification environments.

PROCESS GUIDELINES

1. Ordering of development tasks

The natural process for developing OVM/UVM verification environment is bottom-up. Blocks are first verified in block-level environments, and then the integration of the blocks into SoC is verified in chip-level testbench. Some refers to this methodology as IP-centric methodology because the blocks are considered IPs [4]. The focus of block-level verification is to verify the blocks thoroughly, while the chip-level is focused on verifying the integration of the blocks and the application scenarios. A bottom-up verification approach has several benefits:

  • Localization of bugs: finding bugs easily
  • Easier to test all the block modes at the block-level
  • Confidence in the block-level allowing them to be reused in several projects.

In this section we describe the recommended ordering for development of verification environment elements. Such ordering must be in mind when developing executable verification plans.

Table 1: Components Development Order

Interfaces
Agents
     Transaction
     Configuration
     Agent Skeleton
     Transactors
     Basic Sequences
Block level Subsystem
     Configuration
     Virtual Sequencer
      Initial Sequences/Tests
      Scoreboards & Protocol Checkers
      Coverage Model
      Constrained Random Sequences/Tests
Chip Level
       Integration of Subsystem environments
       Chip-Level Sequences/Tests

It is worth noting the following:

  • Once transaction fields are defined and implemented, the agent skeleton can be automatically generated.
  • Transactors refer to drivers and monitors
  • The reason for having the scoreboards & protocol checkers early on is to make sure that what was developed is functioning
  • Coverage model needs to be before the constrained random tests to guide the test development and eliminate redundancy. This is a corner stone of Coverage Driven verification. The coverage model not only guides the test writing effort but rather gives is a metric for verification progress and closure.
  • Each block/subsystem/cluster verification environment and tests act as a VIP for this block.

2. Use code and template generators

Whether you are relying on script or elaborate OVM template generators, these generators are keys to increase the productivity of verification engineers, reduce errors and increase code conformity. Code generators are also used to generate register models from specification thus automating the creation of these models

3. Qualify your VIPs

Qualify your VIP during development and before releasing them. First, several tools can conduct static checking on your VIP components for common errors and conformance to coding styles. They can also provide statistics about the size of your code, checks and covergroups.

Second, typically a simulator can provide statistics about memory consumption and performance bottlenecks of your VIP. Although SystemVerilog has automatic garbage collection, you can still have memory leaks because you keep a reference to dynamically allocated objects somewhere and forget about them.

Third, your VIPs should be robust to user mistakes whether in connections or proper use. You need to have sanity checks that can flag early a user error.

Finally, peer review is still beneficial to point-out issues that are missed in the other steps.

4. Incremental integration

As described in the introduction, OVM/UVM facilitates composition and layering. Several components/agents can form an environment and two or more environments can form a higher level environment. Incremental integration is important to reduce debugging time.

5. Better regression management and result analysis

The usual scripts that compile and run testcases come short when running complex OVM/UVM SoC verification environment. Typical requirements on run management is to keep track of seeds, log files of different tests, execution time, flexibility of running different groups of tests and running on local machine or grid. Once a regression is run we end up with data that needs to be processed to come out for useful information such as which tests passed/failed, common failure messages, which tests were more efficient and which seeds produced better coverage.

6. Communication and change management

Communication between verification engineers and specification owners should be captured in an issue tracking tool to avoid losing the information along the project. Also verification engineers need mechanism to share what they learn between each other, Wikis serve as good vehicles to knowledge sharing.

Change management is the other crucial element. By change management we are not only referring to code version management but the way the changes in RTL and block-level environments are handled in cluster or chip level environments.

METHODOLOGY GUIDELINES

1. CPU modeling

SoCs typically have one or more software programmable component such as microcontroller, microprocessor or DSP. Processor Driven Verification refers to using either a functional model of the processor or RTL model to verify the functionality of the SoCs. This approach is useful to verify the firmware interactions and certain application scenarios. However, for thorough verification of subsystems/cluster this approach can be costly in terms of effort, complexity, and simulation time. This paper proposes two level approach: for the verification of subsystems use a pin-accurate and protocol accurate Bus Functional Model (BFM), this will enable rapid development of the verification environment and tests and at the same time gives flexibility to the verification engineer in creating the environment and test. The BFM usually comes as VIP for the specific bus standard that the processor connects to. While the VIP usually models the standard interface faithfully, the processor might have extra side-band signals and interrupt. There are two approaches to this: the VIP can model in a generic way the side-band and interrupt controller behavior through the use of configuration, transactions and sequences. The other approach is to model the functionalities in different agents for side-band signals and interrupts. This increases the burden on the development and requires synchronization between different agents.

For the verification of firmware interaction, such as boot-loading or critical application scenarios, the RTL model or a full functional model can be used guarantee that firmware is validated versus the hardware it is going to run on and that the hardware.

2. Environment Reuse

Environments should be self-contained having only knowledge about its components and global elements and can communicate only through configuration mechanism, TLM connections or global events such as reset event. Following these rules, an environment at the block-level can be reused at the chip-level making the chip-level environment the integration of block-level environments.

3. Sequence Reuse

It is important to write sequences with eye on reusing them. In OVM/UVM, there are two types of sequences: sequence which sends transactions and sequences that starts sequences on sequencers. The latter is called a virtual sequence. Below is further classification of the sequences based on the functionality:

  • Basic agent sequence: this sequence allows the user to control the fields of a transaction that sent by the basic sequence from outside. The basic agent sequence acts as an interface or API to randomize or set the fields of the transactions sent by a higher layer which is usually the virtual sequence.
  • Register read/write sequences: these are sequences that try to write and read address mapped registers in the DUT. Two important rules need to be considered: they should have API that is independent of the bus protocol and rely on use the name of the register rather than address. A register package can be used to lookup the register address by name. For Example: OVM register package built-in sequences [5] supports this kind of abstraction. It is also expected that the UVM register package will support these rules. Abiding by these rules make these sequences reusable and maintainable because there is no need to update the sequence each time a register address changes.
  • DUT configuration sequences: some verification engineer try to provide sequences that abstracts the different configurations of the DUT into enum fields to ease the burden on the test writer. This way the test writer does not need to know about which register to write and with what value. These sequences are still reusable at the chip-level.
  • Virtual sequences on accessible interfaces at chip-level: These sequences are reusable from block-level to chip-level; some of them can be used to verify the integration into full-chip.
  • Virtual sequences on internal interfaces that are not visible at the chip-level: Special attention should be paid for sequences generating stimulus on interfaces that are no longer visible at the chip-level.

Although goals are different between block and chip level testing, some virtual sequences from block-level can be reused at chip-level as integration tests. Interfaces that become internal at the chip-level can be usually stimulated through some external interface. In order to make the last type of virtual sequences reusable at chip-level, it is better to plan ahead to abstract the data from the protocol. For example in Figure 1 of SoC diagram peripherals 1 through N are on peripheral bus which might be using a different protocol than the system bus. There are two approaches to make the sequences reusable:

Use functional abstraction by defining functions in the virtual sequence that can be overridden like:

write(register_name, value);

read(register_name, value);

Or rely on a layering technique like ovm_layering[3]. In this approach, a layering agent sits on top of a lower level agent and it forwards high-level transactions that can be translated by the low-level agent according to the bus standard. The high-level agent can be connected to a different low-level agent without any change to the high-level sequences.

Figure 1: Typical SoC Block Diagram20110509_1

4. Scoreboards

A critical component of self-checking testbenches is the scoreboard that is responsible for checking data integrity from input to output. A scoreboard is a TLM component, care should be taken not activate on a cycle by cycle basis but rather at the transaction level. In OVM/UVM, the scoreboard is usually connected to at least 2 analysis ports one from the monitors on the input(s) side and the other on the output(s) Figure 2 depicts these connections. A Scoreboard operation can be summarized in the following equations:

Expected = TF(Input Transaction);
Compare(Actual , Expected);

TF : Transfer function representing the DUT functionality from inputs to outputs

Sometimes the operation is described as predictor-comparator. Where the predictor computes the next output (transfer function) and the comparator checks the actual versus predicted (compare function). Usually the transfer function is not static but can change depending on the configuration of the devices. In SoC, most peripherals have memory-mapped registers that are used for configuration and status. These devices are usually called memory-mapped peripherals and they pose two challenges:

  • DUT transfer function and data-flow might change based on the configuration
  • Status bits should be verified

The common solution to the first one is to have a handle of the memory-map model and connect an analysis port from the configuration bus monitor to the scoreboard. On reception of new transaction on this analysis port, the scoreboard updates the peripheral's registerfile model and then uses it to update the transfer function accordingly. This approach has one disadvantage; each peripheral scoreboard has to implement the same functionality and needs to connect to the configuration bus monitor. A better approach is that the registerfile updates occur in a central component on the bus. To eliminate the need for the connections to the bus monitor, the register package can have an analysis port on each registerfile model. Each Scoreboard can connect to this registerfile model internally without the need for external connections. One of the requirements on the UVM register package is to have update notification method [6].

The second challenge is status bit verification. Status bits are usually modeled in the register model and register model can act as a predictor of the value of status bits. This requires that the scoreboard predicts changes to status bits, update the register models and on register reads the value read from the DUT is compared versus the register model.

There are other aspects to consider when implementing the scoreboards:

  • Data flow analysis: data flow can change based on configuration, or data flow can come from several inputs towards the output.
  • Scoreboard connection technique: Scoreboards can be connected to monitors using one of two ways: through ovm_imps in the scoreboard or through ovm_exports and tlm_analysis_fifos: the latter requires a thread on each tlm_analysis_fifo to get transactions while the former executes in the context of the caller.
  • Threaded or thread-less: the scoreboard can have 0 or more threads depending on a number of factors such as the connection method, the complexity of synchronization and experience of the developer. As a general rule, verification engineers should avoid spawning unnecessary threads in the scoreboard.

At the SoC level, there are two approaches to organize scoreboards with End-to-End and Multi-step [2]. Figure 3 depicts the difference between the two. The multi-step approach has several advantages over the end-to-end:

  • By product of the block-level to chip-level reuse.
  • The checking task is simpler since it is divided over several components each concerned with specific block.
  • Easy to localize bugs at block-level since the violating block scoreboard will flag the error

Figure 2: Scoreboard Connection in OVM20110509_2

 Figure 2: Scoreboard Connection in OVM20110509_3

CONCLUSION

OVM/UVM is a powerful verification methodology. To maximize the value achieved by adopting OVM/UVM there is a need for guidelines. These guidelines are not only for the methodology deployment but also for the verification process. This paper tried to summarize some of the pitfalls and tradeoffs and provide guidelines for successful SoC verification. The set of guidelines in this paper can help you plan ahead your SoC verification environment, avoid pitfalls and increase productivity.

Get free daily email updates!

Follow us!

Thursday, 27 June 2013

SystemVerilog Fork Disable "Gotchas"

SystemVerilig-fork-join This is a long post with a lot of SystemVerilog code. The purpose of this entry is to hopefully save you from beating your head against the wall trying to figure out some of the subtleties of SystemVerilog processes (basically, threads). Subtleties like these are commonly referred to in the industry as "Gotchas" which makes them sound so playful and fun, but they really aren't either.

I encourage you to run these examples with your simulator (if you have access to one) so that a) you can see the results first hand and better internalize what's going on, and b) you can tell me in the comments if this code works fine for you and I'll know I should go complain to my simulator vendor.

OK, I'll start with a warm-up that everyone who writes any Verilog or SystemVerilog at all should be aware of, tasks are static by default. If you do this:

module top;
task do_stuff(int wait_time);
#wait_time $display("waited %0d, then did stuff", wait_time);
endtask

initial begin
fork
do_stuff(10);
do_stuff(5);
join
end
endmodule

both do_stuff calls will wait for 5 time units, and you see this:

waited 5, then did stuff
waited 5, then did stuff

I suppose being static by default is a performance/memory-use optimization, but it's guaranteed to trip up programmers who started with different languages. The fix is to make the task "automatic" instead of static:

module top;
task automatic do_stuff(int wait_time);
#wait_time $display("waited %0d, then did stuff", wait_time);
endtask

initial begin
fork
do_stuff(10);
do_stuff(5);
join
end
endmodule

And now you get what you expected:

module top;
task automatic do_stuff(int wait_time);
#wait_time $display("waited %0d, then did stuff", wait_time);
endtask

initial begin
fork
do_stuff(10);
do_stuff(5);
join_any
$display("fork has been joined");
end
endmodule

You'll get this output:

waited 5, then did stuff
fork has been joined
waited 10, then did stuff

That's fine, but that extra action from the slower do_stuff after the fork-join_any block has finished might not be what you wanted. You can name the fork block and disable it to take care of that, like so:

module top;
task automatic do_stuff(int wait_time);
#wait_time $display("waited %0d, then did stuff", wait_time);
endtask

initial begin
fork : do_stuff_fork
do_stuff(10);
do_stuff(5);
join_any
$display("fork has been joined");
disable do_stuff_fork;
end
endmodule

Unless your simulator, like mine, "in the current release" will not disable sub-processes created by a fork-join_any statement. Bummer. It's OK, though, because SystemVerilog provides a disable fork statement that disables all active threads of a calling process (if that description doesn't already make you nervous, just wait). Simply do this:

module top;
task automatic do_stuff(int wait_time);
#wait_time $display("waited %0d, then did stuff", wait_time);
endtask

initial begin
fork : do_stuff_fork
do_stuff(10);
do_stuff(5);
join_any
$display("fork has been joined");
disable fork;
end
endmodule

And you get:

waited 5, then did stuff
fork has been joined

Nothing wrong there. Now let's say you have a class that is monitoring a bus. Using a classes are cool because if you have two buses you can create two instances of your monitor class, one for each bus. We can expand our code example to approximate this scenario, like so:

class a_bus_monitor;
int id;

function new(int id_in);
id = id_in;
endfunction

task automatic do_stuff(int wait_time);
#wait_time $display("monitor %0d waited %0d, then did stuff", id, wait_time);
endtask

task monitor();
fork : do_stuff_fork
do_stuff(10 + id);
do_stuff(5 + id);
join_any
$display("monitor %0d fork has been joined", id);
disable do_stuff_fork;
endtask
endclass

module top;
a_bus_monitor abm1;
a_bus_monitor abm2;
initial begin
abm1 = new(1);
abm2 = new(2);
fork
abm2.monitor();
abm1.monitor();
join
$display("main fork has been joined");
end
endmodule

Note that I went back to disabling the fork by name instead of using the disable fork statement. This is to illustrate another gotcha. That disable call will disable both instances of the fork, monitor 1's instance and monitor 2's. You get this output:

monitor 1 waited 6, then did stuff
monitor 1 fork has been joined
monitor 2 fork has been joined
main fork has been joined

Because disabling by name is such a blunt instrument, poor monitor 2 never got a chance. Now, if you turn the disable into a disable fork, like so:

class a_bus_monitor;
int id;

function new(int id_in);
id = id_in;
endfunction

task automatic do_stuff(int wait_time);
#wait_time $display("monitor %0d waited %0d, then did stuff", id, wait_time);
endtask

task monitor();
fork : do_stuff_fork
do_stuff(10 + id);
do_stuff(5 + id);
join_any
$display("monitor %0d fork has been joined", id);
disable fork;
endtask

endclass

module top;
a_bus_monitor abm1;
a_bus_monitor abm2;
initial begin
abm1 = new(1);
abm2 = new(2);
fork
abm2.monitor();
abm1.monitor();
join
$display("main fork has been joined");
end
endmodule

You get what you expect:

monitor 1 waited 6, then did stuff
monitor 1 fork has been joined
monitor 2 waited 7, then did stuff
monitor 2 fork has been joined
main fork has been joined

It turns out that, like when you disable something by name, disable fork is a pretty blunt tool also. Remember my ominous parenthetical "just wait" above? Here it comes. Try adding another fork like this (look for the fork_something task call):

class a_bus_monitor;
int id;

function new(int id_in);
id = id_in;
endfunction

function void fork_something();
fork
# 300 $display("monitor %0d: you'll never see this", id);
join_none
endfunction

task automatic do_stuff(int wait_time);
#wait_time $display("monitor %0d waited %0d, then did stuff", id, wait_time);
endtask

task monitor();
fork_something();
fork : do_stuff_fork
do_stuff(10 + id);
do_stuff(5 + id);
join_any
$display("monitor %0d fork has been joined", id);
disable fork;
endtask

endclass

module top;
a_bus_monitor abm1;
a_bus_monitor abm2;

initial begin
abm1 = new(1);
abm2 = new(2);
fork
abm2.monitor();
abm1.monitor();
join
$display("main fork has been joined");
end
endmodule

The output you get is:

monitor 1 waited 6, then did stuff
monitor 1 fork has been joined
monitor 2 waited 7, then did stuff
monitor 2 fork has been joined
main fork has been joined

Yup, fork_something's fork got disabled too. How do you disable only the processes inside the fork you want? You have to wrap your fork-join_any inside of a fork-join, of course. That makes sure that there aren't any other peers or child processes for disable fork to hit. Here's the zoomed in view of that (UPDATE: added missing begin...end for outer fork):

task monitor();
fork_something();
fork begin
fork : do_stuff_fork
do_stuff(10 + id);
do_stuff(5 + id);
join_any
$display("monitor %0d fork has been joined", id);
disable fork;
end
join
endtask

And now you get what you expect:

monitor 2 fork has been joined
monitor 1 fork has been joined
monitor 1 waited 6, then did stuff
monitor 2 waited 7, then did stuff
main fork has been joined
monitor 1 waited 11, then did stuff
monitor 2 waited 12, then did stuff
monitor 2: you'll never see this
monitor 1: you'll never see this

So, wrap your fork-join_any inside a fork-join or else it's, "Gotcha!!!" (I can almost picture the SystemVerilog language designers saying that out loud, with maniacal expressions on their faces).

But wait, I discovered something even weirder. Instead of making that wrapper fork, you can just move the fork_something() call after the disable fork call and then it doesn't get disabled (you actually see the "you'll never see this" message, try it). So, you might think, just reordering your fork and disable fork calls and that will fix your problem. It will, unless (I learned by sad experience) the monitor task is being repeatedly called inside a forever loop. Here's a simplification of the code that really inspired me to write this all up:

class a_bus_monitor;
int id;

function new(int id_in);
id = id_in;
endfunction

function void fork_something();
fork
# 30 $display("monitor %0d: you'll never see this", id);
join_none
endfunction

task automatic do_stuff(int wait_time);
#wait_time $display("monitor %0d waited %0d, then did stuff", id, wait_time);
endtask // do_stuff

task monitor_subtask();
fork : do_stuff_fork
do_stuff(10 + id);
do_stuff(5 + id);
join_any
$display("monitor %0d fork has been joined", id);
disable fork;
fork_something();
endtask

task monitor();
forever begin
monitor_subtask();
end
endtask

endclass

module top;
a_bus_monitor abm1;
a_bus_monitor abm2;

initial begin
abm1 = new(1);
abm2 = new(2);
fork
abm2.monitor();
abm1.monitor();
join_none
$display("main fork has been joined");
# 60 $finish;
end
endmodule

The fork inside the fork_something task will get disabled before it can do its job, even though it's after the disable fork statement.

My advice? Just always wrap any disable fork calls inside a fork-join.

Tuesday, 8 January 2013

Creating .lib file from Verilog netlist

Creating a dummy .lib file is something every physical design engineer has done now and then. If you have a verilog model of the block available, your task gets easier. The following script automates .lib generation from your verilog netlist. Use this as a dummy .lib to get your design flow going or use it as a template for your analog blocks for modifying the values.

Customize, edit and use. The script creates a simple Data Structure. So any modification can be done pretty easily. The script parses the verilog for the module name specified, and collects the ports & directions. In the .lib file written out, a default capacitance and transition value is specified. This is a good starting point for your blocks.

Usage: create_lib <verilog_netlist> <module_name> [transition_value] [capacitance_value]

#!/usr/bin/perl

use strict;

if ($#ARGV < 1 ) {
    print "usage: create_lib <verilog_netlist> <module_name> \n";
    exit;
}

my $netlist = $ARGV[0] ;
my $module  = $ARGV[1] ;
my $tran = 2.5 ;
my $cap = 0.001;
my $signal_level = "VDD" ;

if(defined $ARGV[2]) {$tran = $ARGV[2];}
if(defined $ARGV[3]) {$cap = $ARGV[3];}
if(defined $ARGV[4]) {$signal_level = $ARGV[4];}

my $FF;
my $FO;
open $FF, "< $ARGV[0]" or die "Can't open $ARGV[0] : $!";
open $FO, ">$module.lib" or die "Can't open $module.lib for write : $!";

my $db = createTopLevelDB();
createDotLib($db,$FO);

sub createDotLib
{
    my $topLevelDBRef = shift;
    my $FO = shift ;   
    ### Header
    print $FO "library\($topLevelDBRef->{'design'}->{'cell'}\) {\n";
    print $FO "\n /* unit attributes */\n";
    print $FO "  time_unit : \"1ns\"\;\n";
    print $FO "  voltage_unit : \"1V\"\;\n";
    print $FO "  current_unit : \"1uA\"\;\n";
    print $FO "  pulling_resistance_unit : \"1kohm\"\;\n";
    print $FO "  leakage_power_unit : \"1nW\"\;\n";
    print $FO "  capacitive_load_unit\(1,pf\)\;\n\n";
    foreach my $direction (keys(%{$topLevelDBRef->{'bus'}})) {
        foreach my $bus_type (keys %{$topLevelDBRef->{'bus'}->{$direction}}) {
            my @bus_width =  split(/_/, $bus_type);
            my $bus_hi = $bus_width[1] ;
            my $bus_lo = $bus_width[2] ;
            my $bus_width = $bus_hi+1-$bus_lo;
            print $FO " type \($bus_type\) { \n";
            print $FO "   base_type : array ; \n" ;
                print $FO "   data_type : bit  \n" ;;
                print $FO "   bit_width : $bus_width   \n" ;;
                print $FO "   bit_from : $bus_hi  \n" ;;
                print $FO "   bit_to : $bus_lo ; \n" ;
                print $FO "   downto : true ; \n" ;
                print $FO " } \n" ;
        }
    }
    print $FO "\n  cell\($topLevelDBRef->{'design'}->{'cell'}\) {\n";
    foreach my $direction (keys(%{$topLevelDBRef->{'pins'}})) {
        foreach my $pin_name (@{$topLevelDBRef->{'pins'}->{$direction}}) {
            print $FO ("    pin\($pin_name\) { \n");
            print $FO ("\tdirection : $direction ;\n");
            if($direction eq "input") {
                print $FO ("\tmax_transition : $tran;\n");
            }
            print $FO ("\tcapacitance : $cap; \n");     
            print $FO ("    } \n") ;
        }
    }
    foreach my $direction (keys(%{$topLevelDBRef->{'bus'}})) {
        foreach my $bus_type (keys %{$topLevelDBRef->{'bus'}->{$direction}}) {
            my @bus_width =  split(/_/, $bus_type);
            my $bus_hi = $bus_width[1] ;
            my $bus_lo = $bus_width[2] ;
            foreach my $bus_name (@{$topLevelDBRef->{'bus'}->{$direction}{$bus_type}}) {
                                        chomp($bus_name);
                print "BUS $bus_name : $bus_type : $direction \n" ;
                print $FO ("    bus\($bus_name\) { \n");
                print $FO ("\tbus_type : $bus_type ;\n");
                print $FO ("\tdirection : $direction ;\n");
                if($direction eq "input") {
                    print $FO ("\tmax_transition : $tran;\n");
                }   
                for(my $i=$bus_lo; $i<=$bus_hi; $i++) {
                    print $FO ("\tpin\($bus_name\[$i\]\) { \n");
                    print $FO ("\t\tcapacitance : $cap; \n"); 
                    print $FO ("\t} \n") ;
                }
                print $FO ("    } \n") ;
            }
        }
    }
    print $FO ("  } \n") ;
    print $FO ("} \n") ;
}

sub createTopLevelDB
{
    my $find_top_module = 0;
    my %topLevelDB = () ;
    my %pins = () ;
    my %bus = () ;
    my @input_pins ;
    my @output_pins ;
    my @inout_pins ;
    my @bus_types ;
    my %input_bus = () ;
    my %output_bus = () ;
    my %inout_bus = () ;
    my %design = ();
    $design{'cell'} = $module;
    $design{'tran'} = $tran;
    $design{'cap'} = $cap;
    $design{'signal_level'} = $signal_level;
    while(my $line = <$FF>) {
        last if($find_top_module == 1);
        if($line=~/module\s+$module/) {
            $find_top_module = 1 ;
            while(my $line = <$FF>) {
                next if($line =~ "\s*//" );
                chomp($line);
                if ($line =~/input\s+/ ) {
                    $line=~s/\s*input\s+//;
                    $line=~s/;//;
                    if($line =~/\[(\d+):(\d+)\]/) {
                        my $bus_type = "bus_$1_$2";
                        $line=~s/\[(\d+):(\d+)\]//;
                        my @line =  split(/,/, $line);
                        unless(grep {$_ eq $bus_type} @bus_types) { 
                            push(@bus_types,$bus_type);
                        }
                        foreach my $pin (@line) {
                            $pin=~s/\s+//;
                            push(@{$input_bus{$bus_type}}, $pin );
                        }
                    }
                    else {
                        my @line =  split(/,/, $line);
                        foreach my $pin (@line) {
                            $pin=~s/\s+//;
                            push(@input_pins, $pin);
                        }
                    }
                }
                if ($line =~/output\s+/ ) {
                    $line=~s/\s*output\s+//;
                    $line=~s/;//;
                    if($line =~/\[(\d+):(\d+)\]/) {
                        my $bus_type = "bus_$1_$2";
                        $line=~s/\[(\d+):(\d+)\]//;
                        my @line =  split(/,/, $line);
                        unless(grep {$_ eq $bus_type} @bus_types) { 
                            push(@bus_types,$bus_type);
                        }
                        foreach my $pin (@line) {
                            $pin=~s/\s+//;
                            push(@{$output_bus{$bus_type}}, $pin );
                        }
                    }
                    else {
                        my @line =  split(/,/, $line);
                        foreach my $pin (@line) {
                            $pin=~s/\s+//;
                            push(@output_pins, $pin);
                        }
                    }

                }
                if ($line =~/inout\s+/ ) {
                    $line=~s/\s*inout\s+//;
                    $line=~s/;//;
                    if($line =~/\[(\d+):(\d+)\]/) {
                        my $bus_type = "bus_$1_$2";
                        $line=~s/\[(\d+):(\d+)\]//;
                        my @line =  split(/,/, $line);
                        unless(grep {$_ eq $bus_type} @bus_types) { 
                            push(@bus_types,$bus_type);
                        }
                        foreach my $pin (@line) {
                            $pin=~s/\s+//;
                            push(@{$inout_bus{$bus_type}}, $pin );
                        }
                    }
                    else {
                        my @line =  split(/,/, $line);
                        foreach my $pin (@line) {
                            $pin=~s/\s+//;
                            push(@inout_pins, $pin);
                        }
                    }

                }

                last if($line=~/endmodule/);
            }

        }
    }
    $pins{'input'} = \@input_pins;
    $pins{'output'} = \@output_pins;
    $pins{'inout'} = \@inout_pins;
    $bus{'input'} = \%input_bus;
    $bus{'output'} = \%output_bus;
    $bus{'inout'} = \%inout_bus;
    $topLevelDB{'pins'} = \%pins;
    $topLevelDB{'bus'} = \%bus;
    $topLevelDB{'design'} = \%design;
    return \%topLevelDB;
}