Àâòîáèîãðàôèÿ
Ðåôåðàò
Áèáëèîòåêà
Ññûëêè
Îò÷åò î ïîèñêå
Èíäèâèäóàëüíîå çàäàíèå

Using Transaction-Based Verification in SystemC

Cadence Berkeley Labs, Technical Report # CDNL-TR-2002-0601, June 2002.

C. Norris Ip, ip@cadence.com; Stuart Swan, stuart@cadence.com

Cadence Design Systems, Inc.

Èñòî÷íèê: http://www.openverificationfoundation.org/docs/transaction_based_systemc.pdf

Keywords : Functional Verification, System-Level Verification, C++, TestBuilder, SystemC, TestBuilder-SC

Abstract

SystemC is a new modeling language based on C++ for hardware and system-level design modeling. This paper examines how a basic transaction-based test bench may be created in the SystemC version 2.0 standard. An example with a pipelined bus interface is presented to illustrate what can be done easily with the latest SystemC version 2.0 distribution [1].

The TestBuilder team is currently creating a TestBuilder-SC library to fill in several missing pieces in SystemC 2.0, in order to support advanced transactionbased verification in a real design development process. While TestBuilder [3] was designed for direct connection to an HDL simulator, TestBuilder-SC acts as a verification layer on top of SystemC. The API in TestBuilder-SC has been proposed to the SystemC’s verification working group to be adopted as a standard.

1. Introduction

Recently SystemC has released version 2.0 that is capable of capturing System-Level Designs. Together with the basic facilities in version 1.0, it seems that we may be able to complete a design from System-Level down to RTL level in a seamless flow.

However, one important element in creating a product is verification. Does the current SystemC standard contain enough features for an effective functional validation of a real design?

In order to answer this question, we have gone through the exercise of creating a transaction-based test bench for a simple design with a pipelined bus. While we were able to create a basic transaction-based test bench using the current SystemC 2.0 standard, we have also identified several aspects that are not convenient or effective in supporting a realistic verification effort.

This paper reviews the design and the test benches that we used to evaluate a SystemC-based verification environment. After the review of this system, we describe the missing verification aspects, and how the existing TestBuilder concepts can be adapted into a SystemC verification layer to address these missing verification aspects in the current SystemC 2.0 standard.

We have been driving the standardization effort in the SystemC verification working group, gathering requirements and feedback from the group members. While we plan to release a new library called TestBuilder-SC as an open-source library, we are also submitting this library as a proposal to the SystemC standard, and our implementation as the reference implementation.

This paper explains some basic SystemC and TestBuilder concepts as we describe the design and the transaction-based test bench. For a more detailed description of SystemC and TestBuilder concepts, please check out the papers by Stuart Swan [4] and by Cox et al. [5].

2. Basic Transaction-Based Test Benches

SystemC 1.0 provides a set of modeling constructs that are similar to those used for RTL and behavioral modeling within an HDL such as Verilog or VHDL. Similar to HDLs, users can construct structural designs in SystemC 1.0 using modules, ports, and signals. SystemC 2.0 enables modeling of systems both at and above the RTL level of abstraction. It introduces a set of features for generalized modeling of communication and synchronization, using channels, interfaces, and events [4].

The different levels of abstraction in SystemC 1.0 and 2.0 provide the basis upon which transaction-based test benches can be created. As shown in Figure 1a, a transaction-based verification methodology partitions the system into transaction-level tests, transactors, and the design. Communication between the tests and the transactors is done through task invocation, at a level above the RTL level of abstraction. The communication between the transactors and the design is done through signal manipulation at the RTL/Signal level.

Let’s look at a transaction-based test bench for a design with a pipelined bus interface, shown in Figure 1b. The complete code can be obtained upon request.

Figure 1a : transaction-based verification

Figure 1b : an example with a pipelined bus

Using abstract methods in C++, the task-based interface

can be declared as:

