Random Verification Using VCS Native Testbench


Huawei Technologies Co.LTD
Wang Wanwan
wangwanwan@huawei.com

1. Preface

The increase in the scale and complexity of ASICs has made verification a critical part of the design flow. Using traditional direct verification method, engineers must write many testcases to verify the multitudinous specifications of a. Nowadays random test technology has been widely used in ASIC verification which provides following advantages:

● A random environment is helpful to uncover hidden or corner test cases and bugs

● Functional coverage grows fast in early phase

● Number of testcases in random test is much fewer than direct test

In a general way, a random verification environment must contain the following three constituent parts:

1. Random stimulus generation (RSG)

2. Automatic response checking (ASC)

3. Coverage measurement (CM)

The random stimulus generation is one of the most important aspects of random environment. It generates all kinds of data packets under random constraint; the BFMs(Bus Function Model) or transactors send these packets into the design according to the timing of the design interface. Since the random stimulus generation actually creates the test, a good random stimulus generator makes a comprehensive verification. The automatic response checking is performed by comparing the output of the reference model and the design, and monitor the timing of the design interface or internal signals on the fly. It is used to confirm that the behavior of the design match the expected specification. The coverage measurement measures if the design is fully tested. The most common types of coverage are code coverage and functional coverage.

Following technologies are used to build a random test environment:

● A verification language, which is based on OOP/AOP technologies and well supports random constraints, automatic response checking methods such as expectation, assertion, etc

● Advantage simulation environment, which supports both hardware design language and the verification language

● Code coverage technologies

● Functional coverage technologies

We can easily find a traditional solution such as VCS+VERA environment. But now Synopsys provides a easier method: VCS Native Testbench (NTB) environment! In this environment, VCS integrates VERA language and uses kernel interface to connect simulator and verification language, it provides a higher performance and more features for random verification. Also, NTB environment supports code coverage and functional coverage.

This article discusses the implementation of the three aspects using synopsys NTB environment. It also compares the performance between NTB and traditional ways and gives some advice about random verification.

2. Environment Architecture

Figure1 shows the basic random verification environment for reference. The environment is divided into four layers: signal layer, traffic layer, command layer and function layer. Signal layer is consisted of DUT(Design Under Test), BFMs and monitors. In the signal layer, components are connected with actual signals. The BFMs and the monitors implement the timing and protocol of the design interface. Traffic layer consists of random stimulus generator, the reference model and the automatic response checker. The data type between them is the object of packets. Functional coverage module is embedded into the signal layer and the traffic layer; it connects information of the two layers and analyzes the score of functional coverage. The command layer comprises a series of functions or tasks, these functions and tasks encapsulate the methods of lower layers such as signal layer or traffic layer, and provide many abstract utilities. Encapsulation of command layer makes testcases more readable and makes test much easier. Function layer consists of random and direct testcases. Each testcase tests one or more functions of the DUT. Testcase can call functions or tasks in command layer, or directly call functions or tasks in traffic layer and signal layer.

The following is a testcase example:

// Testcase example: TC0001

function integer testcase()

{

// Variable definitions

C_BasePacket Packet;

// Functional coverage declarations

CG_F0112 cg_f0112 = new(); // Specify which coverage group will be invoked

CG_F0113 cg_f0113 = new(); // Specify which coverage group will be invoked

// Specify the filename and directory of coverage database

coverage_set_database_file_name( “ ../fc/TC0001_FC.db ” );

// Call commands to reset and initialize the DUT

GlobalReset();

InitializeDUT();

// Call commands or lower layer methods to

// generate random packets and send them into DUT and RM

Packet = new(“Base Packet”);

Packet.m_RandomMode = 1; // Use random mode

Packet.GenPacket(); // Generate a packet

TH.m_GMIIBFM.SendPacket( Packet ); // Send the packet into DUT

TH.m_RM.SendPacket( Packet ); // Send the packet into RM

repeat(1000) @( posedge CLOCK );

if( ErrorCounter() > 0 )

PrintResult(“FAIL”); // PrintResult is also a command

else

PrintResult(“PASS”);

return 0;

}

3. Random Stimulus Generation

Randomness in computer systems is derived from pseudorandom number generators. As the name suggests, pseudorandom numbers are not truly random. Rather, they are computed from mathematical formula or simply taken from a precalculated list. If we control the initial state of the mathematical formula or precalculated list, the random sequence is predictable. The predictability is very useful for verification, because we can change tests by modifying the initial state of the mathematical to find new bugs. It also allows failing tests to be rerun with the same conditions that caused the failures.

Random stimulus generator generates data packets under random constraints. VCS native testbench supports powerful random constraints. Under random constraints, data packets are generated in an expected area. The following example constraints the length of Ethernet Packets from 64 to 1535:

C_EthernetPacket

{

integer m_Length;

constraint RC_EthernetPacket

{

m_Length >= 64;

m_Length <= 1535;

}

}

According to the OSI model, network is divided into seven layers. When higher layer PDU is encapsulated in a lower layer, it is usually added a header and a trailer. Figure2 shows the relationship of encapsulation between these layers.

For example, when an IP packet is encapsulated in an ethernet frame, it is added a MAC header and a FCS. So, if we want to create the packets of each layer, we must create the headers and trailers of these layers, such as MAC header, FCS, IP header, UDP header, TCP header, DHCP header, etc. Generally, since a header is consisted of several fields and there are relationships among them, we define random constraint blocks to restrict them. The following is an example for MAC header definition:

class C_MACHeader

rand bit[47:0] m_DMAC; // Destination MAC address

rand bit[47:0] m_SMAC; // Source MAC address

rand bit[15:0] m_EtherType; // EtherType

rand E_TagType m_TagType; // VLAN Tag type

rand bit[31:0] m_VLANTag; // VLAN Tag

rand integer m_MACHeaderLength; // MAC header length

bit[ 7:0] m_MACHeaderData[]; // MAC header data

constraint RC_MACHeader;

task new();

task GenMACHeader();

}

The constraint block for MAC header is defined as the following:

constraint C_MACHeader::RC_MACHeader

{

if( m_TagType == UNTAG )

{

m_MACHeaderLength == 14;

}

else

{

m_MACHeaderLength == 18;

}

m_DMAC[47:24] == 24′h01005e;

m_SMAC[40] == 0;

m_EtherType >= 16′h0600;

}

Thus, we can create a random object using C_MACHeader as the following statements:

C_MACHeader MyMACHeader;

MyMACHeader = new();

void = MyMACHeader.randomize();

MyMACHeader.GenMACHeader();

We can create other headers or trailers of each layer in the same way, such as IP header, TCP header, UDP header, DHCP header, etc.

I strongly suggest that verification environment uses a PACKET as the basic unit of data stimulus. First, we define a prototype of packet, and then extend it to real packets by adding headers and trailers. NTB is an OOP based verification methodology; it can easily make our plan reality. The following class defines a prototype of packet:

virtual class C_VirtualPacket

{

string m_PacketName;

integer m_SeqNo;

integer m_RandomMode;

integer m_PacketLength;

bit[7:0] m_PacketData[] assoc_size m_PacketLength;

virtual task LoadPayload(); // Load Payload

virtual task LoadCRC(); // Load CRC

virtual task GenPacket(); // Generate a packet

virtual task ShowPacket(); // Display the packet

}

Since C_VirtualPacket is a virtual class and it is only a prototype, the implementations of the methods are not defined. We should create another packet to define the implementations.

class C_BasePacket extends C_VirtualPacket

{

bit m_PacketError; // Add other members

constraint RC_BasePacket; // Define contraint block

task new(); // constructor

virtual task LoadPayload(); // Overload the methods

virtual task LoadCRC(); // of C_VirtualPcket

virtual task GenPacket();

virtual task ShowPacket();

}

C_BasePacket extends C_VirtualPacket, overloads its methods and defines the implementations:

constraint C_BasePacket::RC_BasePacket

{

// constraint statements for RC_BasePacket

… …

}

task C_BasePacket::new()

{

// implementation of new()

… …

}

task C_BasePacket::LoadPayload()

{

// implementation of LoadPayload()

…. ….

}

C_BasePacket is a basic packet definition, it contains some basic information of the packet such as packet length, packet name, sequence number, etc. We can extend C_BasePacket to other packet types. For example, an IP packet is defined as following:

class C_IPPacket extends C_BasePacket

{

C_IPHeader m_IPHeader; // Add IP header

constraint RC_IPPacket; // Constraint IP packet

task new(); // Constructor

virtual task LoadIPHeader(); // Load IP header

virtual task GenPacket(); // Overload GenPacket() method

}

Similarly, IP over ethernet frame is defined as following:

class C_EthernetPacket extends C_IPPacket

{

C_MACHeader m_MACHeader;

constraint RC_EthernetPacket;

task new();

virtual task LoadMACHeader();

virtual task LoadFCS();

virtual task GenPacket();

}

We define these methods as virtual methods because it is easy to overload a virtual method and to extend the class that contains virtual methods. Thus, packets of each layer such as ethernet frame, IP packet, UDP packet, TCP packet, etc, are created by this extension technology. In the following example, 100 ethernet packets are randomly created and put into a mailbox:

C_EthernetPacket EthernetPacket;

Integer i, Traffic;

