64
Vera ® Tutorial Last updated: 20 Aug 2003 Synopsys Inc. 700 East Middlefield Road Mountain View, California 94043 Tel (650) 584-5612 • Fax (650) 584-5620 http://www.synopsys.com • [email protected]

Vera Tutorial

Embed Size (px)

Citation preview

Page 1: Vera Tutorial

Vera® Tutorial

Last updated: 20 Aug 2003

Synopsys Inc.700 East Middlefield RoadMountain View, California 94043Tel (650) 584-5612 • Fax (650) 584-5620http://www.synopsys.com • [email protected]

Page 2: Vera Tutorial

VERA-VS, VERA-SV, VERA-VL, VERA-HVL, VERA Verification System, Verity, Verity ToolKit, ISDB, ISDB-cycle, and PowerFault are trademarks of Synopsys Inc. Magellan, PowerSim, SimWave, and VERA are registered trademarks of Synopsys Inc. All other trademarks are the property of their respective owners.

This software and the concepts embodied in it are proprietary and confidential in nature, and are not to be used, duplicated in whole or in part, reverse-engineered, modified, or disclosed in any manner, for any purpose whatsoever, without prior written permission from Synopsys Inc. Synopsys Inc. assumes no liability for any use of this software, and provides no warranty of any kind for the software, its documentation, or the correctness of the results. Receipt of this material shall be considered acceptance of the conditions specified herein.

Copyright © 1996, 1997, 1998, 1999, 2000, 2002, 2003 by Synopsys, Inc.All rights reserved.

PATENTS PENDING.

Vera Tutorial 6.0 (2003)

Aug, 2003

Page 3: Vera Tutorial

Tutorial Table of Contents 3

Table of Contents

1. Introduction to Vera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

2. System Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7Memory System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7System File Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9Running the Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

3. Arbiter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13Arbiter Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13Vera Testbench Key Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15Verifying the Arbiter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

4. Memory Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25Memory Controller Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25Verifying the Memory Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28Using the Vera Debugger with the cntrlr Example . . . . . . . . . . . . . . . . . . . . . . 37

5. Memory System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39Memory System Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39Verifying the Memory System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

Page 4: Vera Tutorial

4 Table of Contents Tutorial

Page 5: Vera Tutorial

Tutorial Chapter 1. Introduction to Vera 5

1. Introduction to Vera Vera is a robust and thorough verification tool for design and verification engineers. Vera is not only simple to use, it is also powerful and a lot of fun.

Vera has been recognized as the leading testbench automation tool by numerous customer evaluations and reviews. One reason we believe our product is successful is that we put great importance on the satisfaction of our customers. We are actively increasing the functionality and usability of our tool, and we listen carefully to what our customers have to say.

If you have any questions or problems, do not hesitate to contact us by email at [email protected].

Page 6: Vera Tutorial

6 Chapter 1. Introduction to Vera Tutorial

Page 7: Vera Tutorial

Tutorial Chapter 2. System Overview 7

2. System OverviewThis chapter introduces the system used for the remainder of the tutorial. It discusses briefly the components of the system and describes how they interact to complete the system. It also details the basic structure of the files used for this tutorial. This chapter includes these sections:

• Memory System• System File Setup

2.1 Memory SystemThe system used in this tutorial is a simple memory system for a two CPU machine. It consists of a system bus, a centralized round-robin arbiter, and a memory controller that controls four static SRAM devices. Figure 2-1 shows the system block diagram.

Figure 2-1 Memory System Schematic

SRAM

SRAM

SRAM

SRAM

MEMORYCONTROLLER

System Bus

request[0]

grant[0]

grant[1]

request[1]

ce0_N

ce1_N

ce2_N

ce3_N

addressdata

ROUND-ROBINARBITER

reset

CPU0

CPU1

rdWr_N

Page 8: Vera Tutorial

8 Chapter 2. System Overview Tutorial

Notice that the blocks labeled CPU0 and CPU1 are shaded. This is to indicate that these blocks are not part of the system under test, but rather these blocks will be modeled within our testbench. The signals shown between the CPUs and the rest of the system are the interface between the system under test and the “outside world.”

The memory system consists of the SRAMs, the Memory Controller, and the Arbiter. These files are all described in the HDL files of each sub-module. The approach used to verify the memsys system is similar to most project verification flows:

1. sub-modules are individually verified,

2. sub-modules are integrated into the final design.

This “full chip” functionality is verified in the system simulation.

First, in Chapter 3 of the tutorial, the arbiter sub-module is verified. To do this, the surrounding blocks in the Vera testbench are modeled.

Second, in Chapter 4, the memory controller sub-module is verified. For this module level verification, both the CPU interface and the memory interfaces are designed with Vera. This gives us a chance to show some of the advanced features in Vera that are used to verify protocol based designs.

Finally, Chapter 5 verifies the complete system by integrating the arbiter and controller sub-modules as shown in Figure 2-1 with a Vera model of the CPUs instantiated in the testbench. Several different features of Vera are used in different approaches. We also introduce Object Oriented Programming (OOP), Functional Coverage, and Interprocess Communication using Triggers and Mailboxes.

Page 9: Vera Tutorial

Tutorial Chapter 2. System Overview 9

2.2 System File SetupThe tutorial’s directory structure is shown below.

README: short description and file/directory index and listing of tools and versions used

sram: contains the memory RTL

arb: contains the submodule RTL and test directory

cntrlr: contains the submodule RTL and test directory

memsys: contains the top-level RTL netlist that integrates the entire memsys design and the test directory

Each “rtl” directory contains both VHDL and Verilog HDL code.

You will be working inside the arb, cntrlr, and memsys test directories where you will be creating your Vera testbench. Each test directory, contains the solution or testbench for each module. You can refer to this solution while creating your own testbench.

The diagram below, shows the general “test” directory structure for each module.

include: contains Vera interface and ports and binds files for arb and memsys

source: contains Vera testbench running both Verilog and VHDL code

vera_out: compilation and runtime generated files go here

run_scr: cshell and make run scripts for various simulators are stored here

sram arb cntrlr

new_memsys

memsys

rtl

sram.v

rtl test

arb.v

rtl

cntrlr.v

test

memsys.v

sram.vhdarb.vhd

cntrlr.vhdmemsys.vhd

rtl test

arb_top.vhdcntrlr_top.vhd

memsys_top.vhd

README

Page 10: Vera Tutorial

10 Chapter 2. System Overview Tutorial

You use the test directory for creating testbench, run scripts, compilation, and simulation generated files and directories. The files contained in the include, source, and “run_scr” directories are tutorial solutions.

2.3 Running the TutorialBefore running the tutorial you should customize the setup script found in the current test directory. The test directory can be found in new_memsys/PROJECT_DIR/test. Where PROJECT_DIR is either arb, cntrlr, or memsys. Within the setup script you will be setting tool specific settings for Vera and your simulator.

After you have finished editing, source the setup file:

>source setup

The tutorial contains a combination of makefiles and run scripts for execution of the tutorial solutions that run on the supported simulators shown in Table 2. The options for invoking the simulation are best handled from the Makefile included in new_memsys/PROJECT_DIR/test. This Makefile abstracts the commands for all supported simulators:

Table 1: Supported Simulators

Platform VCS VCS-MX MTI VHDL MTI Verilog NC Verilog

Solaris 5.7, 5.8 X X X X X

RedHat 7.2 X X X X X

include source

current test directory

vera_out

makefile

run_scr

interface.vri testbench.vr empty beforeport_binds.vri simulation

Makefilesetup

Page 11: Vera Tutorial

Tutorial Chapter 2. System Overview 11

The Makefile is compatible with both make and gmake. To see the list of options invoke the Makefile with either make/gmake or make –help / gmake –help. For example from within new_memsys/memsys/test:

make -help

Vera Tutorial Makefile

Note – You must edit and source setup to customize your environment

General Options:

make help : displays this messagemake clean :cleans all files created during compilation and runtimemake cleanall :cleans all files created during compilation and runtime

Synopsys Simulation

make vcs : run with VCS (Verilog)make vcs-mx : run with VCS-MX (VHDL)

MTI Simulation

make mti_vlog : run with MTI (Verilog)make mti_vhdl : run with MTI (VHDL)

NC Simulation

make nc-vlog : run with NC (Verilog)

Coverage Report Options

make html : generates coverage report and opens under Netscape.make text : generates coverage report and opens under more.

Note – The Makefile will run the solution files not your custom file.

Examples of Usage:

To run the solution for VCS:

>gmake cleanall>gmake vcs

To run the solution for MTI VHDL:

>make cleanall>make mti_build>make mti_vhdl

Page 12: Vera Tutorial

12 Chapter 2. System Overview Tutorial

Page 13: Vera Tutorial

Tutorial Chapter 3. Arbiter 13

3. ArbiterThis chapter focuses on the arbiter’s roll in the design. It briefly describes what the arbiter does, including a short timing and logic discussion. The chapter then describes the Vera methodology and functionality used to verify the arbiter section of the system. This chapter explains how Vera interacts with both a Verilog and a VHDL design to drive signals, how the connections between the testbench and DUT are made, and how some of the basic signal operations behave. This chapter is divided into these sections:

• Arbiter Overview• Vera Testbench Key Components• Verifying the Arbiter

3.1 Arbiter OverviewOne reason Vera is flexible is because the same Vera testbench works with devices described using Verilog or VHDL. Once Vera is hooked up to the simulator, everything in Vera stays the same even though the simulator is changed.

In this section, you will be working inside the arb/test directory.

• The tutorial arbiter Vera source file solution is in this file:

new_memsys/arb/test/source/arb.vr

• The VHDL arbiter RTL source code is in the file:

new_memsys/arb/rtl/arb.vhd

• The Verilog arbiter RTL source code is in this file:

new_memsys/arb/rtl/arb.v

• The Tutorial solution run scripts for Verilog/VHDL simulators are in the following directory:

new_memsys/arb/test/run_scr

• The Tutorial solution Vera interface file is in the following file:

new_memsys/arb/test/include/arb.if.vri

• Tutorial solution Vera compile output files are written to the following directory:

new_memsys/arb/test/vera_out

Page 14: Vera Tutorial

14 Chapter 3. Arbiter Tutorial

• Tutorial Makefile and setup script is located in:

new_memsys/arb/test

Make sure you edit setup for your installation and then source the file.

Figure 3-1 shows the arbiter timing diagram.

Figure 3-1 Arbiter Timing Diagram

The arbiter implements a round-robin arbitration algorithm between two CPUs. Each CPU can drive a request input signal (request[0] or request[1]). The arbiter queues the requests and determines which CPU will gain access to the system bus. The arbiter grants this access by asserting one of the grant output signals (grant[0] or grant[1]). While the grant signal is asserted for a given CPU, the CPU continues to assert its request signal so that both the grant and request signals for the CPU remain high while the CPU accesses the system bus. Once the CPU is done, it de-asserts its request signal and, on the next subsequent clock cycle, the arbiter de-asserts its grant signal. With all the signals de-asserted, the cycle can continue with the next request.