class rw_task_if : virtual public sc_interface {
public:
    typedef sc_uint<64> addr_t;
    typedef sc_uint<64> data_t;
    struct write_t {
        addr_t addr;
        data_t data;
    };
    virtual data_t read( addr_t * ) = 0;
    virtual void write(write_t * ) = 0;
};

This abstract base class specifies two abstract methods, read and write, and their related data types, representing the abstract level in which a test is to be written in. The class sc_interface is provided by SystemC to facilitate the creation of such interfaces. The template sc_uint and other similar templates are provided by SystemC to support data objects with different bit widths and different operator semantics.

The communication between the transactor and the design is captured in another class with signal-level ports:

class pipelined_bus_ports : public sc_module {
public:
    sc_in clk;
    sc_inout rw;
    sc_inout addr_req;
    sc_inout addr_ack;
    sc_inout< sc_uint<8> > bus_addr;
    sc_inout data_rdy;
    sc_inout< sc_uint<8> > bus_data;
};

The signals in this class represent the RTL-level interface in which a design under verification communicates to its environment. The class sc_module is provided by SystemC to specify a block with ports; and signal ports are created via the templates sc_in, sc_out, and sc_inout, indicating a read-only port, a write-only port, and a readwrite port respectively.

A transaction-based verification methodology relies on transactors to act as the adaptors between the abstract tests and the concrete design. By capturing these transactors as reusable IP, new tests with complex concurrent behavior can be quickly created.

In this example, a transactor is created as a class deriving from both aforementioned interfaces:

class rw_pipelined_transactor
    : public rw_task_if,
    public pipelined_bus_ports {
public:
    SC_CTOR(rw_pipelined_transactor) {}
    virtual data_t read(addr_t *);
    virtual void write(write_t *);
private:
    fifo_mutex addr_phase;
    fifo_mutex data_phase;
};

The macro SC_CTOR is provided by SystemC to signify the constructor of a module. The class fifo_mutex is a mutex that grant access in a first-come-first-serve manner. The job of a transactor is to convert a transaction as modeled by a function call into signal-level communication, and vice versa. For example, the read method is implemented as:

data_t rw_pipelined_transactor::read(addr_t * addr) {
    addr_phase.lock();
    bus_addr = *addr;
    addr_req = 1;
    wait ( addr_ack->posedge_event() );
    addr_req = 0;
    wait ( addr_ack->negedge_event() );
    addr_phase.unlock();
    data_phase.lock();
    wait ( data_rdy->posedge_event() );
    data_t data = bus_data.read();
    wait ( data_rdy->negedge_event() );
    data_phase.unlock();
}

This method translates a read transaction into a series of signals according to the specific protocol at the signal-level interface. Because a pipelined bus is used, two mutexes, addr_phase and data_phase, are used for the two phases. At the beginning of the address phase, the corresponding mutex is locked so that there is at most one address communication on the bus at any given time. At the end of the address phase, the corresponding mutex is unlocked so that, although the data phase has not finished yet, another call to this transactor can still start its address phase. The three methods, posedge_event(), negedge_event(), and read(), are provided by SystemC to denote a positive edge transition of a signal, a negative edge transition of a signal, and the access of the value in a port. The procedure wait() in SystemC suspends the thread of execution until the event specified in the argument occurs.

With the detailed protocol abstracted by the transactor, a test can be written in a form independent of the actual signal-level interface:

class test : public sc_module {
public:
    sc_port< rw_task_if > transactor;
    SC_CTOR(test) { SC_THREAD(main); }
    void main();
};
void test::main() {
    // simple sequential tests
    for (int i=0; i<3; i++) {
        rw_task_if::addr_t addr = i;
        rw_task_if::data_t data = transactor->read(&addr);
        cout << "received data : " << data << endl;
    }
    // simple concurrent tests
    rw_task_if::addr_t addr[ 2 ]; addr[ 0 ] = 0; addr [ 1 ] = 1;
    rw_task_if::data_t data[ 2 ];
    SC_FORK
        sc_spawn_method( &data[ 0 ], transactor[ 0 ], &rw_task_if::read, &addr[ 0 ]),
        sc_spawn_method( &data[ 1 ], transactor[ 0 ], &rw_task_if::read, &addr[ 1 ])
    SC_JOIN
    cout << "received data : " << data[ 0 ] << ","
    << data[ 1 ] << endl;
}

