DVCon 08 abstractBFM Final
DVCon 08 abstractBFM Final
This paper reviews this style of using virtual interfaces, and then presents in detail an
alternative approach in which the BFM is not implemented in the class-based SystemVerilog
testbench. Instead, a suitable generic BFM abstract base class is created. Virtual methods in
this base class provide access to all the BFM functionality that will be required by a
testbench, but no implementation is provided. Instead, the concrete BFM is implemented as a
class derived from this generic BFM, but whose code is embedded in the SystemVerilog
module or interface that will be instanced alongside the DUT to connect to its signals. We
show that this approach brings a number of practical and methodological benefits,
minimizing the testbench code's dependence on details of the DUT's connections and
providing a more intuitive separation of concerns at the lowest level of the testbench while
fitting neatly into an object-oriented testbench design style.
Finally this paper will consider how this style of BFM and the more conventional style based
on virtual interfaces interact with other SystemVerilog constructs, such as clocking blocks, to
further isolate the testbench from the design.
Figure 1: Trivial RTL DUT stimulated by a SV object
I. THE TWO KINGDOMS OF THE VERIFICATION WORLD
module dummy_RTL_module(input R);
For the current authors, and for the majority of working ...
verification engineers in our industry, a design-under-test or endmodule
device-under-test (DUT) is most likely to be coded in
Verilog or VHDL using the register-transfer level (RTL) of package stimulus_pkg;
class Stimgen;
abstraction. At this level, information about the current state task run();
of the design is carried on signals (nets or variables, in repeat (10)
Verilog) and the design advances from its current state to a #5 testbench_Top.R = 1'b0;
future state thanks to signal updates executed in response to #5 testbench_Top.R = 1'b1;
signal value changes. The design's topology is determined end
endtask
once and for all at the start of simulation, in a process endclass
generally known as elaboration of the design. endpackage
By contrast, the test environment that is used to exercise module stimulus_module;
a DUT in simulation may be constructed in a variety of import stimulus_pkg::*;
styles. However, recent developments in languages and tools initial begin
for verification have encouraged the adoption of object- Stimgen stimgen;
oriented programming (OOP) techniques for building the test stimgen = new;
stimgen.run();
environment. An environment built using OOP has a end
topology that is determined not in a distinct elaboration step, endmodule
but instead by the action of procedural software that
constructs verification objects and assembles them into a module testbench_Top;
complete testbench. Although it is common (and often very reg R;
dummy_RTL_module DUT(R);
convenient) for this testbench topology to be constructed stimulus_module tester();
early in the simulation, and to be left unmodified endmodule
thenceforward, it is certainly possible in principle for the
testbench topology to be modified dynamically during the While this mechanism clearly works, and is
course of a simulation run. straightforward, it fails to address a number of important
In consequence, we find that typical verification practical concerns for verification environment developers.
environments naturally fall into two sections: on the one Some of the most pressing of these concerns are described
hand the statically-instantiated invariant topology of RTL below.
DUT code and perhaps some support structures for the DUT;
and on the other hand, the dynamically-constructed and A. Synchronous Signal Timing Abstraction
flexible structure of the OOP testbench. The coding styles, In a typical modern design, large groups of signals are
design approaches and dominant concerns of these two likely to be synchronous to a clock (although, of course,
sections are so different that we are tempted to think of them there may be many such clock domains in a big design).
as separate kingdoms in the verification world. But they are Within each clock domain, all synchronous signals should be
kingdoms that must of necessity share a border. It is the driven and/or sampled with some fixed timing relationship
frontier-posts of that border that concern us in this paper. relative to the clock. It is straightforward, but tedious, to
manage these timing relationships using procedural code in
II. THE CHALLENGE OF CONNECTING OBJECTS TO RTL the testbench. Such timing clutters the procedural code and
SystemVerilog [1] offers object-oriented programming confuses two levels of abstraction that most verification
within a language that also fully supports all existing engineers prefer to keep distinct: the event-driven, signal-
constructs of the Verilog Hardware Description level abstraction of the RTL code, and the cycle-based
Language(HDL) [2]. Consequently, verification code written abstraction of the testbench in which timing is expressed in
using SystemVerilog's object-oriented features can directly clock cycles rather than nanoseconds.
read and manipulate nets and variables in an RTL design
using Verilog's hierarchical name resolution mechanism. B. Encapsulation and Re-use of Verification Code
Figure 1 shows how this could be achieved, using a trivial Elements
example in which the RTL design contains only one In good object-oriented programming practice, classes
variable, and that variable is stimulated by code in the form self-contained, re-usable, extensible elements. The
run() method of a SystemVerilog class Stimulus. Note Stimulus class in Figure 1 clearly does not meet these
that the stimulus generator object (instance stimulus of criteria. It contains not merely a hard-coded signal name but
class Stimulus) is constructed dynamically by code in the even a hard-coded Verilog hierarchical path name.
testbench module stimulus_module. Consequently it would be useless in an even slightly
modified verification environment.
Stimulus and monitoring elements are very likely to be Figure 2: The example of Fig.1 modified to use a
written to interact with a standardized set of signals or virtual interface
interface protocol, and therefore have the potential for wide
module dummy_RTL_module(input R);
re-use across verification projects. Any element containing ...
hard-coded path or signal names is immediately disqualified endmodule
from such re-use.
interface dummy_intf();
reg R;
C. Clumsy Code endinterface
The need to specify signal names with full hierarchical
qualification on each occasion the signal is modified leads to package stimulus_pkg;
class Stimgen;
code that is difficult to read and difficult to maintain. It is
virtual dummy_intf V;
clear that some indirection is required between the testbench function new(virtual dummy_intf V);
class and the RTL with which it interacts. this.V = V;
endfunction
task run();
III. VIRTUAL INTERFACE repeat (10)
The indirection mentioned above requires the testbench #5 V.R = 1'b0;
#5 V.R = 1'b1;
class to have some kind of reference or pointer into the RTL end
environment. SystemVerilog provides a specific mechanism endtask
to achieve this, known as a virtual interface. endclass
endpackage
Before discussing virtual interfaces, we will first describe
briefly the interface construct of SystemVerilog. module stimulus_module;
import stimulus_pkg::*;
initial begin
A. Interface Stimgen stimgen;
An interface is a design unit in SystemVerilog, broadly stimgen = new(testbench_Top.di);
similar to a module, but having certain special features that stimgen.run();
end
make it especially well suited to the description of endmodule
interconnect structures. Interfaces are described in more
detail in references [3], [5] and [7]. Interfaces are like module testbench_Top;
modules in that they are statically instantiated, with their dummy_intf di();
instances becoming members of the elaborated hierarchy. dummy_RTL_module DUT(di.R);
stimulus_module tester();
Consequently, any interface instance has a Verilog endmodule
hierarchical path name.
module having a similar external interface. The testbench
B. Taking a Reference to an Interface
class Stimulus no longer needs any knowledge of the
A virtual interface is a SystemVerilog variable that can layout of the DUT and its surrounding infrastructure. Instead
hold a reference to an interface instance. A variable of virtual it merely knows that it is interacting with some instance of a
interface type can be given a value (i.e. can be made to dummy_intf, which is known to contain a well-understood
reference an existing interface instance) by assigning the collection of nets and variables representing the interconnect
hierarchical path name of the chosen interface instance to it. structure of interest. A reference to that instance will be
Once this has been done, members (nets and variables) of the passed into the object via its constructor argument - and,
referenced interface instance can be accessed using the "." indeed, could be updated later in the life of the simulation if
select operator, just as if the variable stood for the interface's required.
full hierarchical path name.
In other respects, though, a virtual interface variable is C. Virtual Interfaces in a Wider Context
like any other variable in that it can be passed as an argument For use in a realistic, generally applicable verification
to a subprogram, copied to another variable of appropriate methodology there remain some unsatisfactory features in
type, compared for equality with another variable and so Figure 2. Although the stimulus class is now usefully
forth. decoupled from the DUT and test environment, and therefore
Figure 2 modifies the code example of Figure 1 to use an can be re-used, it remains tightly coupled to the
interface to contain the signal that will be manipulated by the dummy_intf interface definition; these two pieces of code
testbench, with a virtual interface holding a reference to the therefore must remain locked together. It is also necessary to
appropriate interface instance. The organization of this code hook the DUT to the signals in this interface. In our example
is now much more satisfactory than that of Figure 1. The we have chosen to do it by hierarchical reference into the
interface declaration dummy_intf encapsulates all the interface instance, but other methods are possible (see for
connections needed to hook to the DUT module, or any example [7]).
Finally, we note that it is necessary to pass the Figure 3: UART transmitter BFM
appropriate instance name as an argument to the stimulus
module UART_Tx(output logic line);
class's constructor, but typically the constructor call is not
located in the same piece of code that instantiates the int NBits;
required interface. In the specific example of Figure 2 it is time BitPeriod;
the top-level testbench structure module testbench_Top
task setNBits(input int N);
that instantiates the interface, and therefore controls its if (N>0 && N<=10) NBits = N;
instance name; but it is the verification component, in endtask : setNBits
module stimulus_module, that constructs the stimulus
object and therefore needs to know the interface's instance task setBitPeriod(input time T);
name. This issue is part of the wider problem of verification if (T>0) BitPeriod = T;
endtask : setBitPeriod
component configuration, which will be discussed in more
detail later. task send(input logic [9:0] d);
line = 0; // start bit
None of these problems detract from the usefulness of repeat (NBits) begin
virtual interfaces in providing a flexible connection between #BitPeriod line = d[0];
the dynamic world of software-like verification components d = d >> 1;
and the statically-instantiated environment of the DUT and end
#BitPeriod line = 1; //stop
its support structures. However, it is clear that careful #BitPeriod;
attention to testbench organization is needed in order to endtask : send
maintain the reusability of class-based verification
components. endmodule : UART_Tx
IV. BUS FUNCTIONAL MODELS Published verification methodology guidelines such as [6],
[7], and [9] describe in detail how such environments can be
A. Review of traditional module-based BFMs deployed in practice.
In traditional HDL-based verification practice, a bus References [7] and [9] are somewhat dogmatic
functional model (BFM) is a component, typically packaged concerning the use of interfaces and virtual interfaces to
as a module, whose purpose is to mimic the activity found on connect class-based BFMs to the statically instantiated DUT
a collection of signals (such as a bus) without necessarily structures. Other methodologies are less specific in their
mimicking the detailed internal behavior of any specific recommendations, allowing the environment developers
device connected to those signals. Verilog HDL makes it some discretion in implementing this low-level but essential
very easy and convenient to create BFM modules. The part of testbench functionality. In the remaining sections we
signals that will be manipulated by the BFM appear as ports describe an alternative style of connection between BFM and
of the module, and operations that the BFM can perform are DUT using a well-known software engineering design
coded as tasks or functions in the module, which will be pattern that we believe offers significant practical benefits
called by procedural code in a test environment. Figure 3 for re-use and testbench organization.
shows a simple Verilog BFM that imitates the behavior of an
asynchronous serial transmitter. Its only port is the simulated
serial line output. Task calls into the module can configure V. ABSTRACT BFM
the BFM for serial data rate and other features; the necessary
configuration information is stored in variables in the A. Abstract base class to represent the API
module. Another task, send, can be called by the test code Our starting-point for an alternative style of BFM
to cause the BFM to perform appropriate activity on its connection is to note that the public interface (application
output port. programming interface or API) presented to a verification
environment by a re-usable BFM should take the form of a
B. BFMs in an OOP verification environment set of virtual methods. In this way, users of the BFM can
remain ignorant of its implementation details, and class
Module-based BFMS as described in IV.A are useful in
extension can easily be used to create alternative BFM
simple block-level testbenches, but the statically instantiated
implementations that nevertheless present identical APIs to
nature of Verilog modules makes them insufficiently flexible
the rest of the verification environment.
for re-use in large verification environments following a
modern OOP methodology. Instead we prefer to code our Such an API can usefully be encapsulated as an abstract
BFMs as classes. Configuration and stimulus-generation base class whose sole contents are the public methods, coded
tasks are readily modeled as public methods of the BFM as pure virtual methods. Once such an abstract base class has
class. Verification environments of arbitrary complexity and been defined, variables of that class type can be used to hold
flexibility can be constructed dynamically, using procedural references to an instance of any concrete BFM class that
code in a testbench environment class to construct and inherits from the abstract base class. Testbench code that
manage appropriate instances of the BFM and other classes. makes use of such a BFM can do so via variables of the
abstract base class type, conveniently decoupling the Figure 4: Abstract BFM class with concrete
remainder of the test environment from details of BFM implementation in an interface
implementation. In software engineering circles, this
package APB3_pkg_Fig4;
technique is effectively known as the template method virtual class APB3_BFM;
design pattern [13]. From the perspective of this paper, pure virtual task write (
however, the most important aspect of this decoupling is that int unsigned addr, data);
the connection of a BFM to its target HDL signals can also pure virtual task read (
be hidden by this same mechanism. int unsigned addr,
output int unsigned data);
endclass
B. Concrete Implementations Tightly Coupled to the DUT endpackage
interface APB3_TB_intf(input bit PCLK);
It is preferable to decouple an abstract BFM base class import APB3_pkg_Fig4::*;
from the DUT and its supporting structures as completely as logic PENABLE, PWRITE, PSEL, PREADY;
possible in the interests of reusability. However, there must logic [15:0] PADDR, PWDATA, PRDATA;
also be at least one class derived from this base class that class APB3_concrete_BFM extends APB3_BFM;
implements the BFM's concrete functionality. This task write(int unsigned addr, data);
@(posedge PCLK)
implementation class must manipulate the DUT signals PSEL <= 1'b1;
directly, and it is convenient for its code to exist in a scope PENABLE <= 1'b0;
where those signals are directly visible. This scope should be PWRITE <= 1'b1;
the module or interface that implements the set of signals PADDR <= addr;
that the BFM will manipulate. PWDATA <= data;
@(posedge PCLK)
Figure 4 presents an example of an abstract BFM class PENABLE <= 1'b1;
do @(posedge PCLK); while (!PREADY);
APB3_BFM forming the public API to a BFM that can PSEL <= 1'b0;
perform transactions on the well-known APB3 peripheral PENABLE <= 1'b0 ;
bus structure [10]. The base class is declared in a common endtask
package will be imported into a number of different scopes. task read ( int unsigned addr,
Note how the base class is specified to be abstract by means output int unsigned data);
@(posedge PCLK)
of the virtual qualifier in its declaration. The interface PSEL <= 1'b1;
APB3_TB_intf creates the standard set of APB3 bus PENABLE <= 1'b0;
signals, and also declares a derived BFM class PWRITE <= 1'b0;
APB3_concrete_BFM that implements the API declared PADDR <= addr;
@(posedge PCLK)
in the base class. Within the same interface, an instance of PENABLE <= 1'b1;
the concrete BFM class is created and initialized. A reference do @(posedge PCLK); while (!PREADY);
to this instance can then be passed to any part of the PSEL <= 1'b0;
testbench that has a variable of APB3_BFM type, and calls to PENABLE <= 1'b0 ;
data = PRDATA;
the concrete class's methods can be made through that endtask
variable. endclass
APB3_concrete_BFM bfm = new;
endinterface
VI. BENEFITS OF THIS STRUCTURE module APB3_TB_top;
The arrangement outlined in V. has a number of useful import APB3_pkg_Fig4::*;
benefits. Because the concrete BFM implementation class is bit CLK;
always #5 CLK = ~CLK; // clock generator
embedded in the same scope that declares the relevant bus APB3_TB_intf apb3_intf(CLK);
signals, it has direct access to those signals without any form APB3_device DUT(
of hierarchical reference. Furthermore, the BFM code is .PCLK(CLK),
naturally locked to the bus signal declarations, avoiding the .PENABLE(apb3_intf.PENABLE),
need to keep two independent design units together. The ...);
initial begin : Test_Activity
package APB3_pkg that defines our abstract BFM class can APB3_BFM bfm; // abstract BFM reference
readily be imported into any design unit that needs it, int read_data;
without concern for the implementation details of the bus or bfm = apb3_intf.bfm; // reference to BFM
its BFM. bfm.write(100, 1234);
bfm.read(100, read_data);
The bus signals are declared in an interface. This if (read_data != 1234)
interface can easily be extended to incorporate modports $display("error: unexpected read data");
end
[1][5], providing a convenient way to connect the interface to endmodule
SystemVerilog RTL designs that would normally be
connected to a physical bus interface.
VII. USING THIS STRUCTURE WITH VIRTUAL INTERFACES updating or sampling, using the clocking block construct. A
As shown in Figure 4, the abstract BFM mechanism clocking block insulates a class-based verification
makes virtual interfaces unnecessary for accessing the BFM. environment from the nanosecond-by-nanosecond minutiae
However, virtual interfaces may nevertheless be useful in of signal transition timing and allows the testbench to
providing an easy way for class-based verification operate in terms of clock cycles. It also relieves the testbench
components to locate the concrete BFM variable in the of the need to understand the differences between nets and
interface instance. variables at the interface to the DUT; all signals controlled
through a clocking block appear to the testbench as a special
kind of variable known as a clockvar. Tutorial review of the
VIII. CLOCKING BLOCKS FOR TIMING ABSTRACTION clocking block mechanism may be found in [4] and [8].
Within a SystemVerilog module or interface, any Figure 5 shows the code of Figure 4 reworked to use a
collection of signals (nets or variables) may be grouped clocking block in the APB3 bus interface. It is interesting to
according to the clock signal that normally controls their note that the concrete BFM class definition is embedded
Figure 5: Abstract BFM class and concrete implementation in an interface using clocking block