00 01 00 10 00 11

00

xx

xx 01 00 10 00

11

01

10

00 10

00

0000

clk

request

grant

reset

clk

request

grant

reset

Page 15: Vera Tutorial

Tutorial Chapter 3. Arbiter 15

3.2 Vera Testbench Key ComponentsA Vera testbench suite is comprised of several key components:

• Vera Testbench Module - Vera testbench• Interface Specification - Defines the Vera signals for the testbench module.

Typically the interface specification is contained in a file with a name of this format: filename.if.vrh or filename.vri. The vrh stands for Vera compiler generated interface file or header file, and the .vri suffix denotes user generated interface or header file.

• Vera Shell File (filename.vshell or filename_shell.vhd) - Verilog and VHDL, respectively, file that acts as a wrapper or gasket around the testbench module. The Vera testbench drives the DUT through this shell file. The shell file also contains all the PLI calls in Verilog and the simulator interface calls in VHDL required to run the testbench.

• Test-top File (filename.test_top.v or filename_top.vhd) - Top level netlist file that encapsulates the DUT and the Vera testbench suite. It instantiates the DUT and the shell file, handles the clock generation, and handles file dumping in Verilog.

Figure 3-2 shows the basic schematic for this configuration.

Figure 3-2 Test-top Configuration Schematic

DeviceUnder Test

Clock Generator

Test-top file

Vera Shell FileDevice Under Test

VeraTestbenchModule

Interface Specification

Input Signals

Output Signals

Page 16: Vera Tutorial

16 Chapter 3. Arbiter Tutorial

3.2.1 How to Hook Up Verilog

Vera provides a template generator to assist in the setup of this configuration. Within the arbiter working directory (new_memsys/arb/test), invoke the template generator:

vera -tem -t arb -c clk ../rtl/arb.v

The -tem compiler option invokes the Vera template generator. The -t switch defines the top level name of the circuit under test as arb. The -c switch defines the clock signal to be used in the generated interface. The specified file (../rtl/arb.v) is the RTL source code from which the template files are generated. Note that the names of the generated files are derived from the top-level RTL filename.

Invoking the Vera template generator command will create the following output:

Parsing ../rtl/arb.v..

Writing top_file to arb.test_top.v

Writing vera interface file to arb.if.vrh

Writing vera template file to arb.vr.tmp

Done.

As an alternative, you could use the interface wizard to create the interface defintion. See the Vera user guide for more information about the wizard.

arb.test_top.v

The generated arb.test_top.v file is the Verilog test-top file. It contains the signal and wire declarations that connect the Vera testbench to the DUT. The declarations are made using the top level RTL (arb.v). The test-top file also instantiates the Vera shell file (vera_shell). Finally, the test-top file defines a clock generator (SystemClock) that is passed to the Vera interface as the clk signal. This could be hand generated: the instantiations and interconnections between the DUT and the Vera shell, clock generators, and any needed infrastructure being written in plain Verilog.

arb.if.vrh

The generated arb.if.vrh is the Vera interface file. It contains the Vera signal declarations made within the arb interface. The signal names are taken from the top level RTL (arb.v). Signals declared as outputs in the RTL are declared as inputs in the Vera interface (and vice versa). Bidirectional signals remain bidirectional. Input signals are given the default skew of -1 and output signals are given the defaut skew of +1. Signals are driven or sampled on the positive edge of the interface clock (clk in this example). You can customize the interface by editing this file if you want to.

Page 17: Vera Tutorial

Tutorial Chapter 3. Arbiter 17

Note that each interface has a clock associated with it by which all timing takes place. All signal operations occur on the corresponding interface clock edge. For example, given an interface with drives occurring on positive clock edges and a skew of 1, the timing diagram is given by:

The Vera shell file connections to the HDL simulation are generated from the interface declarations when the Vera program file is successfully compiled.

arb.vr.tmp

The generated arb.vr.tmp is the Vera template testbench file. It contains preprocessor directives that include the vera_defines.vrh header file as well as the arb.if.vrh interface file.

Rename the arb.vr.tmp to arb.vr.

3.2.2 How to Hook Up VHDL (VCS-MX)

This description shows how to hook up Vera to VCS-MX. Other VHDL simulators are similar. See the README file in the Vera installation directory:

$VERA_HOME/doc/README.simulators.

Also, see $VERA_HOME/doc/install/install.pdf for the installation directions.

Vera provides a toplevel testbench template generator to assist in the setup of connecting Vera to the DUT.

Within the arbiter working directory (new_memsys/arb/test) create an interface declaration. For this tutorial call the file arb.if.vri and use it to define the interfaces of all the signals Vera will be connecting to in the device. Then create an “empty” arb.vr file that simply includes the arb.if.vri file and the vera_defines.vrh. The content of the arb.vr file should look like:

#include <vera_defines.vrh>

#include “arb.if.vrh”

The arb.if.vri file contains the Vera signal declarations made within the arb interface. Signals declared as outputs in the RTL are declared as inputs in the Vera interface (and vice versa). Bidirectional signals remain bidirectional. Input signals are given the default skew of -1 and

clk

request

grant

request driven1 time unit afterdriving clock edge

grant driven 1time unit after nextdriving clock edge

Page 18: Vera Tutorial

18 Chapter 3. Arbiter Tutorial

output signals are given the defaut skew of +1. Signals are driven or sampled on the positive edge of the interface clock (clk in this example). You can customize the interface by editing this file if you want to. These signals correspond to signals in the DUT.It is recommended that the customized interface file name use a “.vri” suffix to indicate a user-edited file as opposed to compiler generated. Also, is also recommended that non-zero hold and setup delays are defined. This pulls these delays away from the clock edges and can more realistically model the back-annotated delays of the actual device.

Your arb.if.vri file should look like:

interface arb {

input clk CLOCK ;

output reset PHOLD #1 ;

output [1:0] request PHOLD #1 ;

input [1:0] grant PSAMPLE #-1 ;

} // end of interface arb

Note that each interface has a clock associated with it by which all timing takes place. All signal operations occur on the corresponding interface clock edge. For example, given an inter-face with drives occurring on positive clock edges and a skew of 1, the timing diagram is given by:

After you have created the interface arb.if.vri and the program template file arb.vr, you need to create the VHDL code to hook up Vera to the VHDL simulator. The -sro switch is used with VCS-MX for VHDL only. Type vera -help for other VHDL simulation options including mixed language support.

To create the VHDL code, type:

vera -cmp -sro -top arb.vr

The –top compiler option invokes the Vera template generator. The -sro switch creates the appropriate top level file as well as the shell.vhd file for the VCS-MX simulator.

clk

request

grant

request driven1 time unit afterdriving clock edge

grant driven 1time unit after nextdriving clock edge

Page 19: Vera Tutorial

Tutorial Chapter 3. Arbiter 19

Invoking the Vera template generator creates these files:

• arb_top.vhd • arb_shell.vhd • arb.vro

arb_top.vhd

The arb_top.vhd file is the VHDL test-top file. It contains the signal and wire declarations that connect the Vera testbench to the DUT. The declarations are made using the top level RTL (arb.vhd). The test-top file also instantiates the Vera shell file (vera_shell). Finally, the test-top file defines a clock generator (SystemClock) that is passed to the Vera interface as the clk signal.

Now edit the arb_top.vhd to give it the name of the entity and details of how the wires hook up from the Vera entity to the VHDL entity. Follow the directions listed in the comments at the top of arb_top.vhd. You may also refer to ../rtl/arb.vhd for more details on the VHDL DUT.

arb_shell.vhd

The arb_shell.vhd is the interface from the Vera testbench to the device. Be sure to call it first in the command line of the VHDL compiler before arb_top.vhd, otherwise the VHDL simulator could get confused about the missing Vera entity.

arb.vro

The arb.vro is the compiled Vera testbench contained in arb.vr. The Vera .vr file instructions are used by the Vera simulator to test the DUT. New code can be complied into the arb.vro file with the command

vera -cmp arb.vr

however, this is not necessary at this point as you have not added anything new to the arb.vr file.

3.3 Verifying the ArbiterIdentify the required tests. First, verify the arbiter reset. Second, verify the arbiter handles simple requests appropriately and can grant access to one of the CPUs. Finally, check for proper Arbiter handling of request sequences.

3.3.1 Reset Verification

Verify resets are working correctly. First assert the reset signal. With the reset signal asserted, hold the request signals inactive for each CPU (drive them to 0) and check that the grant signals are at their inactive states (0) after the reset.

Page 20: Vera Tutorial

20 Chapter 3. Arbiter Tutorial

Referencing Vera Signals

To reference a Vera signal, specify the interface name and the Vera signal name. Using our arb interface, the reset, request, and grant signals are referenced as:

arb.reset

arb.request

arb.grant

Basic Signal Operation

All signal operations occur on the clock edge specified in the interface. If an output signal is marked PHOLD, all drives occur on the positive edge of the interface clock. Similarly, input signals marked PSAMPLE are sampled on the positive edge of the interface clock.

To advance the simulation to the next change of a specified signal, use the synchronize construct:

@(clock_edge signal_name);

This advances the simulation to the next specified edge of the signal. If the clock edge is omitted, it advances the simulation to the next sampling edge that indicates a signal change.

To assert and de-assert the signals, use the Vera drive construct:

@n signal_name = value;

The specified signal is driven to the appropriate value after n clock cycles pass. If the delay is omitted, the drive occurs on the next driving edge as defined in the interface (positive clock edge in our example).

To check that a signal has a specific value at a specified time, use the Vera expect construct:

@n signal_name == value;

The specified signal is compared to the given value after n clock cycles pass. If the signal value is the same as the specified value, the simulation continues. If there is a mismatch, a verification error occurs, the simulation terminates, and an error message is displayed (note that the error mode can be set so that errors do not terminate the simulation using the soft keyword). The soft keyword should be used in conjunction with the flag() method to determine if the expect was satisfied.

Generally, it is best to sample signals slightly before the rising edge of the clock to avoid race conditions. For this purpose, define an input skew of -1 unit inside the arb.vr.tmp file.Below the already included #define statements add:

#define INPUT_SKEW #-1

This define should be set in arb .vr following the other defines generated by the template generator or by the user.

Page 21: Vera Tutorial

Tutorial Chapter 3. Arbiter 21

Verifying the Reset

Using the basic signal operations described earlier, add this code to the arb.vr.tmp file to verify the resets:

arb.reset = 1; //assert reset

@1 arb.reset = 0; // de-assert reset after 1 clock cycle