The first half of the test generates a series of three read transactions, starting a new one after the previous one has completed. The second half generates two read transactions in parallel, so that they exercise the pipeline. The macro SC_THREAD creates a new thread of execution for the method main during simulation. The sc_spawn_method function is similar to create in pthread, creating a new C++ thread and executing the method specified in the third argument for the object in the second argument. The first argument is the object to store the return value, and the last argument is the argument to the method.

The constructs, SC_FORK, and SC_JOIN, together with sc_spawn_method, are dynamic spawning enhancements proposed to SystemC and their implementation is distributed as an example in the SystemC 2.0 distribution kit.

It is important to note that the port of this test has the rw_task_if interface as the template argument. Because of this, the test can be reused with other designs with a different bus interface, by plugging in a appropriate transactor.

To complete the example, the code for the design and the netlist is shown below:

class design : public pipelined_bus_ports {
public:
    SC_CTOR(design) {
        ...
        SC_THREAD(addr_phase);
        SC_THREAD(data_phase);
    }
    void addr_phase() { while (1) ... }
    void data_phase() { while (1) ... }
private:
    ...
};
int sc_main(int argc, char *argv[ ]) {
    ...
    // the static structures in this simulation
    test t ("t"); // the test
    rw_pipelined_transactor tr ("tr"); // the transactor
    design duv ("duv"); // the design under verification
    sc_clock clk ("clk",20,0.5,0,true); // a clock
    // the signals to connect the static data structures
    sc_signal < bool > rw;
    sc_signal < bool > addr_req;
    ...
    // connecting the signals and transactors to
    // the ports of the modules
    t.transactor = tr;
    tr ( clk.signal(), rw, addr_req, ... );
    duv ( clk.signal(), rw, addr_req, ... );
    // start simulation
    sc_start(10000);
    ...
}

The design implementation contains the same set of signals for the pipelined interface, with two C++ threads to respond to the two phases of the pipeline.

This example illustrates a preferred style for transactionbased verification in SystemC. In the remainder of this paper, we will discuss how to use TestBuilder-SC to extend this example for a more effective verification effort.

3. Advanced Transaction-Based Test Benches

Although existing SystemC features are sufficient for creating a basic test bench, there are still many missing pieces that are necessary for an effective verification effort. These include:

  • constrained randomization for effective generation of random stimulus.

  • creation of a event and transaction database for effective debugging and coverage analysis.

  • complex synchronization and assertions for effective coordination among concurrent tests and detection of illegal behaviors.

  • infrastructure and scripts for interaction with HDL simulators and other tools.

TestBuilder was developed to address similar issues in verification for HDL simulators such as NC-Sim. However, because it was developed independent of SystemC, TestBuilder and SystemC do not operate together seamlessly.

In September 2001, a prototype for connecting TestBuilder 1.2 and SystemC was made available in www.testbuilder.net. While it is in general very useful to our customers, the translations between data types and events are not very efficient. Some functionality is duplicated in both libraries with different APIs and use models; this could lead to confusion and deepen the learning curve.

As a result, the TestBuilder team has started a project to create another version of TestBuilder directly on top of SystemC. It is called TestBuilder-SC and is designed to have the same look and feel of SystemC, but fill in the hole so that our customers would be able to perform transactionbased verification with linkage to analysis tools offered by Cadence.

Using our experience in developing TestBuilder, and customer feedback on the use models of TestBuilder and SystemC, the TestBuilder team was able to design the interface for TestBuilder-SC in a relatively short amount of time, and is in the processing of implementing an opensource library for external distribution.

3.1 TestBuilder-SC

Taking the pipelined bus design as an example, let’s discuss how TestBuilder-SC would be able to improve the verification process.

Handling arbitrary data types.

TestBuilder-SC contains a data introspection facility to analyze third-party data types and user-specified composite types.

