CoveragePkg User Guide
CoveragePkg User Guide
Using CoveragePkg
By
Jim Lewis
http://www.SynthWorks.com
Table of Contents
1 Overview ................................................................................................................. 4
2 Getting CoveragePkg............................................................................................... 5
3 What is Functional Coverage and why do I need it?.................................................... 6
3.1 What is Functional Coverage? ................................................................................... 6
3.2 Why can't I just use code coverage? ......................................................................... 6
3.3 Test Done = Test Plan Executed and All Code Executed ............................................ 7
3.4 Why You Need Functional Coverage, even with Directed Testing................................. 7
3.5 What is "Coverage" then? ......................................................................................... 8
4 Writing Functional Coverage Using CoveragePkg ........................................................ 8
4.1 Item (Point) Coverage done Manually........................................................................ 8
4.2 Basic Item (Point) Coverage with CoveragePkg .......................................................... 9
4.3 Cross Coverage with CoveragePkg .......................................................................... 11
5 Intelligent Coverage is 5X or more faster than constrained random ........................... 13
5.1 Constrained Random Repeats Test Cases ................................................................ 13
5.2 Intelligent Coverage ............................................................................................... 14
5.3 Intelligent Coverage reduces your work ................................................................... 15
6 Flexibility and Capability ......................................................................................... 16
7 Declaration of the Coverage Object - CovPType ....................................................... 17
8 Basic Bin Description .............................................................................................. 18
8.1 Basic Type for Coverage Bins .................................................................................. 18
8.2 Creating Count Bins - GenBin .................................................................................. 18
8.3 Creating Illegal and Ignore Bins - IllegalBin and IgnoreBin........................................ 19
8.4 Predefined Bins - ALL_BIN, …, ALL_ILLEGAL, ZERO_BIN, ONE_BIN........................... 19
8.5 Combining Bins Using Concatenation - & ................................................................. 20
9 Data Structure Construction.................................................................................... 20
9.1 Item (Point) Bins - AddBins..................................................................................... 20
9.2 Cross Coverage Bins - AddCross.............................................................................. 20
9.3 Controlling Reporting for Illegal Bins - SetIllegalMode............................................... 21
9.4 Bin Size Optimization - SetBinSize ........................................................................... 21
10 Accumulating Coverage - ICover ............................................................................. 21
11 Basic Randomization .............................................................................................. 22
11.1 Randomly generating a value within a bin - RandCovPoint ........................................ 22
11.2 Randomly selecting a coverage bin - RandCovBinVal ................................................ 22
11.3 Randomization, Illegal, and Ignore Bins................................................................... 22
12 Coverage Model Statistics ....................................................................................... 23
12.1 Model Covered - Testing Done - IsCovered .............................................................. 23
12.2 Model Initialized - IsInitialized................................................................................ 23
1 Overview
Writing functional coverage is concise and flexible. The basics of writing functional
coverage using coverage package are covered in the section, "Writing Functional
Coverage using CoveragePkg."
One important, unique feature is the "Intelligent Coverage" that is built directly into the
coverage data structure. This capability allows us to randomly select a hole in the
current functional coverage to pass to the stimulus generation process. Using
"Intelligent Coverage" helps minimize the number of test cases generated to achieve
complete coverage - resulting in fewer simulation cycles and a higher velocity of
verification. More details are provided in the section, "Intelligent Coverage is 5X or
more faster than constrained random testing."
remaining part of the document. Note the package contains additional undocumented
methods that are either experimental features or artifacts from older use models that
are maintained for backward compatibility. Use these at your own risk as they may be
removed from future revisions.
This documentation is not a substitute for a great training class. CoveragePkg was
developed and is maintained by Jim Lewis of SynthWorks. It evolved from
methodology and packages developed for SynthWorks' VHDL Testbenches and
Verification class. This class includes additional packages that are not yet part of
OSVVM. Please support our effort in supporting OSVVM by purchasing your VHDL
training from SynthWorks.
All CoveragePkg features are supported now by many simulators. The only required
features are protected types (VHDL-2002) and integer_vector (in package std.standard
in VHDL-2008). In addition, since they are open source, the packages are free
(download and usage) and will be updated on a regular basis.
2 Getting CoveragePkg
CoveragePkg is released under the Perl Artistic open source license. It is free (both to
download and use - there are no license fees). You can download it from
http://www.synthworks.com/downloads. It will be updated from time to time.
Currently there are numerous planned revisions.
If you add features to the package, please donate them back under the same license as
candidates to be added to the standard version of the package package. If you need
features, be sure to contact us. I blog about the packages at
http://www.synthworks.com/blog. We also support a user community and blogs
through http://www.osvvm.org.
The STANDARD revision of this package requires VHDL-2008. It will work with a VHDL-
2002 compliant simulator by uncommenting the VHDL-2008 compatibility packages.
Functional coverage that examines the values within a single object is called either
point (SystemVerilog) or item ('e') coverage. I prefer the term item coverage since
point can also be a single value within a particular bin. One relationship we might look
at is different transfer sizes across a packet based bus. For example, the test plan may
require that transfer sizes with the following size or range of sizes be observed: 1, 2, 3,
4 to 127, 128 to 252, 253, 254, or 255.
Functional coverage that examines the relationships between different objects is called
cross coverage. An example of this would be examining whether an ALU has done all
of its supported operations with every different input pair of registers.
Code coverage cannot detect conditions that are not in the code. For example, in the
packet bus item coverage example discussed above, code coverage cannot determine
that the required values or ranges have occurred - unless the code contains expressions
to test for each of these sizes. Instead, we need to write functional coverage.
In the ALU cross coverage example above, code coverage cannot determine whether
particular register pairs have been used together, unless the code is written this way.
Generally each input to the ALU is selected independently of the other. Again, we need
to write functional coverage.
Code coverage on a partially implemented design can reach 100%. It cannot detect
missing features (oops forgot to implement one of the timers) and many boundary
conditions (in particular those that span more than one block). Hence, code coverage
cannot be used exclusively to indicate we are done testing.
3.3 Test Done = Test Plan Executed and All Code Executed
To know testing is done, we need to know that both the test plan is executed and all of
the code has been executed. Is 100% functional coverage enough?
Unfortunately a test can reach 100% functional coverage without reaching 100% code
coverage. This indicates the design contains untested code that is not part of the test
plan. This can come from an incomplete test plan, extra undocumented features in the
design, or case statement others branches that do not get exercised in normal
hardware operation. Untested features need to either be tested or removed.
As a result, even with 100% functional coverage it is still a good idea to use code
coverage as a fail-safe for the test plan.
3.4 Why You Need Functional Coverage, even with Directed Testing
You might think, "I have written a directed test for each item in the test plan, I am
done right?"
As design size grows, the complexity increases. A test that completely validates one
version of the design, may not validate the design after revisions. For example, if the
size a of FIFO increases, the test may no longer provide enough stimulus values to fill it
completely and cause a FIFO Full condition. If new features are added, a test may
need to change its configuration register values to enable the appropriate mode.
Without functional coverage, you are assuming your directed, algorithmic, file based, or
constrained random test actually hits the conditions in your test plan.
Don't forget the engineers creed, "In the divine we trust, all others need to show
supporting data." Whether you are using directed, algorithmic, file based, or
constrained random test methods, functional coverage provides your supporting data.
Functional coverage can be written using any code. CoveragePkg and language syntax
are solely intended to simplify this effort. In this section, we will first look at
implementing functional coverage manually (without CoveragePkg). Then we will look
at using CoveragePkg to capture item and cross coverage.
In a packet based transfer (such as across an ethernet port), most interesting things
happen when the transfer size is at or near either the minimum or maximum sized
transfers. It is important that a number of medium sized transfers occur, but we do
not need to see as many of them. For this example, let's assume that we are
interested in tracking transfers that are either the following size or range: 1, 2, 3, 4 to
127, 128 to 252, 253, 254, or 255. The sizes we look for are specified by our test plan.
We also must decide when to capture (aka sample) the coverage. In the following
code, we use the rising edge of clock where the flag TransactionDone is 1.
Any coverage can be written this way. However, this is too much work and too specific
to the problem at hand. We could make a small improvement to this by capturing the
code in a procedure. This would help with local reuse, but there are still no built-in
operations to determine when testing is done, to print reports, or to save results and
the data structure to a file.
The basic steps to model functional coverage are declare the coverage object, create
the coverage model, accumulate coverage, interact with the coverage data structure,
and report the coverage.
Coverage is modeled using a data structure stored inside of a coverage object. The
coverage object is created by declaring a shared variable of type CovPType, such as
CovBin1 shown below.
architecture Test1 of tb is
shared variable CovBin1 : CovPType ;
begin
Internal to the data structure, each bin in an item coverage model is represented by a
minimum and maximum value (effectively a range). Bins that have only a single value,
such as 1 are represented by the pair 1, 1 (meaning 1 to 1). Internally, the minimum
and maximum values are stored in a record with other bin information.
The coverage model is constructed by using the method AddBins and the function
GenBin. The function GenBin transforms a bin descriptor into a set of bins. The
method AddBins inserts these bins into the data structure internal to the protected
type. Note that when calling a method of a protected type, such as AddBins shown
below, the method name is prefixed by the protected type variable name, CovBin1.
The version of GenBin shown below has three parameters: min value, max value, and
number of bins. The call, GenBin(1,3,3), breaks the range 1 to 3 into the 3 separate
bins with ranges 1 to 1, 2 to 2, 3 to 3.
TestProc : process
begin
-- min, max, #bins
CovBin1.AddBins(GenBin(1, 3, 3)); -- bins 1 to 1, 2 to 2, 3 to 3
. . .
Additional calls to AddBins appends additional bins to the data structure. As a result,
the call, GenBin(4, 252, 2), appends two bins with the ranges 4 to 127 and 128 to 252
respectively to the coverage model.
Since creating one bin for each value within a range is common, there is also a version
of GenBin that has two parameters: min value and max value which creates one bin per
value. As a result, the call GenBin(253, 255) appends three bins with the ranges 253 to
253, 254 to 254, and 255 to 255.
Coverage is accumulated using the method ICover. Since coverage is collected using
sequential code, either clock based sampling (shown below) or transaction based
sampling (by calling ICover after a transaction completes - shown in later examples)
can be used.
-- Accumulating coverage using clock based sampling
loop
wait until rising_edge(Clk) and nReset = '1' ;
CovBin1.ICover(to_integer(unsigned(RxData_slv))) ;
end loop ;
end process ;
A test is done when functional coverage reaches 100%. The method IsCovered returns
true when all the count bins in the coverage data structure have reached their goal.
The following code shows the previous loop modified so that it exits when coverage
reaches 100%.
Finally, when the test is done, the method WriteBin is used to print the coverage results
to OUTPUT (the transcript window when running interactively).
-- Print Results
CovBin1.WriteBin ;
architecture Test1 of tb is
shared variable CovBin1 : CovPType ; -- Coverage Object
begin
TestProc : process
begin
-- Model the coverage
CovBin1.AddBins(GenBin( 1, 3 )) ;
CovBin1.AddBins(GenBin( 4, 252, 2)) ;
CovBin1.AddBins(GenBin(253, 255 )) ;
-- Accumulating Coverage
-- clock based sampling
while not CovBin1.IsCovered loop
wait until rising_edge(Clk) and nReset = '1' ;
CovBin1.ICover(to_integer(RxData_slv)) ;
end loop ;
-- Print Results
CovBin1.WriteBin ;
wait ;
end process ;
Note that when modeling coverage, we primarily work with integer values. All of the
inputs to GenBin and ICover are integers; WriteBin reports results in terms of integers.
This is similar to what other verification languages do.
D0 Q0 Mux
... ... SRC1 ... 8:1
D7 Q7
Mux
SRC2 ... 8:1
Cross coverage for SRC1 crossed SRC2 with can be visualized as a matrix of 8 x 8 bins.
SRC2
R0 R1 R2 R3 R4 R5 R6 R7
R0
R1
S R2
R R3
C R4
1 R5
R6
R7
The steps for modeling cross coverage are the same steps used for item coverage:
declare, model, accumulate, interact, and report. Collecting cross coverage only differs
in the model and accumulate steps.
Cross coverage is modeled using the method AddCross and two or more calls to
function GenBin. AddCross creates the cross product of the set of bins (created by
GenBin) on its inputs. The code below shows the call to create the 8 x 8 cross. Each
call to GenBin(0,7) creates the 8 bins: 0, 1, 2, 3, 4, 5, 6, 7. The AddCross creates the
64 bins cross product of these bins. This can be visualized as the matrix shown
previously.
The accumulate step now requires a value for SRC1 and SRC2. The overloaded ICover
method for cross coverage uses an integer_vector input. This allows it to accept a
value for each item in the cross. The extra set of parentheses around Src1 and Src2 in
the call to ICover below designate that it is a integer_vector.
The code below shows the entire example. The shared variable, ACov, declares the
coverage object. AddCross creates the cross coverage model. IsCovered is used to
determine when all items in the coverage model have been covered. Each register is
selected using uniform randomization (RandInt). The transaction procedure, DoAluOp,
applies the stimulus. ICover accumulates the coverage. WriteBin reports the
coverage.
architecture Test2 of tb is
shared variable ACov : CovPType ; -- Declare
begin
TestProc : process
variable RV : RandomPType ;
variable Src1, Src2 : integer ;
begin
-- create coverage model
ACov.AddCross( GenBin(0,7), GenBin(0,7) ); -- Model
ACov.WriteBin ; -- Report
EndStatus(. . . ) ;
end process ;
The problem with constrained random testbenches is that they repeat some test cases
before generating all test cases. In general to generate N cases, it takes "N * log N"
randomizations. The "log N" represents repeated test cases and significantly adds to
simulation run times. Ideally we would like to run only N test cases.
Running the previous ALU testbench, we get the following coverage matrix when the
code completes. Note that some case were generated 10 time before all were done at
least 1 time. It took 315 randomizations to generate all 64 unique pairs. This is
slightly less than 5X more iterations than the 64 in the ideal case. This correlates well
with theory as 315 ≈ 64 * log(64). By changing the seed value, the exact number of
randomizations may increase or decrease but this would be a silly way to try to reduce
the number of iterations a test runs.
SRC2
R0 R1 R2 R3 R4 R5 R6 R7
R0 6 6 9 1 4 6 6 5
R1 3 4 3 6 9 5 5 4
S
R R2 4 1 5 3 2 3 4 6
C R3 5 5 6 3 3 4 4 6
1 R4 4 5 5 10 9 10 7 7
R5 4 6 3 6 3 5 3 8
R6 3 6 3 4 7 1 4 6
R7 7 3 4 6 6 5 4 5
Let's return to the ALU example. The Intelligent Coverage methodology starts by
writing functional coverage. We did this in the previous example too. Next preliminary
stimulus is generated by randomizing using the functional coverage model. In this
example, we will replace the uniform randomization with RandInt with a call to
RandCovPoint (one of the Intelligent Coverage randomization methods). This is shown
below. In this case, Src1 and Src2 are used directly in the test, so we are done.
architecture Test3 of tb is
shared variable ACov : CovPType ; -- Declare
begin
TestProc : process
variable RV : RandomPType ;
variable Src1, Src2 : integer ;
begin
-- create coverage model
ACov.AddCross( GenBin(0,7), GenBin(0,7) ); -- Model
ACov.WriteBin ; -- Report
EndStatus(. . . ) ;
end process ;
The process is not always this easy. Sometimes the value out of RandCovPoint will
need to be further shaped by the stimulus generation process.
The Intelligent Coverage methodology works now and works with your current
testbench approach. You can adopt this methodology incrementally. Add functional
coverage today to make sure you are executing all of your test plan. For the tests that
need help, use the Intelligent Coverage.
With Intelligent coverage, we focus on writing high fidelity coverage models. The
constrained random step is reduced to a refinement step and only needs to focus on
things that are not already shaped by the coverage. Hence, Intelligent Coverage
methodology reduces (or eliminates) the work needed to generate test constraints.
The added flexibility and capability comes from writing the model incrementally using
any sequential code (if, loop). As long as the entire model is captured before we start
collecting coverage, we can use as many calls to AddBins or AddCross as needed. As a
result, conditionally capturing coverage based on a generic is straight forward. In
addition, algorithms that iterate using a loop are no more trouble than writing the code.
Additional flexibility and capability comes from being able to give each bin within a
coverage model a different coverage goal. A coverage goal specifies the number of
times a value from a particular bin needs to be observed before it is considered
covered. The Intelligent Coverage randomization by default will use these coverage
goals as randomization weights.
To demonstrate this flexibility, let's consider a contrived example based on the ALU. In
this example, each SRC1 crossed with any SRC2 has a different coverage goal. In
addition, it is an error if SRC1 and SRC2 are equal. The coverage goal for each bin is
specified in the table below.
Coverage Goal Src1 Src2
2 0 1, 2, 3, 4, 5, 6, 7
3 1 0, 2, 3, 4, 5, 6, 7
4 2 0, 1, 3, 4, 5, 6, 7
5 3 0, 1, 2, 4, 5, 6, 7
5 4 0, 1, 2, 3, 5, 6, 7
4 5 0, 1, 2, 3, 4, 6, 7
3 6 0, 1, 2, 3, 4, 5, 7
2 7 0, 1, 2, 3, 4, 5, 6
Illegal Src1 = Src2
To model the above functional coverage, we use a separate call for each different
coverage goal. The function, IllegalBin, is used to mark the bins with Src1 = Src2
illegal. This is shown below.
architecture Test4 of tb is
shared variable ACov : CovPType ; -- Declare Cov Object
begin
TestProc : process
variable Src1, Src2 : integer ;
begin
-- Capture coverage model
ACov.AddCross( 2, GenBin (0), IllegalBin(0) & GenBin(1,7)) ;
ACov.AddCross( 3, GenBin (1), GenBin(0) & IllegalBin(1) & GenBin(2,7)) ;
ACov.AddCross( 4, GenBin (2), GenBin(0,1) & IllegalBin(2) & GenBin(3,7)) ;
ACov.AddCross( 5, GenBin (3), GenBin(0,2) & IllegalBin(3) & GenBin(4,7)) ;
ACov.AddCross( 5, GenBin (4), GenBin(0,3) & IllegalBin(4) & GenBin(5,7)) ;
ACov.AddCross( 4, GenBin (5), GenBin(0,4) & IllegalBin(5) & GenBin(6,7)) ;
ACov.AddCross( 3, GenBin (6), GenBin(0,5) & IllegalBin(6) & GenBin(7)) ;
ACov.AddCross( 2, GenBin (7), GenBin(0,6) & IllegalBin(7) ) ;
ACov.WriteBin ; -- Report
EndStatus(. . . ) ;
end process ;
Note that all of the methods we have discussed have all of the methods have additional
overloading that is documented in the CoveragePkg users guide
(CoveragePkg_user_guide.pdf).
The shared variable declaration for the coverage object is commonly put in the
architecture of the design as shown below.
architecture Test3 of tb is
shared variable ACov : CovPType ; -- Declare Cov Object
begin
The functions GenBin, IllegalBin, and IgnoreBin are used to create bins of type
CovBinType. These bins are used as inputs to the methods, AddBins and AddCross,
that create the coverage data structure. Using these functions replaces the need to
know the details of type CovBinType.
The version of GenBin shown below has three parameters: min value, max value, and
number of bins. The call, GenBin(1, 3, 3), breaks the range 1 to 3 into the 3 separate
bins with ranges1 to 1, 2 to 2, 3 to 3.
-- min, max, #bins
CovBin1.AddBins(GenBin(1, 3, 3)); -- bins 1 to 1, 2 to 2, 3 to 3
If there are less values (between max and min) than bins, then only "max - min + 1"
bins will be created. As a result, the call GenBin(1,3,20), will still create the three bins:
1 to 1, 2 to 2 and 3 to 3.
CovBin2.AddBins( GenBin(1, 3, 20) ) ; -- bins 1 to 1, 2 to 2, and 3 to 3
If there are more values (between max and min) than bins and the range does not
divide evenly among bins, then each bin with have on average (max - min + 1)/bins.
Later bins will have one more value than earlier bins. The exact formula used is
(number of values remaining)/(number of bins remaining). As a result, the call
GenBin(1, 14, 4) creates four bins with ranges 1 to 3, 4 to 6, 7 to 10, and 11 to 14.
CovBin2.AddBins( GenBin(1, 14, 4) ) ; -- 1 to 3, 4 to 6, 7 to 10, 11 to 14
Since creating one bin per value in the range is common, there is also a version of
GenBin that has two parameters: min value and max value which creates one bin per
value. As a result, the first call to AddBins/GenBin can be shortened to the following.
-- min, max
CovBin1.AddBins(GenBin(1, 3)); -- bins 1 to 1, 2 to 2, and 3 to 3
GenBin can also be called with one parameter, the one value that is contained in the
bin. Hence the call, GenBin(5) creates a single bin with the range 5 to 5. The following
two calls are equivalent.
CovBin3.AddBins( GenBin(5) ) ;
CovBin3.AddBins( GenBin(5,5,1) ) ; -- equivalent call
The functions IllegalBin and IgnoreBin are used to create illegal and ignore bins. One
version of IllegalBin and IgnoreBin has three parameters: min value, max value, and
number of bins (just like GenBin).
-- min, max, NumBin
IllegalBin( 1, 9, 3) -- creates 3 illegal bins: 1-3, 4-6, 7-9
IllegalBin( 1, 9, 1) -- creates one illegal bin with range 1-9
IgnoreBin ( 1, 3, 3) -- creates 3 ignore bins: 1, 2, 3
There are also two parameter versions of IgnoreBin and IllegalBin that creates a single
bin. Some examples of this are illustrated below. While this is different from the action
of the two parameter GenBin calls, it matches the common behavior of creating illegal
and ignore bins.
-- min, max
IllegalBin( 1, 9) -- creates one illegal bin with range 1-9
IgnoreBin ( 1, 3) -- creates one ignore bin with range 1-3
There are also one parameter versions of IgnoreBin and IllegalBin that creates a single
bin with a single value. Some examples of this are illustrated below.
-- AVal
IllegalBin( 5 ) -- creates one illegal bin with range 5-5
IgnoreBin ( 7 ) -- creates one ignore bin with range 7-7
Calls to GenBin, IllegalBin, and IgnoreBin can also be combined. As a result the
following creates the four separate legal bins (1, 2, 5, and 6), a single ignore bin (3 to
4), and everything else falls into an illegal bin.
CovBin2.AddBins( GenBin(1,2) & IgnoreBin(3,4) & GenBin(5,6) & ALL_ILLEGAL ) ;
The coverage model data structure is created using the methods AddBins and
AddCross.
The method ICover is used to accumulate coverage. For item (point) coverage, ICover
accepts an integer value. For cross coverage, ICover accepts an integer_vector. The
procedure interfaces are shown below. Since the coverage accumulation is written
procedurally, ICover will support either clock based sampling or transaction based
sampling (examples of both shown previously).
procedure ICover( CovPoint : in integer ) ;
procedure ICover( CovPoint : in integer_vector ) ;
Since the inputs must be either type integer or integer_vector, conversions must be
used. To convert from std_logic_vector to integer, numeric_std_unsigned and
numeric_std provide the following conversions.
CovBin3.ICover( to_integer(RxData_slv) ) ; -- using numeric_std_unsigned (2008)
CovBin3.ICover( to_integer(unsigned(RxData_slv)) ) ; -- using numeric_std
Since the language does not do introspection of aggregate values when determining
the type of an expression, the boolean vector expression needs to be constructed using
concatenation (as shown above) rather than aggregates (as shown below).
--! CrossBin.ICover( to_integer_vector( ((Empty='1'),(Rdy='1')) )); -- ambiguous
11 Basic Randomization
13 Reporting Coverage
Coverage results can be written as either all the bins (WriteBin) or just the bins that
have not reached their coverage goal (WriteCovHoles). These results can either be
printed to the std_output (file OUTPUT) or to a designated file. In addition, one or
more lines of heading (SetMessage) may be printed before the results.
If the internal randomization seed has not yet been initialized, the first call to
SetMessage will initialize the seed using the string value.
If the internal randomization seed has not yet been initialized, the first call to SetName
will initialize the seed using the string value.
If SetName is not set, the first word in the first message (SetMessage) will be used
instead.
Note that each call to AddBins and AddCross supports a Name as the first parameter.
WriteBin has parameters to control printing of each of these fields. The complete
interface to WriteBin is:
procedure WriteBin (
WritePassFail : CovOptionsType := COV_OPT_DEFAULT ;
WriteBinInfo : CovOptionsType := COV_OPT_DEFAULT ;
WriteCount : CovOptionsType := COV_OPT_DEFAULT ;
WriteAnyIllegal : CovOptionsType := COV_OPT_DEFAULT ;
WritePrefix : string := "" ;
PassName : string := "" ;
FailName : string := ""
) ;
The type CovOptionsType is used to specify values for many of these parameters. It is
defined as follows.
type CovOptionsType is (COV_OPT_DEFAULT, DISABLED, ENABLED) ;
The default values for parameters to Write bins is initially as shown in the table below.
The reason they are not specified directly on the WriteBin interface is that they are
[local/global] settings that can be changed by the procedure set SetReportOptions.
For details, see next section.
WritePassFail DISABLED
WriteBinInfo ENABLED
WriteCount ENABLED
WriteAnyIllegal DISABLED
WritePrefix "%% "
PassName "PASSED"
FailName "FAILED"
When all parameters are enabled, the report will print with the following format.
%% State0 PASSED Bin:(0) Count = 1 AtLeast = 1
%% State1 PASSED Bin:(1) Count = 1 AtLeast = 1
%% State2 FAILED Bin:(2) Count = 0 AtLeast = 1
%% State3 FAILED Bin:(3) Count = 0 AtLeast = 1
The "%% " is the default prefix. It can be changed by specifying a value for
WritePrefix. The "State0", "State1", … are the names of the bins and if present always
print. Next is the PassFail message. It will print "PASSED" if the count is greater than
or equal to the goal (AtLeast value), otherwise, it prints "FAILED". The PassFail
message is enabled using the WritePassFail field. The value printed when it passes or
fails is controlled by the PassName and FailName fields. Printing of bin information is
redundant when a bin is named. This information can be disabled suing the
WriteBinInfo field. Printing of the Count (current coverage) and AtLeast (coverage
goal) can be disabled with the WriteCount field. Nominally illegal bins are only printed
when the have failed (something landed in that bin). The parameter, WriteAnyIllegal,
can be used to enable printing of all illegal bins (including the ones with no values and
hence pass).
In the current version, SetReportOptions sets defaults for the current coverage model.
In the next revision, it is expected to migrate this feature to a global feature that sets
defaults for all coverage models. At that time, it may also be supported by a settings
file (such as osvvm.ini).
Keep in mind, this is an experimental feature and may change in the future. Feel free
to try it out and comment about what you need.
If every WriteBin or WriteCovHoles writes to the same file, then FileOpenWriteBin can
be use to open a file internal to the coverage model. The declaration of
FileOpenWriteBin is shown below. When a file is open and WriteBin or WriteCovHoles
is called without a file specification, then the opened file is used rather than OUTPUT.
procedure FileOpenWriteBin (FileName : string; OpenKind : File_Open_Kind ) ;
If several different files are used with WriteBin or WriteCovHoles, then the file name
and open kind can be specified as a parameters. The following overloading is provided.
procedure WriteBin (
FileName : string;
OpenKind : File_Open_Kind := APPEND_MODE ;
WritePassFail : CovOptionsType := COV_OPT_DEFAULT ;
WriteBinInfo : CovOptionsType := COV_OPT_DEFAULT ;
WriteCount : CovOptionsType := COV_OPT_DEFAULT ;
WriteAnyIllegal : CovOptionsType := COV_OPT_DEFAULT ;
WritePrefix : string := "" ;
PassName : string := "" ;
Note, WRITE_MODE initializes and opens a file, so make sure to only use it on the first
write to the file. For all subsequent writes to the same file use APPEND_MODE (hence
it is the default). The following shows a call to WriteBin followed by a call to
WriteCovHoles.
-- FileName, OpenKind
CovBin1.WriteBin ("Test1.txt", WRITE_MODE);
CovBin1.WriteCovHoles ("Test1.txt", APPEND_MODE);
Coverage goals and randomization weights are an important part of the Intelligent
Coverage methodology. A coverage goal specifies how many times a value must land
in a bin before the bin is considered covered. A randomization weight determines the
relative number of times a bin will be selected in randomization. In VHDL, each bin
within a coverage model may have a different coverage goal and randomization weight.
Up to this point, every coverage bin has a coverage goal of 1 and that value has been
used as the randomization weight. However, some tests require coverage goal of
other than one and some tests require a randomization weight that is different from the
coverage goal. This section addresses how to set coverage goals and randomization
weights using overloaded methods and functions in CoveragePkg.
The GenBin function also has an AtLeast parameter. Its declaration is shown below.
function GenBin(AtLeast, Min, Max, NumBin : integer ) return CovBinType ;
If a bin is an ignore or illegal bin, then the coverage goal is set to 0. If a bin is a count
bin and a coverage goal is specified in more than one place, then the largest specified
value is used. If a bin is a count bin and no coverage goal is specified then the
coverage goal is set to 1.
The GenBin function also has an Weight parameter. Its declaration is shown below.
function GenBin(AtLeast, Weight, Min, Max, NumBin : in integer)
return CovBinType ;
If a bin is an ignore or illegal bin, then the bin weight is set to 0. If a bin is a count bin
and a bin weight is specified in more place, then the largest specified value is used. If
a bin is a count bin and no bin weight is specified then the coverage goal is set to 1.
Likewise for the coverage goal.
15 Coverage Targets
For some tests, the AtLeast parameters will be used to set an initial coverage
distribution. Later it may be desirable to use the same coverage distribution, but run it
for much longer. Use of a coverage target allows the coverage goal to be scaled
(increased or decreased) without having to change anything else in the coverage
model. Hence, the effective coverage goal for a bin is the product of bin's AtLeast
least value and the coverage model's coverage target value (specifically, AtLeast *
CovTarget / 100.0).
The coverage target is intended to scale the run time of a test without having to
change a bin's AtLeast values. CovTarget is set to 100.0 initially. Setting the coverage
target to 1000.0 will increase the run time 10X. Setting the coverage target to 50.0 will
decrease the run time by 2X.
The versions of the following methods that do not have a PercentCov parameter use
the CovTarget value: RandCovPoint, RandCovBinVal, IsCovered, and WriteCovHoles.
Currently bin merging also merges count bins when they have identical bin values.
Merging of count bins is expensive. Since this feature is correctly handled by LastIndex,
it may be removed in the future. If you need count bins to be merged, please contact
the package author.
This may be addressed in a future version. For now it is up to the user understand
overlap and to avoid this.
Caution: this experimental feature may be removed from future versions if it impacts
run time. If you have need for COUNT_ALL, please contact the package author.
Intelligent coverage uses pseudo random number generation as its basis. As such, for
a given randomization seed value it will generate the same sequence of numbers every
time a simulation is run. This is important as it means that when a bug is found and
fixed, the fix can be validated since the same test sequence that caused the bug will be
generated.
On the other hand, it also means that if a design has two identical interfaces and the
testbench uses the two identical coverage models to generate tests that they will both
see the same test sequence. This is not desirable since it is unlikely to generate
interesting interactions between the two interfaces. As a result, it is desirable that each
coverage model is given a different initial seed value. This is simple to do.
The InitSeed method initializes a coverage model's internal randomization seed. The
following example shows the method overloading and an example call. One easy way
to generate a unique seed value for each coverage bin is to use the string value
generated by 'path_name applied to the coverage object as shown. Note that the
method SetMessage will also call InitSeed with its parameter if the seed is not already
set.
The methods GetSeed and SetSeed are intended for saving and restoring the seeds.
In this case the seed value is of type RandomSeedType, which is defined in
RandomBasePkg. RandomBasePkg also defines procedures for reading and writing
RandomSeedType values (see RandomPkg Users Guide for details).
procedure SetSeed (RandomSeedIn : RandomSeedType ) ;
impure function GetSeed return RandomSeedType ;
Note that the time it takes to achieve coverage closure with open loop randomization
methods, such as SystemVerilog's constrained random, may depend heavily on the
initial seed value. Hence, within the SystemVerilog community some may try out
different seeds when running simulations just to see if they can improve run times.
This is not necessary with OSVVM's Intelligent Coverage methodology since it only
selects from coverage holes.
The function GetNumBins returns the number of bins in the coverage model. Bin
values are numbered from 1 to NumBins.
The functions GetMinIndex and GetMaxIndex return the index of the first bin with the
minimum and maximum percent coverage of a bin. The functions GetMinCov and
GetMaxCov return the minimum and maximum percent coverage of a bin. The
functions GetMinCount and GetMaxCount return the minimum and maximum count in a
bin.
The function GetErrorCount sums up the count in each of the error bins and returns the
resulting value. Generally GetErrorCount is called at the end of a testbench for
coverage models that have bins marked as illegal.
TestErrCount := CovBin1.GetErrorCount + (Other_Error_Sources) ;
The function GetPoint returns a random point within the addressed bin (BinIndex). The
functions GetMinPoint and GetMaxPoint return a random point within the first bin with
minimum and maximum percent coverage.
The function GetBinVal returns the bin value of the addressed bin (BinIndex). The
functions GetMinBinVal and GetMaxBinVal return the bin value of the first bin with
minimum and maximum percent coverage.
The function CountCovHoles returns the number of holes that are below the PercentCov
parameter. CountCovHoles without a PercentCov parameter returns the number of
holes that are below the CovTarget value.
-- PercentCov
NumHoles := CovBin1.CountCovHoles( 100.0 ) ;
GetHoleBinVal gets the ReqHoleNum bin with a coverage value less than the
PercentCov value. The following call to GetHoleBinVal gets the 5th bin that has less
than 100% coverage. Note that ReqHoleNum must be between 1 and CountCovHoles.
GetHoleBinVal without a PercentCov parameter uses CovTarget in its place.
-- ReqHoleNum, PercentCov
TestData := CovBin1.GetHoleBinVal( 5, 100.0 ) ;
A coverage model can be written and read using WriteCovDb and ReadCovDb. Using
these allows results to be accumulated across multiple tests, and hence, things like test
configurations can be covered and randomized. The method declarations are shown
below. Like WriteBin file parameters cannot be used, so WriteCovDb and ReadCovDb
use parameters that specify the file name as a string and the file open mode.
procedure WriteCovDb (FileName : in string;
OpenKind : File_Open_Kind := WRITE_MODE ) ;
procedure ReadCovDb (FileName : string ; Merge : boolean := FALSE) ;
WriteCovDb saves the coverage model and internal variables into a file. The following
shows a call to WriteCovDb. Generally WriteCovDb is called once per test. As a result,
WRITE_MODE is the default.
-- FileName, OpenKind
CovBin1.WriteCovDb( "CovDb.txt", WRITE_MODE ) ;
The procedure method ReadCovDb reads the coverage model and internal variables
from a file. If the optional Merge parameter is set to TRUE, the values read will be
merged with the current coverage model. The following shows a call to ReadCovDb.
-- FileName
CovBin1.ReadCovDb( "CovDb.txt", TRUE );
The procedure SetCovZero sets all the coverage counts in a coverage bin to zero. This
allows the counts to be set to zero after reading in a coverage database. A simple call
to it is shown below.
CovBin1.SetCovZero ; -- set all counts to 0
The procedure Deallocate deallocates the entire database structure and sets the
internal variables back to their defaults.
CovBin1.Deallocate ;
Constants are used for two purposes. The first is to create a short hand name for an
item (point) bin (normal constant stuff) and then use that name later in composing the
coverage model. The second is to create the entire coverage model in the constant to
facilitate reuse of the model.
One step of refinement is to create an item bin constant for the register addresses,
such as REG_ADDR shown below. The type of REG_ADDR is CovBinType. Since
constants can extract their range based on the object assigned to them, it is easiest to
leave CovBinType unconstrained.
constant REG_ADDR : CovBinType := GenBin(0, 7) ;
Once created the constant can be used in for further composition, such as shown
below. Just like normal constants, this increases both the readability and
maintainability of the code.
ACov.AddCross(REG_ADDR, REG_ADDR); -- Model
Since each element in an item bin may require different coverage goals or weights,
additional overloading of GenBin were added. These are shown below.
function GenBin(AtLeast, Weight, Min, Max, NumBin : integer ) return CovBinType ;
function GenBin(AtLeast, Min, Max, NumBin : integer ) return CovBinType ;
The function GenCross is used to generate these cross products. We need a separate
overloaded function for each of these types. The interface that generates
CovMatrix2Type and CovMatrix9Type are shown below.
function GenCross( -- cross 2 item bins - see method AddCross
constant AtLeast : integer ;
constant Weight : integer ;
constant Bin1, Bin2 : in CovBinType
) return CovMatrix2Type ;
Now we can write our constant for our simple ALU coverage model.
constant ALU_COV_MODEL : CovMatrix2Type := GenCross(REG_ADDR, REG_ADDR);
When we want to add this to our coverage data structure, we need methods that
handle types CovMatrix2Type through CovMatrix9Type. This is handled by the
overloaded versions of AddBins shown below.
To create the coverage data structure for the simple ALU coverage model, call AddBins
as shown below.
ACov.AddBins( ALU_COV_MODEL ); -- Model
This capability is here mostly due to evolution of the package. Keep in mind, the intent
is to create readable and perhaps reusable coverage models.
ACov.WriteBin ; -- Report
EndStatus(. . . ) ;
end process ;
23 Reuse of Coverage
There are a couple of ways to reuse a coverage model. If the intent is to reuse and
accumulate coverage across tests, then the only way to accomplish this is to use
WriteCovDb and ReadCovDb. If the intent is to just reuse the coverage model itself,
then either a constant or a subprogram can be used. The calls to ICover generally are
simple enough that we do not try to abstract them.
The basic level of item (point) coverage that can be captured with CoveragePkg is
similar to when can be captured with IEEE 1647, 'e'. CoveragePkg and 'e' allow an
item bin to consist of either a single value or a single range. SystemVerilog extends
this to allow a value, a range, or a collection of values and ranges. While this
additional capability of SystemVerilog is interesting, it did not seem to offer any
compelling advantage that would justify the additional complexity required to specify it
to the coverage model.
For cross coverage, both SystemVerilog and 'e' focus on first capturing item coverage
and then doing a cross of the items. There is some capability to modify the bins
contents within the cross, but at best it is awkward. On the other hand, CoveragePkg
allows one to directly capture cross coverage, bin by bin and incrementally if necessary.
Helper functions are provided to simplify the process. This means for simple things,
such as making sure every register pair of an ALU is used, the coverage is captured in a
very concise syntax, however, when more complex things need to be done, such as
modeling the coverage for a CPU, the cross coverage can be captured on a line by line
basis.
26 Deprecated Methods
In the original design of the coverage feedback and randomization functions, there was
no coverage goal or weight. Instead, each bin has a weight of 1 and the coverage goal
is determined by the AtLeast parameter in the function calls. These functions are
shown below. In this implementation, all bins had the same coverage goal. Usage of
the AtLeast parameter has been subsumed by the real valued PercentCov parameter.
In addition, each bin now has the capability to have a different coverage goal and
weight. With different coverage goal values, PercentCov has replaced the AtLeast
parameter. The functionality of the AtLeast parameter has been subsumed by has
been subsumed by the PercentCov parameter. A PercentCov parameter of 200.0 is
equivalent to an AtLeast parameter of 2.
impure function GetMinCov return integer ;
impure function GetMaxCov return integer ;
impure function CountCovHoles ( AtLeast : integer ) return integer ;
impure function IsCovered ( AtLeast : integer ) return boolean ;
impure function GetHoleBinVal ( ReqHoleNum : integer := 1 ; AtLeast : integer )
return RangeArrayType ;
impure function RandCovBinVal ( AtLeast : in integer ) return RangeArrayType ;
impure function RandCovPoint (AtLeast : in integer ) return integer_vector ;
27 Future Work
29 About CoveragePkg
Please support our effort in supporting CoveragePkg and OSVVM by purchasing your
VHDL training from SynthWorks.
CoveragePkg is released under the Perl Artistic open source license. It is free (both to
download and use - there are no license fees). You can download it from
http://www.synthworks.com/downloads. It will be updated from time to time.
Currently there are numerous planned revisions.
If you add features to the package, please donate them back under the same license as
candidates to be added to the standard version of the package. If you need features,
be sure to contact us. I blog about the packages at http://www.synthworks.com/blog.
We also support a user community and blogs through http://www.osvvm.org.
Jim Lewis, the founder of SynthWorks, has twenty-eight years of design, teaching, and
problem solving experience. In addition to working as a Principal Trainer for
SynthWorks, Mr Lewis has done ASIC and FPGA design, custom model development,
and consulting.
Mr. Lewis is chair of the IEEE 1076 VHDL Working Group (VASG) and is the primary
developer of the Open Source VHDL Verification Methodology (OSVVM.org) packages.
Neither of these activities generate revenue. Please support our volunteer efforts by
buying your VHDL training from SynthWorks.
If you find bugs these packages or would like to request enhancements, you can reach
me at [email protected].
31 References
[1] Jim Lewis, VHDL Testbenches and Verification, student manual for SynthWorks'
class.
[2] Andrew Piziali, Functional Verification Coverage Measurement and Analysis, Kluwer
Academic Publishers 2004, ISBN 1-4020-8025-5
[3] IEEE Standard for System Verilog, 2005, IEEE, ISBN 0-7381-4811-3
[4] IEEE 1647, Standard for the Functional Verification Language 'e', 2006
While code coverage is generally a useful metric, there are some cases where it does
not accomplish what we want.
To help understand the issue, consider the following process. If SelA, SelB, and SelC all
are 1 when Clk rises, then all of the lines of code execute and the code coverage is
100%. However, only the assignment, "Y <= A" has an observable impact on the
output. The assignments, "Y <= C" and Y <= B" are not observable, and hence, are
not validated.
PrioritySel : process (Clk)
begin
if rising_edge(Clk) then
Y <= "00000000" ;
if (SelC = '1') then
Y <= C ;
end if ;
if (SelB = '1') then
Y <= B ;
end if ;
if (SelA = '1') then
Y <= A ;
end if ;
end if ;
end process ;
In combinational logic, this issue only becomes worse. If we change the above process
as shown below, then it runs due to any change on its inputs. It still has the issues
shown above. In addition, the process now runs and accumulates coverage based on
any signal change. Signals may change multiple times during a given clock period due
to differences in delays - either delta cycle delays (in RTL) or real propagation delays
(in gate simulations or from external models).
PrioritySel : process (SelA, SelB, SelC, A, B, C)
begin
Y <= "00000000" ;
if (SelC = '1') then
Y <= C ;
end if ;
if (SelB = '1') then
Y <= B ;
end if ;
if (SelA = '1') then
Y <= A ;
end if ;
end process ;
Since functional coverage depends on observing conditions in design, it may cover all of
the gaps. There are also additional tools that address this issue with code coverage.