@0 arb.request = 2’b00; // de-assert request on next positive clock edge

@1 arb.grant==2’b00; // check that grant is de-assert after 1 clock cycle

Note that the request and grant signals are 2-bit signals. Each bit of the signals must be de-asserted.

Running the Simulation with VCS

At this point you should be in the arb/test directory. See the “run_scr” directory for different simulator run scripts and the makefiles for both Verilog and VHDL. If you want to use any of these to run the tutorial solution, invoke the script from the test directory. Also see the README file description containing the simulation output.

NOTE: The prebuilt scripts will run the solution, not your custom code. To compile and run your code you must follow the steps outlined below:

With the code added to the arbiter testbench (arb.vr.tmp), run the simulation and test the results. First, verfiy that you have renamed arb.vr.tmp to arb.vr. Compile the Vera testbench:

vera -cmp arb.vr

Compiling the testbench generates the Vera shell file (arb.vshell) and the Vera testbench binary object file (arb.vro).

Run the simulation:

vcs arb.test_top.v ../rtl/arb.v arb.vshell -vera

simv +vera_load=arb.vro

Your test should run to completion without any errors and the output should be as follows:

Compiler version 7.0.1; Runtime version 7.0.1; Aug 6 17:18 2003

Vera: finish encountered at time 250 cycle 3

total mismatch: 0

vca_error: 0

fail(expected): 0

drive: 3

expect: 1

sample: 0

sync: 0

Page 22: Vera Tutorial

22 Chapter 3. Arbiter Tutorial

If there are verification errors when the simulation is run, the simulation terminates and an error message is reported. For instance, change the grant de-assertion line within the arb.vr file so that it is incorrect:

@1 arb.grant==2’b01;

Recompile the Vera code and run the simulation again. (The HDL does not need to be recompiled when only the Vera code is changed.) In this case, the testbench expects that the grant signal is asserted while the Verilog model continues to de-assert the signal as before. This results in an expect mismatch and a verification error as shown below.

Note – Remember to edit the testbench file to correct this error before continuing.

You will observe the following output:

Compiler version 7.0.1; Runtime version 7.0.1; Aug 6 17:21 2003

EXPECT MISMATCH

TIME: 250 CYCLE: 3

Signal: arb.grant.0

Exp Value: 1 : 01

Actual Value: 0 : 00

VERIFICATION ERROR: Expect mismatch Location: WAIT_ON_EXPECT in program

arb_test (arb.vr, line 14, cycle 3)

$stop at time 250 Scope: arb_test_top.vshell File: arb.vshell Line: 50

Running the Simulation with VCS-MX

At this point you should be in the arb/test directory. See the “run_scr” directory for different simulator run scripts and the makefiles for both Verilog and VHDL. If you want to use any of these to run the tutorial solution, invoke the script from the test directory. Also see the README file description containing the simulation output.

With the code added to the arbiter testbench (arb.vr), run the simulation and test the results. Compile the Vera testbench:

vera -cmp arb.vr

Compiling the testbench generates the Vera testbench binary object file (arb.vro). These files need to be included when the simulation is run.

1) Create .synopsys_vss.setup file

echo "WORK > DEFAULT" > .synopsys_vss.setup

echo "DEFAULT : work" >> .synopsys_vss.setup

echo "TIMEBASE = ns" >> .synopsys_vss.setup

Page 23: Vera Tutorial

Tutorial Chapter 3. Arbiter 23

2) Create vera.ini file with inclusion of arbiter object code

echo "vera_load=./vera_out/arb.vro" > ./vera.ini

echo "vera_continue_on_error ON" >> ./vera.ini

3) Analyze vhdl source code

vhdlan -nc -event ../rtl/arb.vhd

vhdlan -nc -event ./vera_out/arb_shell.vhd

vhdlan -nc -event ./vera_out/arb_top.vhd

4) Create scsim (compile)

scs -nc DUT_BENCH_CFG

5) Create run script (optional)

echo "# "> ./vera_out/sc.do

echo "#Type ’run’ to start simulation" >> ./vera_out/sc.do

echo "# " >> ./vera_out/sc.do

echo "run " >> ./vera_out/sc.do

6) Run simulation

scsim -nc -include ./vera_out/sc.do

3.3.2 Simple Request Verification (Verilog and VHDL)

To check if the arbiter is handling simple requests correctly, monitor the request signals, check that the grant signal is set appropriately, and then check that the grant signal is de-asserted after the request is released.

Test For Simple Request by CPU0

To test that simple requests are handled correctly for CPU0, drive bit 0 of the request signal and then monitor bit 0 of the grant signal. Finally, de-assert both bits of the request signal and check that both signals of the grant signal are properly de-asserted.

@0 arb.request = 2’b01; // assert bit 0 of request

@2 arb.grant == 2’b01; // check that bit 0 of grant is asserted

@0 arb.request = 2’b00; // de-assert bit 0 of request

@2 arb.grant == 2’b00; // check that both bits of grant are de-asserted

Test For Simple Request by CPU1

To test that simple requests are handled correctly for CPU1, drive bit 1 of the request signal and then monitor bit 1 of the grant signal. Finally, de-assert both bits of the request signal and check that both signals of the grant signal are properly de-asserted.

Page 24: Vera Tutorial

24 Chapter 3. Arbiter Tutorial

@0 arb.request = 2’b10; // assert bit 1 of request

@2 arb.grant == 2’b10; // check that bit 1 of grant is asserted

@0 arb.request = 2’b00; // de-assert bit 0 of request

@2 arb.grant == 2’b00; // check that both bits of grant are de-asserted

3.3.3 Sequenced Request Verification

Verify sequences of requests are handled properly by checking a series of conditions:

• Assert both request signals and check for correct grant assertion• Release the granted request and check for grant release• Assert both request signals and check for correct grant assertion• Release the newly granted request and check for grant release• Check for new grant assertion• Release last request and check that both grants are released

Given this verification methodology, the code to check arbiter behavior is:

@0 arb.request = 2’b11; // assert both request signals

@2 arb.grant == 2’b01; // check for first grant

@0 arb.request = 2’b10; // de-assert corresponding request

@1 arb.request = 2’b11; // assert both request signals

@1 arb.grant == 2’b00; // check that grant de-asserts for 1 cycle

@1 arb.grant == 2’b10; // check that other grant is asserted

@1 arb.request = 2’b01; // de-assert corresponding request

@2 arb.grant ==2’b00; // check that grant de-asserts for 1 cycle

@1 arb.grant == 2’b01; // check for first grant

@1 arb.request = 2’b00; // de-assert both request signals

@2 arb.grant == 2’b00; // check that both grant signals are de-asserted

Given this testing configuration, there is no way to ensure that grant does not change unpredictably (it is only checked using the expects). To check for unexpected changes, use Vera’s Value Change Alert (VCA). The VCA generates a verification error when unexpected changes occur. To enable the VCA, the signal declaration in the interface file for the signal being monitored must include the vca keyword as shown. This has already been included in the ./include/arb.if.vri:

input [1:0] grant INPUT_EDGE INPUT_SKEW vca r0;

This signal declaration enables the VCA for the grant signal, assigning a default quiescent value of 0 to the signal. To use the VCA, turn it on from within the testbench (before the test sequence begins):

vca(ON, arb.grant);

When the VCA is turned on, any change in signal grant that is not expected (by an expect statement) or explicitly driven generates a verification error. Comment out one of the expect statements and run the simulation, the now unexpected signal change generates an error.

Page 25: Vera Tutorial

Tutorial Chapter 4. Memory Controller 25

4. Memory ControllerThis chapter discusses the memory controller portion of the design. It gives an overview of how the memory controller functions. It discusses some of the major features of Vera that are used to verify the controller, including a description of virtual ports and binds as well as synchronous and asynchronous events. These concepts are presented within the verification framework so that you can learn how to adequately validate our memory controller. This chapter includes these sections:

• Memory Controller Overview • Verifying the Memory Controller• Using the Vera Debugger with the cntrlr Example

4.1 Memory Controller OverviewIn our system, the CPU accesses the bus through the arbiter. Once the CPU has access, it puts its request on the system bus. The memory controller acts on this request by reading data from the SRAM devices and returning data when necessary. All edits will be performed inside the new_memsys/cntrlr/test directory.

• The tutorial controller Vera source file solution is in this file:

new_memsys/cntrlr/source/cntrlr.vr

• The VHDL controller RTL source code is in the file:

new_memsys/cntrlr/rtl/cntrlr.vhd

• The Verilog controller RTL source code is in this file:

new_memsys/cntrlr/rtl/cntrlr.v

• The project Makefile is located in :

new_memsys/cntrlr/test

type ‘make help’ for details

• The Tutorial solution Vera interface declaration is in the following program file:

new_memsys/cntrlr/test/source/cntrlr.vr

• The Tutorial solution Vera compile output files are written to the following directory:

new_memsys/cntrlr/test/vera_out

Page 26: Vera Tutorial

26 Chapter 4. Memory Controller Tutorial

• Tutorial Makefile and setup script is located in:

new_memsys/cntrlr/test

Make sure you edit setup for your installation and then source the file.

The memory controller reads requests from the system bus and generates control signals for the SRAM devices attached to it. For read requests, the controller reads data and transfers it back to the bus and the CPU making the request. The address bus is 8 bits wide, which creates an address space of 256 bytes. The controller supports up to 4 devices, allocating a maximum of 64 bytes of memory to each. The controller decodes the address and generates the chip enable for the corresponding device during a transaction. Figure 4-1 shows a diagram of how Vera works with both the system bus and SRAM device signals.

Figure 4-1 Vera/Memory Controller Interaction

Vera

MEMORYCONTROLLER

SRAM Side System Bus side

adxStrbbusAddr

busDatabusRdWr_Nce_N

ramData

ramAddrrdWr_N

Page 27: Vera Tutorial

Tutorial Chapter 4. Memory Controller 27

Figure 4-2 and Figure 4-3 show the timing diagrams for the memory controller’s read and write operations respectively (note the signal names as you will be using them in the verification process)

Figure 4-2 Memory Controller Read Operation Timing Diagram

clk

reset

adxStrb

busAddr

busData

busRdWr_

valid

cex_

ramData

ramAddr

rdWr_

valid

valid

valid

Page 28: Vera Tutorial

28 Chapter 4. Memory Controller Tutorial

Figure 4-3 Memory Controller Write Operation Timing Diagram

4.2 Verifying the Memory ControllerTo completely check the functionality of the memory controller, perform a series of tests. First, check the read and write capabilities of the controller. To do this, create Vera tasks that drive the bus for read and write operations. Then check the integrity of the read and write operations. Finally, exhaustively check the address map (all 256 addresses) for the read and write functions.