For example, the rw_task_if interface uses a SystemC data class sc_uint<64> and a composite type write_t. We would like to extract information from these types and manipulate them without asking the user to modify the source code.

The data introspection facility uses partial specialization of templates to analyze a data type. Two templates are developed for this purpose:

template class tb_extensions; template class tb_smart_ptr;

The tb_extensions template extends arbitrary data types to have a standard access interface, called tb_extensions_if. This standard access interface allows a piece of code to extract type information, perform value access and value assignment to a variable without requiring the code to have explicit type information at compile time.

The tb_smart_ptr template extends the arbitrary data type to have extra storage for callback registration, randomization, and other purposes.

This facility can be considered as a C++ version of the Verilog PLI standard. The ability to handle arbitrary data types enables the import of legacy code and facilitates the reuse of the same code for multiple libraries. It is a crucial basic building block for constrained randomization, variable recording, and transaction attribute recording. Constrained randomization.

Constrained random tests are an important element in a state-of-the-art verification environment. Using the data introspection facility discussed previously, TestBuilder-SC supports constrained randomization on arbitrary data types. For example, random write requests for our rw_pipelined_transactor transactor can be generated by:

tb_smart_ptr < rw_task_if::write_t > write;
for (int i=0; i<3; i++) {
    write->next(); // generate a random value
    transactor->write(write->get_instance());
    cout << "send data : " << write->data << endl;
}

The tb_smart_ptr template is designed to have very similar use model as a C/C++ pointer. Fields and methods are accessed by the overloaded operator->. The next() method generates a new random value, and the field access returns the value of the data field to cout.

Expressions and constraints can be created using tb_smart_ptr as well. For example, the following code specifies an address range and a relationship between the address and the data:

class write_constraint : public tb_constraint_base {
public:
    tb_smart_ptr< rw_task_if::write_t > write;
    TB_CTOR(write_constraint) {
        TB_CONSTRAINT( write->addr() < 0x00FF );
        TB_CONSTRAINT( write->addr() != write->data() );
    }
};

This constraint creates two Boolean expressions regarding the fields of the variable write. TestBuilder-SC first translates these expressions to Binary Decision Diagrams (BDDs), and then generates only values that satisfy these expressions.

Creation of a event and transaction database. SystemC can be viewed as a hybrid language which contains elements of both procedural and hardware description languages. While the procedural portions of a SystemC description can be analyzed and debugged using a typical C++ debugger, the hardware aspects are best analyzed and debugged using a waveform tools. For example, the activities in the pipelined bus can be captured in a series of transactions as shown in the following figure.

This diagram succinctly displays the activities in terms of transactions, with three non-overlapping back-to-back reads and two overlapping reads. The related address and data values are displayed in the corresponding childtransactions in the address phase and data phase.

Furthermore, a coverage tool may generate a report that five read transactions have been executed, with one occurrence of simultaneous activities on both the address bus and the data bus.

A TestBuilder-SC test bench for SystemC can generate a database from which these tools can extract the relevant information. A transaction is generated through the concept of a transaction stream, with each stream corresponding to a group of mostly sequential but potentially overlapping transactions in the waveform display. For example, addr_phase.lock();

tb_tr::transaction_type
addr_phase_handler("addr phase", addr_phase_stream);
tb_tr::transaction h =
addr_phase_handler.begin_transaction(*addr);
bus_addr = *addr;
addr_req = 1;
wait ( addr_ack->posedge_event() );
addr_req = 0;
wait ( addr_ack->negedge_event() );
addr_phase.unlock();
addr_phase_handler.end_transaction(h);

Several lines of code (shown in italics) are added to the address phase in rw_pipelined_transactor::read() to capture the address phase of a read transaction. The first line creates a addr phase transaction type in the stream addr_phase_stream. The second line begins a transaction via the begin_transaction() method, and the third line ends a transaction via the end_transaction() method. The value in variable addr is added in the beginning of the transaction as an attribute. The attribute type, specified in the parameter of the template tb_tr::transaction_type, can be any arbitrary data type. The implementation of begin_transaction() uses the data introspection facility to extract the name and the values of the attribute. The overall read transactions and the transactions for the data phase can be generated similarly. The database may also contain data for value transitions in variables. This can be done by registering a value change callback to a variable via tb_smart_ptr instantiation. Complex synchronization and assertions.