Traffic = alloc( MAILBOX, 0, 1 ); // Allocate a mailbox

for( i=0; i<100; i++ )

{

EthernetPacket = new();

EthernetPacket.m_RandomMode = 1; // Use random mode

EthernetPacket.GenPacket(); // Generate ethernet packet

mailbox_put( Traffic, EthernetPacket ); // Put them into mailbox

printf( “ INFO: Generate NO.%0d ethernet packet \n ”, i );

}

It is very convenience to save temporary random stimulus in mailboxes.

4. Automatic Response Checking

The goal of verification are: everything is tested and each test result iscorrect. Automatic response checking checks if each test result is correct; it compares the output of RM(Reference Model) and the output of DUT, and checks the timing of DUT interface. There are many ways to verify the DUT interface. We can write monitors to check it directly. Following codes show an example to check cpu_cs signal of CPU interface:

cpu_if.cpu_cs = 0;

repeat(10) @( posedge cpu_if.cpu_clk );

if( cpu_if.cpu_rdy !== 1′b0 )

{

printf( ” ERROR: cpu_cs error \n” );

}

We can also write assertions to check interface. VCS supports OpenVera Assertion (OVA) and SystemVerilog Assertion (SVA). Here is an example for SystemVerilog Assertion:

// Add an example of SVA

Packet data checking is implemented by comparing the packets output from DUT and the packets output from reference model. The following task is an example for packet comparing:

task ComparePacket()

{

integer i;

C_Packet DUTPacket;

C_Packet RMPacket;

DUTPacket = BFM.m_PostPacket.object_copy();

RMPacket = RM.m_PostPacket.object_copy();

if( DUTPacket.m_SeqNO != RMPacket.m_SeqNO ) // Compare sequence number

{

error( ” Sequence Number error \n” );

}

else if( DUTPacket.m_PacketLength != RMPacket.m_PacketLength ) // Length

{

error( ” Packet Length error \n” );

}

else

{

for( i=0; i<DUTPacket.m_PacketLength; i++ )

{

if( DUTPacket.m_PacketData[i] != RMPacket.m_PacketData[i] )

{

error( ” Packet Data %0d error \n”, i );

}

}

}

printf( ” Packet is OK \n” );

}

5. Coverage Measurement

Coverage measurement is used to decide if everything is tested. The most common types of coverage are code coverage and functional coverage. High code coverage with low functional coverage indicates that testcases are not built enough according to design functions or design specifications are listed redundantly. High functional coverage with low code coverage indicates that there is redundant code inside the design or design specifications are not completely listed.

Synopsys VCS simulator natively supports code coverage, including line coverage, condition coverage, FSM coverage and toggle coverage, etc. VCS code coverage utilities also help designers run post-process on the coverage result such as merge, autograding,etc.

VCS native testbench also supports functional coverage. The following examples are coverage group definitions for an ethernet MAC verification environment:

// Feature F0010: Speed 100M/1000M receiving packets

coverage_group CG_F0010()

{

sample_event = wait_var( TH.m_RM.m_CORE.m_RXMACReceiveCounter );

sample TH.m_MDIOBFM.m_PHYStatus.m_SpeedStatus

{

state s_Speed1000M ( SPEED1000M );

state s_Speed100M ( SPEED100M );

}

}

// Feature F0011: Speed 100M/1000M transmitting packets

coverage_group CG_F0011()

{

sample_event = wait_var( TH.m_RM.m_CORE.m_TXMACReceiveCounter );

sample TH.m_MDIOBFM.m_PHYStatus.m_SpeedStatus

{

state s_Speed1000M ( SPEED1000M );

state s_Speed100M ( SPEED100M );

}

}

// Feature F0020: 802.3X flow control

coverage_group CG_F0020()

{

sample_event = @( posedge phy0_rx_if.phy0_rxclk );

sample TH.m_RM.m_CORE.m_RXMACPauseStatus

{

trans t_High2Low ( 1 -> 0 );

trans t_Low2High ( 0 -> 1 );

}

}

The coverage groups describe the features of the design. When we write a testcase to test one or more features, we add the corresponding coverage groups in it. After simulation is completed, we can view the functional coverage result of the specific design features. Just like the code coverage, VCS provides utilities to run post-process on functional coverage result.

6. Performance

We built a random verification environment for an ethernet MAC design. The following table shows the performance of the two different solutions: VCS+VERA and NTB. Obviously, the performance of NTB is much higher than another traditional solution.

7. Summary

● Synopsys VCS native testbench provides a total solution for ASIC random verification

● Performance of NTB is much higher than other traditional solutions

● VCS NTB provides a nature and smooth way to support unique Design and Verification Language SystemVerilog since VCS NTB includes both design and testbench simulation engines.