Note that this chapter checks the memory controller by emulating both the system bus and the memory bus behavior.Rather than connecting the rtl models of the memory to the controller, model the behavior of the 4 different memory devices in Vera.

To start the verification for Verilog designs, create the template files using the -tem switch as described with the arbiter verification:

vera -tem -t cntrlr -c clk ../rtl/cntrlr.v

To start the verification with VHDL designs, follow the steps in the previous chapter for arb, but now working with cntrlr.vhd. You are provided an example of the Vera interface definition inside the controller testbench program file ./cntrlr/source/cntrlr.vr.

clk

reset

adxStrb

busAddr

busData

busRdWr_

valid

valid

cex_

ramData

ramAddr

rdWr_

valid

valid

Page 29: Vera Tutorial

Tutorial Chapter 4. Memory Controller 29

4.2.1 Driving the System Bus For Read and Write Operations

In testing the read and write capabilities of the controller, create two Vera tasks that drive the bus for read and write operations.

Read Operation

Create a task that drives the read operation onto the system bus as specified in the timing diagram for the controller. The task should use an 8-bit bus address as an input. Given this requirement, the read operation task is:

task readOp (bit[7:0] adx)

{

cntrlr.busAddr = adx;

cntrlr.busRdWr_ = 1’b1;

cntrlr.adxStrb = 1’b1;

@1 cntrlr.adxStrb = 1’b0;

}

This task is passed the argument adx. It then drives the busAddr signal to that value. Finally, it drives the busRdWr_ and adxStrb signals such that they match the timing diagram for the read operation of the controller.

Note: do not drive the data onto the bus and check for the expected data here. Before checking for the expected data, check that the read operation displays the correct waveform at the SRAM interface. When checking the entire system in Chapter 5 ”Memory System”, this check is made using multiple threads.

Write Operation

Create a task that drives the write operation onto the system bus as specified in the timing diagram for the controller. The task should use 8-bit address and data busses as inputs. Finally, the task should leave the bus in an idle state (defined when busData is in high z and busRdWr_ is de-asserted). Given these requirements, the write operation task is:

task writeOp (bit[7:0] adx, bit[7:0] data)

{

@1 cntrlr.busAddr = adx;

cntrlr.busData = data;

cntrlr.busRdWr_ = 1’b0;

cntrlr.adxStrb = 1’b1;

@1 cntrlr.busRdWr_ = 1’b1;

cntrlr.busData = 8’bzzzzzzzz;

cntrlr.adxStrb = 1’b0;

}

Page 30: Vera Tutorial

30 Chapter 4. Memory Controller Tutorial

This task is passed the argument adx. It then drives the busAddr signal to that value. Finally, it drives the busData, busRdWr_, and adxStrb signals such that they match the timing diagram for the write operation of the controller.

4.2.2 Implementing Virtual Ports

Vera’s virtual ports allows the grouping of Vera interface signals into logical bundles. These signals can be passed to tasks that you want to act on specific sets of signals. This is done by defining a virtual (or generic) port, which is a set of generic port signal names that act as placeholders for the actual interface signals they are bound to. The virtual ports are then bound to specific interface signals as needed. This feature allows a task to be written once, then re-used many times at different interfaces to the design under test. The port variable allows task and function reuse by giving the verification engineer the ability to pass task or function specific interface connections to both tasks and functions. So in essence, Vera turns interface connections into parameters that can be passed around the testbench as needed. The only limit is the limit of the verification engineer’s imagination.

Defining Virtual Ports

Vera virtual ports are defined outside the main program block using this construct:

port port_name {port_signal_member1; ...; port_signal_memberN;}

port_name - The port_name must be a valid identifier.

port_signal_memberN - port_signal_memberN must be a valid identifier. Multiple port signal names

are separated by semi-colons (;).

Binding Virtual Port Signal Members to Interface Signals

The bind construct (For a complete discussion see “bind Construct for Static Connection,” in the Vera User Guide) not only associates port-signal-members with interface signals, but also involves declaring a port variable as well. Outside the main program block, use the bind construct:

bind port_name port_variable

{

port_signal_memberN interface_name.signal_name;

}

port_name - The port_name is the user defined virtual port whose signal member names you want

associated with interface signals.

port_variable - The port_variable is the name of the variable being declared.

port_signal_member - The port_signal_memberN is the name of the generic signal names you are

including in the bind. Generally, all of the signals in the port are bound. However, you can bind

selected signals if you want, and leave others unbound.

Page 31: Vera Tutorial

Tutorial Chapter 4. Memory Controller 31

interface_name - The interface_name is the name of the interface to which you are binding the port

signal members.

signal_name - The signal_name is the name of the signal you are binding to a particular port signal

member. You can specify signal subfields using signal_name[x:y].

Referencing Ports and Binds

To reference or pass a port to a subroutine, use port variables. Port variables store virtual port/bind pairs. Each virtual port definition becomes a new data type (much like enumerated types) that can be used to declare new port variables. The syntax to declare a port variable, when not using the bind construct, is:

port_name port_variable = initial_value;

port_name - The port_name is the name of the port data type.

port_variable - The port_variable is the name of the port variable you are declaring.

initial_value - The initial_value can be any existing port of the same type as the port variable. If it is

not set, the port_variable has a NULL value until it is assigned a port.

To reference individual port signals within a subroutine, use this construct:

$signal_name

This references the specified port signal in the bind passed to the subroutine.

Implementing Ports and Binds in the Memory Controller

Given the port/bind methodology presented here, define a device port for the SRAM parts (ramAddr, ramData, rdWr_, and ce_):

port device

{

ramAddr;

ramData;

rdWr_;

ce_;

}

After defining the virtual port, connect the port signals to actual interface signals using the bind construct:

bind device device0

{

ramAddr cntrlr.ramAddr;

ramData cntrlr.ramData;

Page 32: Vera Tutorial

32 Chapter 4. Memory Controller Tutorial

rdWr_ cntrlr.rdWr_N;

ce_ cntrlr.ce0_N;

}

This bind construct results in the port variable device0 of port type device. It connects the port signals to their corresponding interface signals. Note that the ce_ signal is connected to its device-specific signal. Similar binds for each device (device1, device2, and device3) should be constructed.

4.2.3 Verifying Read and Write Operations

The memory controller issues read and write operations to each of the four SRAM devices as shown in the earlier timing diagram. Create read and write tasks in our testbench that check these operations. Earlier, we modeled the timing diagram exactly, cycle by cycle. Our approach now is to make use of Vera’s timing windows, which allow you to specify ranges of time and event sequences.

Because of complex timing issues with the read operation, examine the write operation first. A discussion of the timing issues and the read operation follows.

Timing Windows

Vera provides timing windows for its expect signal operation. The syntax is:

@window signal_name == value;

The window of time for which the check is made must be in the form x,y. The check begins x cycles

after the call is made and continues for y cycles after the call is made. If the x is omitted (,y), the check

is made immediately and lasts y cycles after the call. The signal value must match the expected value

for the duration of the check. This mechanism provides a means to evaluate a signal over a specified

period of time. For more details, see the Vera User Guide.

Verifying the Write Operation

To verify the write operation, create a Vera task that checks the SRAM write operation against the timing diagram provided. The task should have an argument of port variable type device so that we can pass in the signals we want it to act on. The task also has 6-bit address and 8-bit data busses as inputs. It must check that the SRAM signals are driven correctly, check that the address is the right address, and drive the data onto the ramData bus at the appropriate time. Given these requirements, the code is:

task checkSramWrite (device d, bit[5:0] adx, bit[7:0] data)

{

@1,5 d.$ramAddr == adx;

@,2 d.$ramData == data;

@1 d.$rdWr_ == 0;

d.$ce_ == 0;

d.$ramData == data;

Page 33: Vera Tutorial

Tutorial Chapter 4. Memory Controller 33

d.$ramAddr == adx;

@1 d.$rdWr_ == 1;

d.$ce_ == 1;

d.$ramData == data;

d.$ramAddr == adx;

@1 d.$ramData == 8’bzzzzzzzz;

}

This task checks that the address (ramAddr) is valid over the timing window 1-5 cycles after the call is made. The the write data (ramData) is checked for two cycles from that point. After checking these signals, check that rdWr_ and ce_ are asserted simultaneously for exactly one cycle, and check that the address and write data remain valid. Next check that rdWr_ and ce_ are de-asserted, and check that the address and write data are still valid. After the checks, make sure ramData returns to tri-state.

Synchronous and Asynchronous Timing

By default, all Vera signal operations are synchronous. That is they occur on the clock edges specified in the interface specification. However, all Vera signal operations can be used asynchronously by adding the async keyword after the operation:

@(edge signal_name async); //advance to next edge of signal

signal_name = value async; // drive new value immediately

signal_name == value async;// execute expect expression immediately

Note that the delays for the drive and expect operations are not used since they occur immediately.

These are examples of async statements:@(posedge main_bus.request async);memsys.data[3:0] = 4’b1010 async;data[2:0] = main_bus.data[2:0] async;main_bus.data[7:4] == 4’b0101 async;

Verifying the Read Operation

To verify the read operation, check that the control signals are asserted, the correct address is driven by the memory controller, and the input data is driven as return data. However, an interesting timing issue arises in this case. The SRAM device drives data after the corresponding ce_ signal is asserted. This must happen in the same clock cycle for the device

Page 34: Vera Tutorial

34 Chapter 4. Memory Controller Tutorial

to work. However, because of the sampling skew, ce_ is sampled just after the rising clock edge. This means that the data is driven on the next rising clock edge, which is invalid. This timing diagram shows this behavior:

With this in mind, create a read task that checks the read operation against the timing diagram provided. The task must have an argument of type device to pass in the virtual port. It also has 6-bit address and 8-bit data busses as inputs. This is the code:

task checkSramRead (device d, bit[5:0] adx, bit[7:0] data)

{

@1,5 d.$ramAddr == adx;

@(d.$ce_ async);

d.$ce_ == 0 async;

d.$rdWr_ == 1 async;

d.$ramAddr == adx async;

d.$ramData = data async;

@1 d.$ramData <= 8’bzzzzzzzz;

}

This task first checks that the address is valid over the specified window of time. Next advance the simulation to the exact change of the chip enable signal (ce_) using the synchronize construct. Use the async form because we want this change to happen immediately without waiting for the next sampling edge. Next, immediately check that ce_ is 0, rdWr_ is de-asserted, and ramAddr has the appropriate value. After these checks, drive the data (ramData) immediately. Use the async construct here so that the drive is done immediately after the checks and not on the next rising clock edge. Finally, drive the data back to tri-state at the next rising clock edge (note the use of the <= drive operator, which indicates a non-blocking drive so that execution continues immediately).

clk

ce_

ce_ is drivenjust after risingedge

data VALID