A realistic design typically has multiple interfaces and complex protocols, and operates in a complex concurrent environment. Capturing the environment of such a design in a test bench requires special features in a tool.

In order to globally coordinate traffic generation among the interfaces, and to check whether the outputs from the design occur in the right sequence, complex synchronization is necessary. The ability to efficiently wait on multiple events such as signal transitions and semaphore waiting (in a sequential, conjunctive, or disjunctive manner) is very important. Similarly a user should be able to create a monitor to verify that a complex protocol is being met throughout the simulation.

TestBuilder-SC provides a set of synchronization classes and the ability to compose them into event expressions. These synchronization classes extend the existing SystemC synchronization class to have better support for verification purposes. The event expression provides extra expressive power to model complex protocols efficiently. Temporal assertions enable the test bench to detect illegal events in simulation, and are fully integrated with the transaction recording facility in TestBuilder-SC. Infrastructure and scripts.

The SystemC standard describes the API with which a user specifies the design and the test bench. However, a complete verification environment requires more than just the design and test bench description.

TestBuilder-SC includes several scripts for detecting utilities in a customer’s C++ working environment, configuring your SystemC and TestBuilder-SC installation, and building and running simulations.

TestBuilder-SC objects support an abstract interface called tb_object_if, which contains various abstract methods that you can call in a debugger and other tools.

Although TestBuilder-SC is different from TestBuilder, the TestBuilder team is working on an interoperability guideline so that existing TestBuilder IP can still be used in a SystemC/TestBuilder-SC environment.

Finally, a complete verification environment also requires the ability to simulate both HDL descriptions and SystemC/TestBuilder-SC descriptions in the same simulation run. The TestBuilder team is currently working on an intermediate solution based on SystemC reference implementation, with a high-speed proprietary version being developed in the NC team.

4. Summary

This tutorial illustrates the use of SystemC and TestBuilder-SC to create effective test benches. Through the use of system-level features in SystemC 2.0, a transactionbased test bench can be created. However, in order to support realistic verification efforts, extensions to SystemC 2.0 are needed.

We have identified several areas that need improvement, and worked with the TestBuilder engineering team to come up with a proposal.

Through the use of constrained random tests, more tests can be created, leading to higher confidence in a design, with better utilization of the simulation cycles in a customer’s server farm. Through the use of recording API, a database is generated in SystemC simulation, with sufficient data for Cadence’s verification tools to facilitate the debugging and coverage analysis process.

Because many of our tools use a C++ library in one form or another for design and test bench capture, SystemC has the potential to be the unifying language with which various tools in a verification environment can talk to each other.

5. Acknowledgement

The authors would like to thank the many people who contributed to the development of SystemC and the TestBuilder team for designing and developing both TestBuilder and TestBuilder-SC open-source libraries.

6. References

[1] Functional Specification for SystemC 2.0, available at www.SystemC.org.

[2] Thorsten Grotker, Stan Liao, Grant Martin, and Stuart Swan, System Design with SystemC, Kluwer Academic Publishers, 2002.

[3] Functional Specification and User Guide for TestBuilder 1.3, available at www.testbuilder.net.

[4] Stuart Swan, An Introduction to System Level Modeling in SystemC 2.0, white paper, available at www.SystemC.org.

[5] Steven Cox, Mark Glasser, William Grundmann, C. Norris Ip, William Paulsen, John L. Pierce, John Rose, Dean Shea, and Karl Whiting. Creating a C++ Library for Transaction-based Test Benches, Forum on Design Languages, France, September, 2001.



Ìàãèñòð ÄîíÍÒÓ Ñìåøêîâ Àëåêñàíäð Ñåðãååâè÷