data should bedriven here

data is driven herebecause of sampling skews

Page 35: Vera Tutorial

Tutorial Chapter 4. Memory Controller 35

Running the Simulation

Before running the simulation, set up a reset check to ensure that the controller is resetting correctly. To check the controller reset, assert the reset signal and de-assert adxStrb. Next check that all the chip enables are de-asserted (cex_). Finally, de-assert the reset signal. These requirements are met with this code:

cntrlr.reset = 1’b1;

cntrlr.adxStrb = 1’b0;

@1,100 cntrlr.ce0_N == 1’b1;

cntrlr.ce1_N == 1’b1;

cntrlr.ce2_N == 1’b1;

cntrlr.ce3_N == 1’b1;

@1 cntrlr.reset = 1’b0;

With the reset check completed, write code to check the write operation of one of the devices. The code to drive the bus for the write operation is in the writeOp task, and the code to check that write operation is in the checkSramWrite task. To check the operation, use these two functions:

writeOp (8’h01, 8’h5A);

checkSramWrite (device0, 6’b000001, 8’h5A);

This code drives the bus and then checks the write operation using the specified virtual port’s signals (device0). When checking other devices, remember that each device has a range of valid addresses:

Device Valid Address Range0 0-631 64-1272 128-1913 192-255

Because the address busses are device specific, if you change the address parameter to a value that is not valid for the device you are checking, the check fails. This fails because of the address dependence in activating the chip enable signals within the RTL. To test this behavior, remember to recompile after making the changes.

Now add in the code to check the write operations for the other devices. The same tasks can be used with different virtual ports and different address parameters.

Similarly, use the generic tasks to drive the bus for read operations and check the device read operations. Remember to check that the returned data matches the return data specified in the timing diagram. The code for these checks is:

readOp (8’h03);

checkSramRead (device0, 6’b000011, 8’h95);

@1 cntrlr.busData == 8’h95;

Page 36: Vera Tutorial

36 Chapter 4. Memory Controller Tutorial

This code drives the bus and then checks the read operation using the specified virtual port’s signals (device0). Finally, the return data is checked to see that it matches the correct value.

These tests only cover a subset of the valid addresses. To exhaustively test the entire range using these calls, each task must be called with every address. To simplify this task, use virtual ports and for-loops. First, define a port variable that will have each device’s port signals assigned to it through the loop:

device dev;

This defines a variable of port type device is used to pass in the ports to each subroutine call in our loop. Now create a for loop, using a case statement to switch device ports and calling our subroutines to drive the bus and check the SRAM operations:

bit[7:0] index;

integer i;

...

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

{

index = i;

writeOp(index, 8’h5A);

case (index[7:6])

{

2’b00: dev = device0;

2’b01: dev = device1;

2’b10: dev = device2;

2’b11: dev = device3;

}

checkSramWrite (dev, index[5:0], 8’h5A);

readOp(index);

checkSramRead (dev, index[5:0], 8’h5A);

@1 cntrlr.busData == 8’h5A;

}

Each iteration of this for loop acts on a different address. It drives the bus operation and then checks the SRAM operation using the subroutines defined previously. The case statement changes the virtual port on which the subroutines act so that they use the correct signal bundles for each device. The bus data is checked at the end of each iteration to monitor the return values.

We have exhaustively tested the address space, but must also make sure that the chip enables (cex_) do not change unexpectedly through the test. Add Value Change Alert (VCA) checks to the interface specification to enable the VCA for each chip enable signal. Remember the VCA must be enabled for each chip enable signal by adding the vca r1 keywords to each signal declaration in the interface.

Page 37: Vera Tutorial

Tutorial Chapter 4. Memory Controller 37

Now turn on the VCAs before the reset check using this code:

vca(ON, cntrlr.ce0_N);

vca(ON, cntrlr.ce1_N);

vca(ON, cntrlr.ce2_N);

vca(ON, cntrlr.ce3_N);

Note that running the simulation with the VCAs enabled like this fails. This is because of our asynchronous sampling of cex_ in the checkSramRead task. So, disable the VCAs when the checkSramRead task is executed and enable them once it is completed. Do this by adding in case statements to the above block:

readOp(index);

case (index[7:6])

{

2’b00: vca(OFF, cntrlr.ce0_N);

2’b01: vca(OFF, cntrlr.ce1_N);

2’b10: vca(OFF, cntrlr.ce2_N);

2’b11: vca(OFF, cntrlr.ce3_N);

}

checkSramRead (dev, index[5:0], 8’h5A);

case (index[7:6])

{

2’b00: vca(ON, cntrlr.ce0_N);

2’b01: vca(ON, cntrlr.ce1_N);

2’b10: vca(ON, cntrlr.ce2_N);

2’b11: vca(ON, cntrlr.ce3_N);

}

After driving the bus with the readOp task, disable the VCA for the device we are checking. After the check is made, the VCA is immediately enabled.

4.3 Using the Vera Debugger with the cntrlr ExampleRefer to the Debugger tutorial by invoking “vera-doc” and selecting the debugger tutorial under application notes.

Using either or both of the following runtime options will bring up the debugger:

+vera_debug_on_start brings up the debugger immediatley before running the simulation.

+vera_debug_on_error causes the debugger to come up in the event of a verification error.

The Vera “breakpoint” command can be used inside the code to start the debugger.

The cntrlr module test code contained in “source/cntrlr.vr” contains a commented breakpoint command. Uncomment it and recompile and run the simulation to bring up the debugger.

Page 38: Vera Tutorial

38 Chapter 4. Memory Controller Tutorial

Page 39: Vera Tutorial

Tutorial Chapter 5. Memory System 39

5. Memory SystemAfter discussing the arbiter and memory controller separately, we now examine the way the components act in a complete system. This chapter briefly overviews the system, which includes the arbiter, controller, and SRAM devices. It also discusses some of the higher level verification techniques used in Vera. These include concurrency control mechanisms such as regions, triggers, and mailboxes, object-oriented programming, runtime signal mapping, functional coverage, and random stimulus generation. Finally, this chapter uses these features to validate our memory system. This chapter includes these sections:

• Memory System Overview• Verifying the Memory System

5.1 Memory System OverviewYou will be working inside the new_memsys/memsys/test directory.

• The tutorial memsys Vera source file solution is in this file:

new_memsys/memsys/source/memsys.vr

• The VHDL memsys RTL is in the file:

new_memsys/memsys/rtl/memsys.vhd

• The VHDL memsys RTL toplevel netlist is in the file:

new_memsys/memsys/rtl/memsys3_oop_top.vhd

• The Verilog memsys RTL netlist is in this file:

new_memsys/memsys/rtl/memsys.v

• Tutorial solution run scripts for Verilog/VHDL simulators are in the following directory:

new_memsys/memsys/test/run_scr

• The Tutorial solution Vera interface file is in the following file:

new_memsys/memsys/test/include/memsys.if.vri

• The Tutorial solution Vera ports and binds file is in the following file:

new_memsys/memsys/test/include/memsys.ports_binds.vri

Page 40: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

40 Chapter 5. Memory System Tutorial

• The Tutorial solution Vera compile output files are written to the following directory:

new_memsys/memsys/test/vera_out

• Tutorial Makefile and setup script is located in:

new_memsys/memsys/test

Make sure you edit setup for your installation and then source the file.

The memory system acts as a wrapper that instantiates the arbiter, memory controller, and four SRAM devices. In our system, the system bus is driven by two separate CPUs, with access granted through the arbiter. The memory controller handles the reading and writing of data to and from the system bus. A schematic of the complete system is given in Figure 5-1.

Figure 5-1 Memory System Schematic

5.2 Verifying the Memory SystemThe methodology used to verify the entire memory system is broken down by tasks and concepts:

SRAM

SRAM

SRAM

SRAM

MEMORYCONTROLLER

System Bus

request[0]

grant[0]

grant[1]

request[1]

ce0_N

ce1_N

ce2_N

ce3_N

addressdata

ROUND-ROBINARBITER

reset

CPU0

CPU1

rdWr_N

Page 41: Vera Tutorial

Tutorial Chapter 5. Memory System 41

• General Verification - Reset verification and read/write operations.• Basic Concurrency Control - Using regions, checks that each address is

unique before the bus is requested.• Object Oriented Programming (OOP) - OOP allows us to simplify our

testbench and provide re-usable code blocks.• Functional Coverage - Vera’s coverage objects help ensure that our address

space is tested sufficiently.• Interprocess Communication - Using triggers, we advance or simulation in

lock-step fashion to make sure that data is read only after it is written to the bus. Mailboxes allow us to use random addresses while checking that each address is used only once.

5.2.1 General Verification

The general verification tasks include checking the reset procedure and modifying the read and write operations previously developed for the memory controller. Finally, we develop a testbench that checks both CPUs running concurrently using multiple threads.

Reset Verification

To check that the system is resetting correctly, we must assert the reset signal, release the adxStrb signal, deassert the request signal, check that the grant signal releases properly, and then deassert the reset signal. The code to check the reset is:

memsys.reset 1’b1;

memsys.adxStrb = 1’b0;

memsys.request = 2’b00;

@1,3 memsys.grant == 2’b00;

memsys.reset = 1’b0;

Read and Write Operations

The bus in the system is very similar to the bus used in the memory controller. We can use the write operation task by simply modifying the interface names in the signal operations (memsys.signal). The writeOp task is:

task writeOp (bit[7:0] adx, bit[7:0] data)

{

@1 memsys.busAddr = adx;

memsys.busData = data;

memsys.busRdWr_ = 1’b0;

memsys.adxStrb = 1’b1;

@1 memsys.busRdWr_ = 1’b1;

memsys.busData = b’bzzzzzzzz;

memsys.adxStrb = 1’b0;

}

Page 42: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

42 Chapter 5. Memory System Tutorial

In addition to driving the read operation onto the system bus, our read operation task must check for the correct return data. The new readOp task with the data checking included is:

task readOp (bit[7:0] adx, bit[7:0] data)

{

@1 memsys.busAddr = adx;

memsys.busRdWr_ = 1’b1;

memsys.adxStrb = 1’b1;

@1 memsys.adxStrb = 1’b0;

@2,5 memsys.busData == data;

}

Multiple Threads

Fork/join blocks are the primary mechanism for creating concurrent processes. The syntax to declare a

fork/join block is:

fork

{statement1;}

{statement2;}

{...}

{statementN;}

join wait_option

statementN - The statements can be any valid Vera statement or sequence of statements.

wait_option - The wait_option specifies when the code after the fork/join block executes. The fork/join

block can be either blocking or non-blocking. If it blocks, the code below the fork/join block will not

execute until the code inside the fork/join thread returns. The wait_option must be one of the following:

all

any

none

The all option is the default. Code after the fork/join block executes after all of the concurrent

processes have completed.

When the any option is used, code after the fork/join block executes after any single concurrent process

within the fork/join is completed.

When the none option is used, code after the fork/join block executes immediately, without waiting for

any of the fork/join processes to start. Threads within the fork/join block are scheduled but not executed

until the code following the fork/join block hits a blocking statement.

With the read and write operations defined, we want to set up our testbench so that each CPU issues a series of reads and write requests to the memory system with random addresses and data. Each CPU should use the random() system function to generate random addresses within the valid address space and an 8-bit data type. The CPUs should then request and

Page 43: Vera Tutorial

Tutorial Chapter 5. Memory System 43

access the bus, write the data to the bus, and release the bus (check for the release of the grant signal upon bus release). This sequence should be repeated 256 times using the repeat() flow control statement. Given these criteria, the code is:

random(12933); // call random with seed

fork

{ // CPU0

repeat(256)

{

randVar0 = random(); // get 32 bit random variable

address0 = randVar0[13:6]; // get random 8-bit address

data0 = randVar0[29:22]; // get random 8-bit data

@1 memsys.request[0] = 1’b1; // request the bus

@2,20 memsys.grant == 2’b01; // check for grant

writeOp(address0, data0); // issue write operation

@1 memsys.request[0] = 1’b0; // release request

@2,20 memsys.grant == 2’b00; // check for release

@1 memsys.request[0] = 1’b1; // request again

@2,20 memsys.grant == 2’b01; // check for grant

readOp(address0, data0); // issue read operation

@1 memsys.request[0] = 1’b0; // release request

@2,20 memsys.grant == 2’b00; // check for grant

}

}

{ // CPU1

repeat(256)

{

randVar1 = random(); // get 32 bit random variable

address1 = randVar1[13:6]; // get random 8-bit address

data1 = randVar1[29:22]; // get random 8-bit data

@1 memsys.request[1] = 1’b1; // request the bus

@2,20 memsys.grant == 2’b10; // check for grant

writeOp(address1, data1); // issue write operation

@1 memsys.request[1] = 1’b0; // release request

@2,20 memsys.grant == 2’b00; // check for release

@1 memsys.request[1] = 1’b1; // request again

@2,20 memsys.grant == 2’b10; // check for grant

readOp(address1, data1); // issue read operation

@1 memsys.request[1] = 1’b0; // release request

@2,20 memsys.grant == 2’b00; // check for grant

}

}

join

Page 44: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

44 Chapter 5. Memory System Tutorial

This test works well in exhaustively checking the read and write operations for each CPU. However, because both CPUs are accessing a single bus, problems arise when each CPU accesses the same address space with different data. For instance, if CPU0 writes to an address space, and CPU1 then writes to the same address space, the data that CPU0 reads is different than expected (it reads the data that CPU1 wrote). This results in simulation failure because of the discrepancy between data read and expected data. A solution to this issue is to use basic concurrency control and is discussed in the next section.

5.2.2 Basic Concurrency Control

In our system, we must check that the address is unique before the bus is requested to avoid conflicts. To do this, we use regions.

Region Overview

A Vera region is a mutual exclusion mechanism that guarantees that the requested values are unique in the simulation. Conceptually, regions can be viewed as a set of letters. First you allocate which letters are included in the set. These letters are the only letters from which words can be made. If one person uses the letters to spell CAT, no one else can spell TIN because the T is already in use. Once the T is returned, TIN can be created. Effectively, this ensures that data sets are unique, and it eliminates concurrent crossover.

To allocate a region, you must use the alloc() system function:

function int alloc(REGION, int region_id, int region_count);

region_id - The region_id is the ID number of the particular region being created. It must

be an integer value. You should generally use 0. When you use 0, Vera automatically generates a

region ID.

region_count - The region_count specifies how many regions you want to create. It must

be an integer value.

The alloc() function returns the base region ID if the regions are successfully created. Otherwise, it

returns 0.

The region_enter() system function checks to see if a particular region is in use:

function int region_enter(keyword wait_option, int region_id,

bit|int value1, value2, ..., valueN);

wait_option - The wait_option can be either NO_WAIT or WAIT. The NO_WAIT option continues

code execution if the specified region is in use. The WAIT option suspends the process until

the specified region is no longer in use.

region_id - The region_id specifies which region is being entered.

valueN - The values are integer or bit vectors up to 64 bits, without X’s or Z’s. These values specify

the unique region values.

Page 45: Vera Tutorial

Tutorial Chapter 5. Memory System 45

The region_enter() system function checks the specified values against all region values for the

specified region. If another process has entered the region with one or more of the values, then those

values are in use, and the current region cannot use them. If none of the values are in use elsewhere, the

function returns a 1, flags the values as in use, and passes control to the next line of code. If one or

more of the values is in use elsewhere, the function suspends the current thread until the values become

available, depending on the wait option.

The region_exit() system task removes the specified values from the in-use state. The syntax is:

task region_exit(int region_id, bit|int value1, value2, ..., valueN);

region_id - The region_id specifies which region that is being exited.

valueN - The values are integer or bit vectors up to 64 bits, without X’s or Z’s. These values specify

the unique region values.

When the region_exit() system task is called, the specified values are no longer in use and can be used

in other regions. Any processes that are suspended (waiting for region values) execute when the region

values are made available.

Implementing Regions

To implement regions within the testing framework established in the previous section, we must allocate the region before the forked process. Then, within each CPU fork, the CPU enters the region with its address value. Next, the address is removed from the pool of valid addresses and the region prevents the other CPU from using the same address until the region is exited and the value returned. Each fork must include a region enter and a region exit to accomplish this. You can monitor the region to see how the synchronization works using the trace() system function and checking the verilog.log file after the simulation. Finally, we should force each CPU to wait a random number of cycles after the sequence is executed before running the sequence again. These requirements are satisfied using this code:

regId = alloc(REGION, 0, 1);

trace(ON, REGION, regId);

random(12933); // call random with seed

fork

{ // CPU0

repeat(256)

{

randVar0 = random(); // get 32 bit random variable

address0 = randVar0[13:6]; // get random 8-bit address

data0 = randVar0[29:22]; // get random 8-bit data

region_enter(WAIT, regId, address0); // check if address is free

@1 memsys.request[0] = 1’b1; // request the bus

@2,20 memsys.grant == 2’b01; // check for grant

writeOp(address0, data0); // issue write operation

@1 memsys.request[0] = 1’b0; // release request

Page 46: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

46 Chapter 5. Memory System Tutorial

@2,20 memsys.grant == 2’b00; // check for release

@1 memsys.request[0] = 1’b1; // request again

@2,20 memsys.grant == 2’b01; // check for grant

readOp(address0, data0); // issue read operation

@1 memsys.request[0] = 1’b0; // release request

@2,20 memsys.grant == 2’b00; // check for grant

region_exit(regId, address0); // exit region

repeat (randVar0[20:17]) @(posedge memsys.clk);

}

}

{ // CPU1

repeat(256)

{

randVar1 = random(); // get 32 bit random variable

address1 = randVar1[13:6]; // get random 8-bit address

data1 = randVar1[29:22]; // get random 8-bit data

region_enter(WAIT, regId, address1); // check if address is free

@1 memsys.request[1] = 1’b1; // request the bus

@2,20 memsys.grant == 2’b10; // check for grant

writeOp(address1, data1); // issue write operation

@1 memsys.request[1] = 1’b0; // release request

@2,20 memsys.grant == 2’b00; // check for release

@1 memsys.request[1] = 1’b1; // request again

@2,20 memsys.grant == 2’b10; // check for grant

readOp(address1, data1); // issue read operation

@1 memsys.request[1] = 1’b0; // release request

@2,20 memsys.grant == 2’b00; // check for grant

region_exit(regId, address1); // exit region

repeat (randVar1[20:17]) @(posedge memsys.clk);

}

}

join

5.2.3 Object Oriented Programming (OOP)

OOP allows you to develop programs that are easier to debug and easier to reuse by encapsulating related code and data together and making access to the class formal and rigorous. Vera then uses many of its features within this object-oriented framework. In this section, we examine how classes can be implemented into our memory system using modified port declarations, how classes are constructed, and how random stimuli are built into the Vera objects.

Page 47: Vera Tutorial

Tutorial Chapter 5. Memory System 47

In our system, each of the methods described below should be included within class CPU:

class CPU

{

property declarations;

constraint definitions;

method definitions;

}

Encapsulation

A class is a collection of data and a set of subroutines that act on that data. A class’s data is referred to as properties, and a class’s subroutines are referred to as methods. These comprise the contents of a class instance, or object.

Class properties are instance-specific. Each instance of a class has its own copy of the variables declared in the class definition.

Because multiple instances of classes can exist, when calling a class method, you must identify the instance name for which the method is being called. This is because each method only accesses the properties associated with its object, or instance. So, when calling a method, you must use this syntax:

instance_name.method_name();

Constructors

Objects, or instances, are created when a class is instantiated using the new statement:

class_name instance_name = new();

This declaration creates an instance (called instance_name) of class class_name. When this construction takes place, the new() method within the class is executed (if any exists). By defining a new task within the class, you can initialize the class upon construction or instantiation. Further, by passing arguments to the constructor, you can allow for runtime customizing of the object:

class_name instance_name = new(argument1, argument2, ... argumentN);

Using this constructor, the specified arguments are passed to the new task within the class. The conventions for these arguments are the same as for Vera subroutine calls.

Port Assignment

When implementing object-oriented concepts into our system, it is useful to simplify our port declarations. For ease of use, the interface specification generated using Vera’s template generator is included in the main memsys.vr file (this is only advisable in small examples where working with a single file is easy).

Page 48: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

48 Chapter 5. Memory System Tutorial

We define a bus arbiter virtual port bus_arb to be used with each CPU. It has a request and a grant signal:

port bus_arb

{

request;

grant;

}

Using this virtual port declaration, we declare two binds, one for each CPU:

bind bus_arb arb0

{

request memsys.request[0];

grant memsys.grant[0];

}

bind bus_arb arb1

{

request memsys.request[1];

grant memsys.grant[1];

}

These binds are passed to the class methods to determine which signals are affected by method calls.

Class Methods

In our class, we must create the initialization method that is executed when the class is constructed. We must then create the read and write operation methods. It is also helpful to create methods to request and release the bus.

The initialization method should pass in the bind of type bus_arb (as declared above) and assign it to a local property. The initialization method new is:

task new (bus_arb arb)

{

printf(“Constructing new CPU.\n”);

localarb = arb;

}

Our read operation readOp must behave as before. However, this time the bind is passed to the object so that we do not have to account for it in the declaration.

Page 49: Vera Tutorial

Tutorial Chapter 5. Memory System 49

The readOp method is:

task readOp()

{

@1 memsys.busAddr = address;

memsys.busRdWr_ = 1’b1;

memsys.adxStrb = 1’b1;

@1 memsys.adxStrb = 1’b0;

@2,5 memsys.busData == data;

printf(“READ address = 0%H, data = 0%H \n”, address, data);

}

Our write operation writeOp must behave as before. Again, the bind is passed to the object so that we do not have to account for it in the declaration. However, note the conditional statement that evaluates the bind passed to the object and prints which CPU is writing. The writeOp method is:

task writeOp()

{

@1 memsys.busAddr = address;

memsys.busData = data;

memsys.RdWr_ = 1’b0;

memsys.adxStrb = 1’b1;

@1 memsys.busRdWr_ = 1’b1;

memsys.busData = 8’bzzzzzzzz;

memsys.adxStrb = 1’b0;

if (localarb == arb0)

printf(“CPU0 is writing.\n”);

else if (localarb == arb1)

printf(“CPU1 is writing.\n”);

printf(“WRITE address = 0%H, data = 0%H \n”, address, data);

}

Our request_bus method must assert the corresponding request line and check for the appropriate grant line:

task request_bus()

{

@1 localarb.$request = 1’b1; // request the bus

@2,20 localarb.$grant == 1’b1; // check for grant

}

Page 50: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

50 Chapter 5. Memory System Tutorial

Conversely, our release_bus method must release the corresponding request line and check for the appropriate grant line:

task release_bus()

{

@1 localarb.$request = 1’b0; // release the bus

@2,20 localarb.$grant == 1’b0; // check for grant

}

Random Variables

You can declare class properties as random using the rand declaration:

rand data_type variable = initial_value;

Variables declared as random within a class are randomized when the randomize() system function is called. Because randomize() acts as a class method, you must specify the instance for which the system function is called:

function int object_name.randomize();

object_name - The object_name is the name of the object in which the random variables have been

declared.

The randomize() class method generates random values for all random variables within the specified

class instance. The randomize() method returns a 1 if it successfully sets all the random variables and

objects to valid values. If it does not, it returns a 0. If an object has no random variables anywhere in

its inheritance hierarchy (no random variables or sub-objects) or if all of its random variables are

inactive, the randomize() function returns a 1.

Using random declarations, we declare our class properties address and data as random:

rand bit[7:0] address, data;

Each time an instance is randomized, the address and data values for that instance are randomized.

Earlier, we generated a random delay using the random() system function. Using random variables, we implement the same delay as a class method delay_cycle:

rand integer delay;

...

task delay_cycle()

{

repeat(delay) @(posedge memsys.clk);

printf(“delay = %d/n”,delay);

}

Page 51: Vera Tutorial

Tutorial Chapter 5. Memory System 51

Note that there are no restrictions on the value that delay can assume because it is declared as an integer. We can implement constraints on the values that random variables can assume using the constraint construct:

constraint constraint_name { contraint_expressions }

constraint_name - The constraint_name is the name of the constraint block.

constraint_expression - The constraint_expressions are the conditional expressions that limits random

values. It is a series of expressions that are enforced when the class is randomized. Constraint

expressions are of the form:

random_variable operator expression;

random_variable - The random_variable parameter specifies the variable to which the constraint is

applied.

operator - The valid operators for constraints are: <, <=, ==, >=, >, !=, ===, !==, =?=, and !?=.

expression - The constraint expression where:

• Constraints can be any OpenVera expression with variables andconstants of type bit, integer, or enumerated type.

• Constraint expressions follow Verilog syntax and semantics,including precedence, associativity, sign extension, truncation,and wrap-around.

• Constraint expressions are evaluated bidirectionally that is, both sides of the equation are

solved simultaneously.

Implementing OOP

Before we can use our objects, we must instantiate each object and invoke our initialization routines, which specify the binds to pass to the objects:

CPU cpu0 = new(arb0);

CPU cpu1 = new(arb1);

With our class CPU defined with the properties and methods described above, the same execution sequence created using fork/join can be written as:

regId = alloc(REGION, 0, 1);

trace(ON, REGION, regId);

fork

{

repeat(256)

{

errflag = cpu0.randomize();

region_enter(WAIT, regId, cpu0.address); // check if address is free

Page 52: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

52 Chapter 5. Memory System Tutorial

cpu0.request_bus();

cpu0.writeOp();

cpu0.release_bus();

cpu0.request_bus();

cpu0.readOp();

cpu0.release_bus();

region_exit(regId, cpu0.address);

cpu0.delay_cycle();

}

}

{

repeat(256)

{

errflag = cpu1.randomize();

region_enter(WAIT, regId, cpu1.address); // check if address is free

cpu1.request_bus();

cpu1.writeOp();

cpu1.release_bus();

cpu1.request_bus();

cpu1.readOp();

cpu1.release_bus();

region_exit(regId, cpu1.address);

cpu1.delay_cycle();

}

}

Note how the class property address is passed (using the instance name). Also note the ease of reuse through invoking the class methods for the appropriate instance name.

5.2.4 Functional Coverage

Vera’s functional coverage capabilities are available in both an external coverage definition as well as within the OOP framework. First, you specify a coverage definition through the coverage_group directive. The definition includes valid and invalid states and transitions that are monitored through out the simulation. Additionally coverage goals and the sample event can be defined. As an introduction to Vera’s functional coverage, a subset of the coverage features is discussed here: state and transition declarations, instantiation and sampling, andcoverage reports.

Note – For the purposes of the tutorial we will be working with coverage group definitions external to a class definition. Please see the User Guide for defining embedded coverage groups.

Page 53: Vera Tutorial

Tutorial Chapter 5. Memory System 53

Coverage Definition

The basic syntax for defining a coverage_group is:

coverage_group definition_name [(argument_list)]

{

sample_definitions;

[cross_definitions;]

sample_event_definition;

attribute_definitions;

}

argument_list:

The arguments are parameters passed at instantiation. They have the same conventions as subroutine arguments and can have their default values set within the declaration.

sample_definition:

A sample_definition defines the variables and/or DUT signals that are sampled by the coverage_group. It is declared using the sample construct of a coverage_group. In its simplest form it can be:

sample variable_name [, variable_name];

where variable_name is the name of the Vera variable or signal name that is sampled by the coverage_group. When defining state and or transitions for sampled variables, the sample construct has the form:

sample variable_name

{

[state_or_transition_definition];

[attribute_definition];

}

cross_definition:

You can define cross coverage of variables sampled in a coverage_group using the cross construct. In its simplest form it can be:

cross cross_name (sampled_variable_list);

where sampled_variable_list is a comma separated list of the sampled variables of the coverage_group. We will not be using cross coverage in the tutorial

Page 54: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

54 Chapter 5. Memory System Tutorial

sample_event_definition:

You must specify a sampling event expression in the coverage group definition. The coverage_group samples all of its sampled variables and updates the appropriate bins when the sampling event triggers. You define a sampling event for the coverage_group as follows:

sample_event = event_expression;

The event_expression can be an expression such as @([specified_edge] interface_signal), sync(), or wait_var().

attribute_definition:

You can use attributes for controlling various aspects of a coverage_group. The User Guide details the attributes that can be specified at the coverage_group level, and their default values. You can specify an attribute’s value as follows:

attribute_name = value_expression;

where attribute_name is the name of the attribute, and value_expression is a Vera expression.

Argument list

What if your coverage model cuts across your class abstraction and all of the elements of your coverage model do not reside in the same class? You can pass arguments to a coverage_group in order to address this need. The coverage_group construct optionally allows for the declaration of formal parameters. Actual arguments are passed in to the coverage_group instance as parameters to the new task call. You can define three kinds of parameters in the coverage_group’s definition: sampled, passed-by-value, and passed-by-reference. For the purposes of the tutorial we will be using sampled variables.

Sampled parameters are preceded by the sample keyword in the formal parameter list of the coverage_group definition. They are treated like a constant “var” argument passed to a task. In the following example coverage_group MyCov defines a sampled parameter paramVar that is sampled at every positive edge of the system clock.

coverage_group MyCov(sample bit[3:0] paramVar){

sample_event = @(posedge CLOCK); // Sample event

sample paramVar; // Passed-in sample parameter

}

Sampled Variable State and Transition Bins

If you do not define any state or transition bins for a sampled variable, Vera automatically creates state bins for you. This provides an easy-to-use mechanism for binning different values of a sampled variable.

Page 55: Vera Tutorial

Tutorial Chapter 5. Memory System 55

You can either let Vera automatically create state bins for a sampled variable or explicitly define named state and/or transition bins for each of the sampled variables. Each named bin groups a set of values (state) or a set of value transitions (trans) associated with a sampled variable.

For the tutorial we will be user-defining the state and transitions bins. If you would like more information on auto bin creation, refer to the Vera User Guide.

State Declarations

Coverage declarations are used to declare legal states, illegal states, legal transitions, and illegal transitions. They associate bins with these activities and monitor how many times these activities occur within a simulation.

The syntax for a state declaration is:

state state_bin_name (state_specification);

In a state declaration, a single state or multiple states are associated with a monitor bin via a state specification. The state specification is a list of elements (separated by commas) that are matched against the current value of the state variable. For the current cycle, any matches increment the bin counter by one.

Each element of the state specification should be an expression. When the state variable matches the expression, the bin counter is incremented one.

The m_state state declaration is used to declare multiple state bins up to a maximum of 4096 bins. The syntax is:

m_state state_bin_name (exp1:exp2);

state_bin_name - The state_bin_name is the base name of the state bins being created. exp - The exps can be any valid coverage expression. You cannot call functions in the expression. The expressions can include variables.

When the m_state declaration is used, multiple state bins are created, covering all the values in the range. The expressions are evaluated when the coverage object is instantiated.

Illegal state declarations associate illegal states with a bin. The syntax is:

bad_state error_bin_name (state_specification);

Illegal or bad states are those states in the design that, when entered, result in verification errors.

Page 56: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

56 Chapter 5. Memory System Tutorial

The state specification can be any expression or combination of expressions as in the state declarations. However, it is often useful to define every state that is not in the state declarations as a bad state. To use that definition of bad states, you can use the not state specification:

bad_state error_bin_name (not state);

This statement increments the specified bin counter every time the state variable matches a value not defined in the state declarations.

Transition Declarations

Transition declarations associate state transitions with monitor bins. The syntax for transition declarations is:

trans trans_bin_name (state_transitions) conditional;

Declaring a sequence of transitions between states specifies state transitions. The general format is:

trans trans_bin_name (state_set_1 -> state_set_2 -> ... ->state_set_N);

Illegal transition declarations associate an illegal transition with a monitor bin. The syntax is:

bad_trans trans_bin_name (state_transitions);

The state transition can be any state transition set valid for transition declarations. However, it is often useful to monitor all transitions that have not been defined as legal transitions. For such instances, Vera uses the not trans argument.

bad_trans trans_bin_name (not trans);

The counter associated with the specified bin will be incremented every time a transition occurs that is not explicitly defined in the transition declaration.

Defining Sample Events

You must specify a sampling event expression in the coverage group definition. This will be used for all instantiations of a coverage definition. The sampling event expression allows you to control when the object takes a sample. Coverage objects can be triggered on clock edges, signal edges, variable changes, sync events and OVA events. For the purposes of the tutorial we will be sampling on Clock and signal edges. For a complete description of other sample events please refer to the Vera User Guide.

Coverage objects can be sampled on clock or signal edges as per the synchronization command. When the specified edge occurs, the object is sampled. The syntax is:

sample_event = @([specified_edge] interface_signal | CLOCK);

Page 57: Vera Tutorial

Tutorial Chapter 5. Memory System 57

In the following example, all instances of coverage group cov1 will be sampled upon the posedge of the system clock.

coverage_group cov1

{

sample_event = @(posedge CLOCK);

sample g_var;

}

Implementing Coverage Groups

We have added a memory controller probe that acts as a state machine for the memory controller. The signal that it monitors is memcntlr_probe.cntlr_state. Because we are monitoring a new signal, we must add a new interface:

interface memcntrlr_probe

{

input clk CLK;

input[1:0] cntrlr_state INPUT_EDGE verilog_node “dut.Umem.state”;

}

To implement a coverage object that monitors the states and transitions of our memory controller, we must first identify the states and transitions we want to check. In our system, we will monitor the state variable cntrlr_state, which is passed in at the time of instantiation via a sampled variable. Assuming we have states IDLE (0), START (1), WRITE0 (2), and WRITE1 (3) and the transitions IDLE to IDLE, IDLE to START, START to IDLE, START to WRITE0, WRITE0 to WRITE1, and WRITE1 to IDLE, our coverage definition is:

coverage_group cntlr_cov(sample bit[1:0] cntlr_state)

{

sample_event = @(posedge CLOCK);

sample cntlr_state {

state IDLE(0);

state START(1);

state WRITE0(2);

state WRITE1(3);

bad_state (not state);

trans t0 ("IDLE" -> "IDLE");

trans t1 ("IDLE" -> "START");

trans t2 ("START" -> "IDLE");

trans t3 ("START" -> "WRITE0");

trans t4 ("WRITE0" -> "WRITE1");

trans t5 ("WRITE1" -> "IDLE");

bad_trans (not trans);

}

}

Page 58: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

58 Chapter 5. Memory System Tutorial

We also want to check that the entire address space is tested. We monitor the state variable peek to check that it assumes all valid states between 0 and 255:

coverage_group range(sample bit[7:0] peek)

{

sample_event = @( negedge memsys.adxStrb);

sample peek {

m_state (0:255);

}

}

Before we can instantiate our coverage objects, we must declare the objects within our main program:

cntlr_cov cov1;

range cov2;

With our objects declared, we must instantiate them within the main program. We want our objects to monitor the activity in our forked processes, so we instantiate them before the forks:

cov1 = new (memcntlr_probe.cntlr_state);

cov2 = new(memsys.busAddr);

It is important to note that Vera cannot directly sample output signals. So we must modify the adxStrb signal declaration within the interface specification such that the signal is defined as bidirectional (inout) with the proper input and output edges:

inout adxStrb OUTPUT_EDGE INPUT_EDGE OUTPUT_SKEW;

Analysis of Coverage Results

Following the simulation run, Vera will generate a coverage database:

memsys_test.db

We have two options for reviewing the data within the database, both involve generating a coverage report and analyzing the results. The choice is to generate a hyperlinked HTML report or a text based report.

HTML report:

vera –cov_report memsys_test.db

netscape memsys_test.index.html&

Text report:

vera –cov_text_report memsys_test.db

more memsys_test.txt

Page 59: Vera Tutorial

Tutorial Chapter 5. Memory System 59

Note – The Makefile for this tutorial has two options for generating reports. Please select either of the following two options:

make html# HTML report, opens with Netscape

make text# text report, opens in more

Figure 5-2 shows a typical example of an HTML coverage report.

Figure 5-2 Coverage Rerport

Page 60: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

60 Chapter 5. Memory System Tutorial

5.2.5 Interprocess Communication

In addition to regions, we can use triggers and mailboxes to control our concurrent processes. Triggers allow us to advance the simulation in lockstep such that one CPU writes data to the bus and the other reads that data before the next write. This allows us to verify that data is correctly written to memory.

Mailboxes allow us to perform a similar test using random addresses. Without running the simulation in lockstep, mailboxes allow us to read only addresses that have been written to previously.

Triggers

Triggers refer to a process that involves events, syncs, and triggers. Events are variables that

synchronize concurrent processes. When a sync is called, a process blocks until another process sends a

trigger to unblock it. Events act as the go-between for triggers and syncs.

Syncs suspend a process until a trigger activates to unblock the process. The sync() system task

synchronizes statement execution to one or more triggers. The syntax to call the sync() task is:

task sync(keyword sync_type, event event1, event2, ... eventN);

sync_type - To simplify our design, we only use the ALL sync, which blocks until all events are

triggered.

eventN - The event is the event variable name on which the sync is activated. Note that you must

declare your event variables within the scope that the sync/trigger combination is used.

Triggers send events. The syntax to call a trigger is:

function int trigger([keyword trigger_type,] event event_name);

trigger_type - In our system, the only trigger_types we use are ON, which turns on an event, and OFF,

which turns off an event.

event_name - The event_name is the event being triggered.

When you call a sync, that process is suspended until a trigger sends an event that unblocks the sync.

Implementing Triggers

In our system, we want to write data to the bus and then read it to check that it was correctly written to memory. To do this, CPU0 must issue only write requests while CPU1 issues only read requests. Further, CPU1 must only read data after CPU0 completes a successful write. This requires that we advance the simulation in lockstep.

To do this, we use triggers within our fork/join block. Note that we must return to the for loop to ensure lockstep behavior (each iteration of the loop of a process depends on the trigger/sync call of the other process):

Page 61: Vera Tutorial

Tutorial Chapter 5. Memory System 61

event go_ahead, done;

integer index0, index1;

...

fork

{

for(index0=0; index0 <= 255; index0++)

{

address0 = index0;

cpu0.request_bus();

cpu0.writeOp(address0,address0);

trigger(ON, go_ahead);

cpu0.release_bus();

sync(ALL, done);

trigger(OFF, done);

}

}

{

for(index1=0; index1 <= 255; index1++)

{

address1=index1;

sync(ALL, go_ahead);

trigger(OFF, go_ahead);

cpu1.request_bus();

cpu1.readOp(index1,index1);

cpu1.release_bus();

trigger(ON, done);

}

}

join

Mailboxes

A mailbox is a mechanism to exchange messages between processes. Data can be sent to a mailbox by

one process and retrieved by another. Conceptually, mailboxes behave like real mailboxes. When a

letter is delivered and put into the mailbox, you can retrieve the letter (and any data stored within).

However, if the letter has not been delivered when you check the mailbox, you must choose whether to

wait for the letter or retrieve the letter on subsequent trips to the mailbox. Similarly, Vera’s mailboxes

allow you to transfer and retrieve data in a very controlled manner.

To allocate a mailbox, you must use the alloc() system function. The syntax is:

function int alloc(MAILBOX, int mailbox_id, int mailbox_count);

mailbox_id - The mailbox_id is the ID number of the particular mailbox being created. It must

be an integer value. You should generally use 0. When you use 0, Vera automatically generates a

mailbox ID.

Page 62: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

62 Chapter 5. Memory System Tutorial

mailbox_count - The mailbox_count specifies how many mailboxes you want to create. It must

be an integer value.

The alloc() function returns the base mailbox ID if the mailboxes are successfully created. Otherwise, it

returns 0.

The mailbox_put() system task sends data to the mailbox. The syntax is:

task mailbox_put(int mailbox_id, scalar data);

mailbox_id - The mailbox_id specifies which mailbox receives the data.

data - The data can be any general expression that evaluates to a scalar.

The mailbox_put() system task stores data in a mailbox in a FIFO manner. Note that when passing

objects, only object handles are passed through the mailbox.

The mailbox_get() system function returns data stored in a mailbox. The syntax is:

function scalar mailbox_get(keyword wait_option, int mailbox_id

[, scalar dest_var [, keyword check_option]]);

wait_option - The wait option can either be NO_WAIT or WAIT. The NO_WAIT option continues

code execution if the mailbox is empty. The WAIT option suspends the process until a message

is sent to the mailbox.

mailbox_id - The mailbox_id specifies which mailbox data is being retrieved from.

dest_var - The dest_var is the destination variable of the mailbox data.

check_option - The check_option is an optional argument that should be set to CHECK when used. It

specifies whether type checking occurs between the mailbox data and the destination variable.

The mailbox_get() system function assigns any data stored in the mailbox to the destination variable

and returns the number of entries in the mailbox, including the entry just received. If there is a type

mismatch between the data sent to the mailbox and the destination variable, a runtime error occurs

unless the CHECK option is used. If the CHECK option is active, a -1 is returned, and the message is

left in the mailbox and is dequeued on the next mailbox_get() function call. If the mailbox is empty, the

function waits for a message to be sent, depending on the wait option. If the wait option is NO_WAIT,

the function returns a 0. If no destination variable is specified, the function returns the number of

entries in the mailbox, but it does not dequeue an item from the mailbox.

Implementing Mailboxes

Using mailboxes, we do not need to run the simulation in lockstep. Instead, we can have CPU0 write to random addresses. Each time it writes to an address, that address is sent to the mailbox and read by CPU1 so that CPU1 knows which addresses are valid to read. In our implementation, we want to store the address and data after a write. Meanwhile, the other CPU waits until an address and data are stored before it reads from the bus. The code for this configuration is:

Page 63: Vera Tutorial

Tutorial Chapter 5. Memory System 63

mboxId = alloc(MAILBOX, 0, 1);

fork

{

repeat(256)

{

errflag=cpu0.randomize();

cpu0.request_bus();

cpu0.writeOp();

mailbox_put(mboxId, cpu0.address);

mailbox_put(mboxId, cpu0.data);

cpu0.release_bus();

}

}

{

repeat(256)

{

success = mailbox_get(WAIT,mboxId,address,CHECK);

success = mailbox_get(WAIT,mboxId,data,CHECK);

cpu1.request_bus();

cpu1.readOp(address,data);

cpu1.release_bus();

}

}

join

Page 64: Vera Tutorial

Confidential and Proprietary Synopsys Inc.

64 Chapter 5. Memory System Tutorial