You are on page 1of 234

VMM Primers

Version C-2009.06 June 2009

Comments? E-mail your comments about this manual to: vcs_support@synopsys.com.

Copyright Notice and Proprietary Information
Copyright © 2008 Synopsys, Inc. All rights reserved. This software and documentation contain confidential and proprietary information that is the property of Synopsys, Inc. The software and documentation are furnished under a license agreement and may be used or copied only in accordance with the terms of the license agreement. No part of the software and documentation may be reproduced, transmitted, or translated, in any form or by any means, electronic, mechanical, manual, optical, or otherwise, without prior written permission of Synopsys, Inc., or as expressly provided by the license agreement.

Right to Copy Documentation
The license agreement with Synopsys permits licensee to make copies of the documentation for its internal use only. Each copy shall include all copyrights, trademarks, service marks, and proprietary rights notices, if any. Licensee must assign sequential numbers to all copies. These copies shall contain the following legend on the cover page: This document is duplicated with the permission of Synopsys, Inc., for the exclusive use of _________________________________ and its employees. This is copy number______.”

Destination Control Statement
All technical data contained in this publication is subject to the export control laws of the United States of America. Disclosure to nationals of other countries contrary to United States law is prohibited. It is the reader’s responsibility to determine the applicable regulations and to comply with them.

Disclaimer
SYNOPSYS, INC., AND ITS LICENSORS MAKE NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO THIS MATERIAL, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

Registered Trademarks (®)
Synopsys, AMPS, Cadabra, CATS, CRITIC, CSim, Design Compiler, DesignPower, DesignWare, EPIC, Formality, HSIM, HSPICE, iN-Phase, in-Sync, Leda, MAST, ModelTools, NanoSim, OpenVera, PathMill, Photolynx, Physical Compiler, PrimeTime, SiVL, SNUG, SolvNet, System Compiler, TetraMAX, VCS, Vera, and YIELDirector are registered trademarks of Synopsys, Inc.

Trademarks (™)
AFGen, Apollo, Astro, Astro-Rail, Astro-Xtalk, Aurora, AvanWaves, Columbia, Columbia-CE, Cosmos, CosmosEnterprise, CosmosLE, CosmosScope, CosmosSE, DC Expert, DC Professional, DC Ultra, Design Analyzer, Design Vision, DesignerHDL, Direct Silicon Access, Discovery, Encore, Galaxy, HANEX, HDL Compiler, Hercules, Hierarchical Optimization Technology, HSIMplus, HSPICE-Link, iN-Tandem, i-Virtual Stepper, Jupiter, Jupiter-DP, JupiterXT, JupiterXT-ASIC, Liberty, Libra-Passport, Library Compiler, Magellan, Mars, Mars-Xtalk, Milkyway, ModelSource, Module Compiler, Planet, Planet-PL, Polaris, Power Compiler, Raphael, Raphael-NES, Saturn, Scirocco, Sciroccoi, Star-RCXT, Star-SimXT, Taurus, TSUPREM-4, VCS Express, VCSi, VHDL Compiler, VirSim, and VMC are trademarks of Synopsys, Inc.

Service Marks (SM)
MAP-in, SVP Café, and TAP-in are service marks of Synopsys, Inc. SystemC is a trademark of the Open SystemC Initiative and is used under license. ARM and AMBA are registered trademarks of ARM Limited. Saber is a registered trademark of SabreMark Limited Partnership and is used under license. All other product or company names may be trademarks of their respective owners.

2

List of the VMM Primers
The VMM primers provide introductions to the topics listed below. •

1

Creating a block-level environment that is reusable in a systemlevel verification environment: - VMM Primer: Composing Environments

Configuring a memory allocation manager and using it to configure a design that requires memory buffers: - VMM Primer: Using the Memory Allocation Manager

Writing VMM-compliant command-lever master transactors, also known as bus-functional models (BFM): - VMM Primer: Writing Command-Layer Master Transactors

Writing VMM-compliant command-layer slave transactors: - VMM Primer: Writing Command-Layer Slave Transactors

Writing VMM-compliant command-lever monitors: - VMM Primer: Writing Command-Layer Monitors

Creating a scoreboard based on the Data Stream Scoreboard foundation classes and integrating it in a verification environment: - VMM Primer: Using the Data Stream Scoreboard

Creating a RAL model of registers and memories, integrating it in a verification environment, and verifying the implementation of the registers and memories using the pre-defined tests: - VMM Primer: Using the Register Abstraction Layer

List of the VMM Primers 1

List of the VMM Primers 2

2 / March 18. 2008 1 VMM Primer: Composing Environments 1 .VMM Primer: Composing Environments Author: Janick Bergeron Version 1.

Other primers cover other aspects of developing VMMcompliant verification assets. If you need to ramp-up on VMM itself. this primer focuses on a single aspect of its functionality: the verification of its transmission path. you should read the other primers in this series. This primer assumes that you are familiar with VMM. The design used to illustrate how to use the VMM Environment Composition application package is the OpenCore Ethernet Media Access Controller (MAC). VMM Primer: Composing Environments 2 . A complete verification environment that can verify the entire functionality of this design requires many more elements and additional capabilities. The system-level design will be a simple composition of two MAC blocks. such as transactors. Despite being a fairly complex design. verification environments and the Register Abstraction Layer package. This design was chosen because it exhibits all of the challenges in reusing parts of its verification environment: It is configurable and it requires access to a shared resource. This primer is designed to learn how to create a block-level environment that will be reusable in a system-level verification environment.Introduction The VMM Environment Composition application package is a set of methodology guidelines and support classes to help create systemlevel verification environments from block-level environments.

not being mapped in the DUT address space. you should read it in a sequential fashion. They have to be structured and architected to make then reusable. VMM Primer: Composing Environments 3 . You can use the same sequence to create your own reusable sub-environments. is not included in the RAL model. The detailed operation of the firmware emulation layer to successfully transmit a frame is described in the Memory Allocation Manager Primer. Step 1: Planning Creating block-level environments that can be reused in a systemlevel environment does not happen by accident. Interfaces that are visible and/or controllable at the block level may no longer be available at the system level.This document is written in the same order you would create a verification environment with portions designed to be reused in a system-level verification environment. the entire block-level environment may not be reusable. The DMA memory. low-level random stimulus at the block level may need to carry well-formed higherlevel information at the system-level. It is important to identify which portion(s) of the block-level environment that would be reusable in a system-level context. Figure 1 on page 4 shows the structure of the verification environment surrounding the Ethernet MAC block. As such. The firmware emulation transactor configures and controls the registers inside the design through a RAL model. The firmware emulation transactor accesses the DMA memory directly through backdoor read()/write() methods provided by the RAM model itself. First. Also.

should the need arise.Figure 1 Block-Level Verification Environment Scrbrd MII Mon GEN FRMWR RAL OcETH MII Rx RAM The shaded portion of the block-level environment will be made reusable in a system-level environment. It will be up to the system-level VMM Primer: Composing Environments 4 . This will enable the firmware layer to be fed from a high-level protocol stimulus source. The same information could have been extracted from the active MII receiver transactor. the MII interface may not be externally visible at the system-level and thus not in need of a receiver to properly terminate it. The Register Abstraction Layer is not included in the reusable portion because the address and means of accessing the registers will likely be different between the block-level environment and the system-level environment. Using a monitor allows the selfchecking portion of the block-level environment to be reused in an environment where that interface is internal. However. The generator that provides stimulus to the firmware emulation block will be made optional. A passive monitor is used to extract information from the MII interface.

environment to provide a reference to a suitable RAL model for the firmware emulation layer to be able to properly configure and control the corresponding block. The system-level environment used in this primer is shown in Figure 2. Figure 2 System-Level Verification Environment Scrbrd GEN FRMWR MII Mon OcETH RAM MII Rx RAL GEN OcETH MII Rx FRMWR MII Mon Scrbrd VMM Primer: Composing Environments 5 . The reusable sub-environment from Figure 1 is used twice with slightly different configurations.

vmm_consensus end_test. A vmm_consensus reference. .Step 2: Encapsulation The portion of the block level environment that will be reusable.passive mii_tx. virtual mii_if.).new("OcEth SubEnv". super. . inst. called a sub-environment. endfunction: new .sv class oc_eth_subenv_cfg. eth_frame_channel tx_chan. endclass: oc_eth_subenv VMM Primer: Composing Environments 6 . endclass: oc_eth_env_cfg class oc_eth_env extends vmm_subenv.. function new(string inst.. File: SubEnv/eth_subenv. A sub-environment configuration descriptor... ..... oc_eth_env_cfg cfg. All virtual interfaces and channels that cross the sub-environment boundary. The constructor arguments must include: • • • • An instance name.. is encapsulated in a class extended from the vmm_subenv base class. . end_test)..

inst)... tx_chan = this. These logical interfaces must also be provided as constructor arguments and include: VMM Primer: Composing Environments 7 . eth_frame_atomic_gen src..out_chan. if (tx_chan == null) begin this.. If set to null. endclass: oc_eth_subenv The sub-environment has additional logical interfaces required to properly interact with other components of the overall environment.The reference to the tx_chan argument is optional... inst.. .. .. vmm_consensus end_test. it will be assumed to not cross the sub-environment boundary and the generator will be internally allocated and connected.src.. If it is not null.. .src = new("OcEth SubEnv Src".passive mii_tx. . . end_test)... the reference will be used as-is and assumed to be properly fed from some source external to the sub-environment.new("OcEth SubEnv". eth_frame_channel tx_chan. end . endfunction: new .). oc_eth_env_cfg cfg. File: SubEnv/eth_subenv.sv class oc_eth_subenv_cfg. super.. . endclass: oc_eth_subenv_cfg class oc_eth_subenv extends vmm_subenv.. virtual mii_if. function new(string inst..

. inst.. end_test).src = new("OcEth SubEnv Src". • • File: SubEnv/eth_subenv. wb_ram dma_ram. function new(string inst.new("OcEth SubEnv". oc_eth_env_cfg cfg. If the system memory were mapped into the space of the block. .out_chan. vmm_consensus end_test. super.. tx_chan = this. A reference to system memory for backdoor read/write. virtual mii_if.. ral_block_oc_ethernet ral. Since the system memory is a potentially shared resource. it my be managed by a shared resource manager.src. vmm_mam dma_mam)..sv class oc_eth_subenv extends vmm_subenv. inst).. eth_frame_channel tx_chan. . endclass: oc_eth_subenv VMM Primer: Composing Environments 8 .. ... This will enable the firmware emulator to configure the block regardless of the physical interface or base address it will be located under.... end .passive mii_tx. endfunction: new . A reference to the memory allocation manager for the system memory. it could be accessed through the RAL model for that block. But such is not the case here. . if (tx_chan == null) begin this.• A reference to a RAL model for the block. eth_frame_atomic_gen src.

ram.eth_sub this. .new().ral.... this.. .eth = new("Eth". null. endclass: test_cfg . this. . wb_ram ram. super. this. endfunction: build .. this. class blk_env extends vmm_ral_env..passive. this. ral_block_oc_ethernet ral_model..sv class test_cfg.set_model(this. oc_eth_subenv eth.ral_model = new(). endclass: blk_env VMM Primer: Composing Environments 9 . in the build() step.... blk_top. this. rand oc_eth_subenv_cfg eth. File: SubEnv/blk_env. this. this. .mii.cfg = new..ral_model). like any other transactor.eth.end_vote.cfg.. .The subenvironment is then instantiated in the block-level environment and constructed. endfunction: new . wm_mam dma_mam..ral_model...dma_mam). test_cfg cfg.. virtual function build(). function new(). super.build().

rand int unsigned run_for_n_frames.Step 3: Configuration The sub-environment configuration descriptor must contain all information necessary to configure the elements in the subenvironment. endclass: oc_eth_subenv_cfg class oc_eth_subenv extends vmm_subenv.. It can be determined by the value of the virtual interfaces or channels.. Structural configuration also includes appropriately configuring the transactors instantiated during construction. the optional reference to a channel controls whether or not a generator is instantiated inside the sub-environment. For example. rand mii_cfg mii. File: SubEnv/eth_subenv.sv class oc_eth_subenv_cfg. . rand bit [47:0] dut_addr. rand int unsigned run_for_n_frames. rand bit [15:0] max_frame_len.sv class oc_eth_subenv_cfg.. rand bit [47:0] dut_addr. . rand mii_cfg mii. VMM Primer: Composing Environments 10 . rand bit [15:0] max_frame_len. This was implemented as part of Step 2. endclass: oc_eth_subenv_cfg Structural configuration is implemented in the constructor.. rand bit [ 7:0] n_tx_bd. File: SubEnv/eth_subenv. rand bit [ 7:0] n_tx_bd.

src = new("OcEth SubEnv Src"..mon = new(inst. frmwr_emulator frmwr. vmm_mam dma_mam). this.passive mii_tx. this.mii.. super..dma_ram. 0. this.src.dut_addr.frmwr = new(inst.cfg. end_test). mii_monitor mon. tx_chan. 0. function new(string inst. this. this.mii_sigs).cfg..randomized_obj.src = this. this. . ral_block_oc_ethernet ral. endclass: oc_eth_subenv Functional configuration is implemented in the user-defined vmm_subenv::configure() method.sb.src. this.new("OcEth SubEnv".cfg.out_chan. this. inst)..cfg.src. wb_ram dma_ram.dma_mam). oc_eth_env_cfg cfg.randomized_obj. vmm_consensus end_test.stop_after_n_insts = this. eth_frame_atomic_gen src. endfunction: new .. this. this. this. VMM Primer: Composing Environments 11 . if (tx_chan == null) begin this. tx_chan = this. eth_frame_channel tx_chan.run_for_n_frames..rand_mode(0).. virtual mii_if.src..src. end . This step configures the DUT to match the configuration of the sub-environment. inst.ral. this.

File: SubEnv/eth_subenv. endclass: blk_env VMM Primer: Composing Environments 12 . ...log.. super. ral. endclass: oc_eth_subenv The call to super.set(0). endtask: configure . if (status !== vmm_rw::IS_OK) begin `vmm_error(ral....configured() at the end of the configure() implements an error detection mechanism to ensure that a subenvironment is completely configured before it is started.TXEN.write(status.). 1).sv class blk_env extends vmm_ral_env.HUGEN. virtual task cfg_dut().sv class oc_eth_subenv extends vmm_subenv. this. task configure().configured(). .eth. ral. The configure() method of the sub-environment is then invoked from at the cfg_dut() step. .FULLD. endtask: cfg_dut . ral. end super. .configure(). vmm_rw::status_e status.cfg_dut().set(1)...... File: SubEnv/blk_env..

the vmm_subenv::start() method must be extended to start all transactors and ancillary threads in the sub-environment. mam..n_bytes == 4.The configuration of the Memory Allocation Manager instance that is responsible for managing the system memory must match the configuration of that memory. ram. . VMM Primer: Composing Environments 13 .. .sv class test_cfg.. constraint test_cfg_valid { ram.min_addr == 32'h0000_0000.. This can be implemented by constraining the configuration of one to match the configuration of the other.end_offset == ram. First. ram. rand wb_slave_cfg rand vmm_mam_cfg ram. mam.max_addr... File: SubEnv/blk_env. File: SubEnv/eth_subenv. mam.sv class oc_eth_subenv extends vmm_subenv. } ..min_addr.max_addr == 32'hFFFF_FFFF. . endclass: test_cfg Step 4: Execution Sequence The sub-environment must then be integrated in the block-level environment’s execution sequence.port_size == wb_cfg::DWORD. mam..start_offset == ram.

..run_for_n_frames > 0 && this. this. virtual task start(). super. .virtual task start()..start_xactor().start_xactor()..src. endtask: start .sv class blk_env extends vmm_ral_env.frmwr.get(fr). super. this.mon. File: SubEnv/blk_env. . end join_none ..src != null) begin this.eth. endclass: blk_env VMM Primer: Composing Environments 14 ..sb..cfg. endtask: start . endclass: oc_eth_subenv The start() method of the sub-environment must then be called in the extension of the vmm_env::start() method. . fork forever begin eth_frame fr. end this.. this.start().start()..to_phy_chan. if (this.received_by_phy_side(fr)..start()...

src.cleanup(). File: SubEnv/eth_subenv.stop().sv class oc_eth_subenv extends vmm_subenv. $psprintf("%0d frames were not seen by the scoreboard".src != null) this. if (this. if (this.sv class blk_env extends vmm_ral_env.run_for_n_frames)).eth.stop(). endtask: stop virtual task cleanup(). virtual task stop(). super. . this.cleanup().. virtual task stop(). super. this. super.The vmm_subenv::stop() and vmm_subenv::cleanup() methods must be similarly implemented then invoked from the vmm_env::stop() and vmm_env::cleanup() method extensions respectively. end endtask: cleanup endclass: oc_eth_subenv File: SubEnv/blk_env.cfg.eth.run_for_n_frames > 0) begin `vmm_error(this.cleanup().stop_xactor().cfg. endtask: cleanup endclass: blk_env VMM Primer: Composing Environments 15 . this..log..stop(). super. endtask: stop virtual task cleanup().. .

There is no wait-for-end method in the subenv base class as the end-of-test decision is implemented through the vmm_consensus instance.The most important—and difficult to implement—is the wait_for_end step..cfg. The decision to end the test is now centralized in that one object but the contributors to that decision can be distributed over the entire verification environment. From the sub-environment’s perspective.run_for_n_frames > 0 && this. File: SubEnv/eth_subenv. virtual task start(). An instance of that object already exists in the vmm_env::end_vote class property and it has already been passed into the vmm_subenv::end_test property via the constructor of the subenvironment (See Step 2).sv class oc_eth_subenv extends vmm_subenv.start_xactor(). super.. The sum of all contributions will be used to determine whether to end the test or not. all channels are empty and all transactors are idle. The various contributions to the end-oftest consensus are registered in the extension of the vmm_subenv::start() method.start(). if (this.frmwr. fork VMM Primer: Composing Environments 16 .start_xactor(). . the test may end once the generator is done.src != null) begin this. Fortunately. regardless of how many contributors there are. end this. the VMM Environment Composition package includes a new object that helps make the decision whether to end the test or not: vmm_consensus.

endtask: start . The consent of the sub-environment is automatically taken care of by virtue of using the same vmm_consensus instance.start().to_phy_chan. this. end this.end_test.mon. end join_none if (this. this..host.register_xactor(this.register_notification( this.end_test. endclass: oc_eth_subenv From the block-level environment’s perspective. File: SubEnv/blk_env.eth.end_vote.sv class blk_env extends vmm_ral_env..start().exec_chan)...frmwr).src != null) begin this.phy). this. super.register_xactor(this.forever begin eth_frame fr.received_by_phy_side(fr).get(fr). eth_frame_atomic_gen::DONE). VMM Primer: Composing Environments 17 .to_phy_chan).src.sb..register_channel(this.register_channel(this. this.end_vote. this.notify. .end_test. . virtual task start(). this.register_channel(this.tx_chan).end_test. this. this. The other contributions to the end-oftest decision are registered in the extension of the vmm_env::start() method.. the test may end when the sub-environment consents and all transactors and channels instantiated in the block-level environment itself are respectively idle and empty.mon).register_xactor(this.mon.end_test.

endclass: blk_env Step 5: Block-Level Tests The block level environment now completely integrates the subenvironment and is ready to perform block-level tests. a trivial block-level test would limit the number of transmitted frames to perform initial debug.sv program test. end VMM Primer: Composing Environments 18 .gen_cfg(). File: SubEnv/blk_env.eth. For example.run().sv class blk_env extends vmm_ral_env.. endtask: wait_for_end .. blk_env env = new. this.. endtask: start ..run_for_n_frames = 3. env.wait_for_end(). virtual task wait_for_end()...wait_for_consensus().host). .this. endclass: blk_env The implementation of the wait_for_end step is now only a matter of waiting for the end-of-test consensus to be reached.register_xactor(this.cfg.end_vote. env. super. initial begin env. File: SubEnv/blk_trivial_test.end_vote.

VMM Primer: Composing Environments 19 . . virtual function build().. File: SubEnv/sys_env....sv class sys_env extends vmm_ral_env. oc_eth_subenv eth[2]. two configuration descriptors are required instead of one. A single RAL model. First.. endclass: test_cfg Two instances of the sub-environments are required.. each connected to their respective physical interfaces. rand oc_eth_subenv_cfg eth[2]. ..sv class test_cfg..endprogram: test The test may be run using the following command: Command: % make blk_tx Step 6: System-Level Environment Constructing the system-level environment using two instances of the sub-environment is very similar to constructing the block-level environment. RAM and Memory Allocation Manager is shared across both sub-environment because there are shared resources. File: SubEnv/sys_env. . .

eth[0]. this. endclass: sys_env Notice how the end-of-test vmm_consensus object is passed to both sub-environments. This allows both sub-environments to independently contribute to the end-of-test decision. endfunction: build . this. virtual task cfg_dut(). this.out_chan.end_vote. join endtask: cfg_dut . this.cfg. VMM Primer: Composing Environments 20 . ... this. this.eth[0] = new("Eth0". this. This configures the subenvironment to use the external source provided—in this case an externally instantiated atomic generator. .cfg. null. this.eth[1].passive.sv class sys_env extends vmm_ral_env.this.. sys_top.. this.mii_1.. notice how the tx_chan value is not null for the second instance of the sub-environment.eth[1].ram.configure().cfg_dut(). It is a good idea to fork the configuration steps to perform as much of it as possible concurrently.ram.ral_model. this..src. this..passive. Both sub-environments must be configured.dma_mam).dma_mam).configure(). File: SubEnv/sys_env. fork this. Also. sys_top.eth[0].mii_0.eth[1] = new("Eth0". super. this.. this.end_vote.ral_model.

virtual task stop(). super.stop().. stop() and cleanup() methods must similarly invoke their respective counterpart in both sub-environment instances. endtask: wait_for_end ... super. endclass: sys_env VMM Primer: Composing Environments 21 . . endclass: sys_env However. . virtual task wait_for_end(). File: SubEnv/sys_env.wait_for_end().. endtask: stop . this. File: SubEnv/sys_env.eth[0]. regardless of the number of contributors.sv class sys_env extends vmm_ral_env. the wait_for_end() task remains identical as the vmm_consensus object takes care of scaling the end-of-test decision.stop().. this.sv class sys_env extends vmm_ral_env.. this.eth[1]..end_vote..endclass: sys_env The start().stop().wait_for_consensus().

cfg. env.gen_cfg(). initial begin env. VMM Primer: Composing Environments 22 .cfg. sys_env env = new.eth[1]. File: SubEnv/sys_trivial_test.run_for_n_frames = 3.run_for_n_frames = 3.eth[0]. a trivial system-level test would limit the number of transmitted frames to perform initial debug.run().sv program test. env. env. end endprogram: test The test may be run using the following command: Command: % make sys_tx Step 8: Congratulations You have now completed the development and integration of a reusable sub-environment.Step 7: System-Level Tests The system-level environment now completely integrates two subenvironments and is ready to perform system-level tests. For example. You can now speed-up the development of system-level tests by leveraging the work done in block-level environments.

VMM Primer: Composing Environments 23 . verification environments. integrate a Register Abstraction Layer model or a Data Stream Scoreboard.You may consider reading other publications in this series to learn how to write VMM-compliant command-layer transactors. or use the Memory Allocation Manager.

VMM Primer: Composing Environments 24 .

2006 VMM Primer: Using the Memory Allocation Manager 1 .VMM Primer: Using the Memory Allocation Manager 1 Author: Janick Bergeron Version 1.1 / May 28.

Other primers cover other aspects of developing VMM-compliant verification assets. verification environments and the Register Abstraction Layer package. Despite being a fairly complex design. If you need to ramp-up on VMM itself. this primer focuses on a single aspect of its functionality: the transmit DMA buffers. such as transactors. This primer assumes that you are familiar with VMM. and how to use it in a verification environment to properly configure a design that requires memory buffers. such as DMA buffers.Introduction The VMM Memory Allocation Manager is an element of the VMM Environment Composition application package that can be used to manage the dynamic allocation of memory regions in a large RAM. The design used to illustrate how to use the VMM Memory Allocation Manager is the OpenCore Ethernet Media Access Controller (MAC). It is useful for managing memory buffers required by designs to store temporary. This primer is designed to learn how to configure a memory allocation manager. you should read the other primers in this series. It is similar to C’s malloc() and free() routines. VMM Primer: Using the Memory Allocation Manager 2 . A complete verification environment that can verify this design requires many more elements than a memory allocation manager. This primer will only show the portions of the environment that make use of the VMM Memory Allocation Manager class. These memory buffers are usually configured as a buffer address value (pointer) through a descriptor or a linked list in the design.

The DMA memory. To transmit a frame. You can use the same sequence to use a memory allocation manager and use it to verify your design. All Transmit Buffer Descriptors are marked as “not ready”. As such. 2. Figure 1 A Partial Verification Environment FRMWR RAL DUT Tx RAM The firmware must first initialize the DUT as follows: 1. The DUT Figure 1 shows the (partial) structure of the verification environment surrounding the Ethernet MAC. you should read it in a sequential fashion. The firmware emulation transactor configures and controls the registers inside the design through a RAL model. Transmission is enabled. not being mapped in the DUT address space.This document is written in the same order you would incorporate a memory allocation manager in a verification environment and verify your design. The firmware emulation transactor accesses the DMA memory directly through backdoor read()/write() methods provided by the RAM model itself. is not included in the RAL model. the firmware must use the following procedure: VMM Primer: Using the Memory Allocation Manager 3 .

Each TxBD is located in consecutive memory addresses and has the structure partially by the following RALF specification. } } register TxPNT { bits 32. field RD. up to 128.ralf block oc_ethernet { bytes 4.. regfile TxBD[128] @. The Transmit Buffer Descriptor is marked as “ready”..1. The number of bytes in the frame is specified in the Transmit Buffer Descriptor.. .. .. } } .. . field LEN { bits 16.. Store the frame in consecutive locations in the RAM.. Wait for a “Buffer Transmitted” interrupt. +2 { register TxBD_CTRL { bits 32. 3.. 5.. The DUT contains a programmable number of Transmit Buffer Descriptors (TxBD). A Transmit Buffer will now be available and automatically marked as “not ready” by the DUT. File: SubEnv/oc_ethernet. 2. field PTR { bits 32. field WR. The address of the frame in the RAM is specified in the Transmit Buffer Descriptor. as defined by the TX_BD_NUM register. register TX_BD_NUM { field TX_BD_NUM { bits 8. } VMM Primer: Using the Memory Allocation Manager 4 . 4.

the TxBD is updated to refer to the newly allocated buffer. But this approach is unlikely to expose several categories of functional bugs in the processing of transmit buffers. to exercise more of the addressable space and maximize the number of different buffer locations and sizes. It would also require several different runs of randomly-allocated static buffers to cover different areas of the addressable space. An actual device driver would probably use a static buffer allocation approach. They are large enough to accommodate the largest possible frames to be transmitted and are reused to transmit different frames. a new buffer is allocated for every frame to be transmitted. VMM Primer: Using the Memory Allocation Manager 5 . With dynamic buffer allocation. With static buffer allocation.. } There are two ways DMA buffers can be allocated: statically or dynamically. a dynamic buffer allocation strategy will be used. buffers are allocated when the TxBD are initialized. Buffers are sized according to the need of each frame.. As such.} } . This approach minimizes the buffer allocation problem and restricts the memory usage to a well-defined area. Therefore. Each time. Step 1: Configuration A Memory Allocation Manager will be used to manage the allocation of DMA buffers in the RAM. it must be configured coherently with the configuration of the RAM itself.

. rand wb_slave_cfg ram... .. File: SubEnv/VIPs/wishbone/config. rand sizes_e port_size.The environment configuration descriptor contains an instance of the RAM model configuration descriptor. rand vmm_mam_cfg mam. Both are randomized and constrained to be coherent. mam.sv class wb_cfg. endclass: wb_cfg class wb_slave_cfg extends wb_cfg. mam. } . DWORD. ..min_addr....max_addr.n_bytes == 1. .. rand bit [63:0] min_addr.sv class test_cfg. . WORD. QWORD} sizes_e.end_offset == ram.. rand bit [63:0] max_addr. typedef enum {BYTE...start_offset == ram. It must also contain an instance of the MAM configuration descriptor. mam... . endclass: test_cfg VMM Primer: Using the Memory Allocation Manager 6 . endclass: wb_slave_cfg File: SubEnv/blk_env. constraint test_cfg_valid { .

transmission can be enabled...cfg.. ok = this. Step 2: Buffer Initialization The TxBD must be initialized to the “not ready” state before transmission is enabled. File: SubEnv/blk_env. bit ok.sv: virtual function void gen_cfg(). .set(cfg. Once the buffer descriptors have been initialized.randomize(). begin int bd_addr = 0. ral.When the test configuration descriptor is randomized in the extension of the vmm_env::gen_cfg() method. endfunction: gen_cfg Note that the memory allocation mode and locality remain unconstrained. File: SubEnv/eth_subenv. . This is accomplished in the extension of the vmm_env::cfg_dut() step. the configuration of the memory allocation manager will match the configuration of the memory it manages: they will both have the same number of bytes per address and the same address range.... This will hopefully uncover bugs when DMA buffers are reused. VMM Primer: Using the Memory Allocation Manager 7 . .n_tx_bd). located in adjacent locations or in widely different addresses.TX_BD_NUM.sv virtual task configure(). super.gen_cfg().

It is waiting for the first TxBD to be specified as “ready”.TxBD[bd_addr]. .sv class frmwr_emulator extends vmm_xactor. logic [7:0] bytes[]. Step 3: Frame Transmission The firmware emulation transactor is responsible for initiating the transmission of frames it receives from the frame generator. repeat (cfg. end ... 0). When a frame and a transmit buffer are available.TXEN.. end ..n_tx_bd) begin . a DMA buffer of the appropriate length needs to be allocated and the frame data transferred to the memory corresponding to the newly allocated buffer. 1).RD.. ral..... local task tx_driver().. if (!drop) begin VMM Primer: Using the Memory Allocation Manager 8 .. ral. .write(status... forever begin eth_frame fr.write(status.. bd_addr++. ... endtask: configure The DUT is now ready to transmit frames.. File: SubEnv/eth_subenv. .

bytes[i+1].. bfr = this.request_region(fr. logic [7:0] bytes[]. bytes[i+2].request_region(fr. .byte_size()...byte_pack(bytes). .. for (int i = 0. i < len.. . .. forever begin eth_frame fr.dma_mam.write(bfr...vmm_mam_region bfr.byte_size().. end end endtask: tx_driver . for (int i = 0. all that remains is to update the buffer descriptor to point to the new DMA buffer and mark it as “ready”. local task tx_driver().byte_pack(bytes).byte_size())..sv class frmwr_emulator extends vmm_xactor.. bytes[i+3]}). i < len.get_start_offset() + i.. endclass: frmwr_emulator Of course..byte_size()). once the frame is laid in the DMA buffer. end . len = fr..ram. . {bytes[i]. bytes = new [len + (4 – len % 4)].. fr. i += 4) begin this. i += 4) begin VMM Primer: Using the Memory Allocation Manager 9 .dma_mam. if (!drop) begin vmm_mam_region bfr. File: SubEnv/eth_subenv.. bytes = new [len + (4 – len % 4)]. bfr = this. len = fr. . fr.

bytes[i+3]}). When an interrupt signals that a frame has been transmitted.. it is necessary to free buffers once the frame they contained has been transmitted. end . {bytes[i]. ral... bytes[i+2]. ral..set(len).LEN.. ral. cfg. local task service_irq().. . . .PTR.get_start_offset() + i. end end endtask: tx_driver . File: SubEnv/eth_subenv... The memory allocated for this DMA buffer is then released.this. VMM Primer: Using the Memory Allocation Manager 10 ..write(bfr..ram.get_start_offset()). . forever begin .TxBD[bd_addr]. bfr. This is implemented in the interrupt service routine. endclass: frmwr_emulator Step 4: Frame Completion To avoid memory leakage.. bytes[i+1].TxBD[bd_addr].dma_bfrs[bd_addr] = bfr.write(status.RD..sv class frmwr_emulator extends vmm_xactor..write(status. 1).. all of the Transmit Buffer Descriptors that are newly marked as “not ready” are assumed to have been transmitted. and allow a greedy memory allocation mode to allocate previously-used memory.TxBD[bd_addr].

dma_bfrs[bd_addr]). this... You can now exercise more functionality of the design and have the opportunity to uncover more functional bugs through the random allocation of DMA buffers. if (RD) break.TxBD[bd_addr]..release_region( this...dma_mam... end end . endclass: frmwr_emulator Step 5: Congratulations You have now completed the integration of a VMM Memory Allocation Manager in a verification environment. You may consider reading other publications in this series to learn how to write VMM-compliant command-layer transactors. . end endtask: service_irq ...) begin .RD. RD)..cfg.read(status.if (TXE || TXB) begin . while (.. ral. ... verification environments or integrate a Register Abstraction Layer model or a Data Stream Scoreboard VMM Primer: Using the Memory Allocation Manager 11 ...

VMM Primer: Using the Memory Allocation Manager 12 .

3 / December 1. 2006 1 VMM Primer: Writing Command-Layer Master Transactors 1 .VMM Primer: Writing Command-Layer Master Transactors Author: Janick Bergeron Version 1.

You can use the same sequence to create your own specific transactor. As such. monitors. a transactor may be declared VMM compliant. how to create a simple VMMcompliant master transactor. it does not require the use of many elements of the VMM standard library. assertions and verification environments.Introduction The Verification Methodology Manual for SystemVerilog book was never written as a training book. It was designed to be a reference document that defines what is—and is not—compliant with the methodology described within it. A word of caution however: it may be tempting to stop reading this primer half way through. It is sufficient to achieve the goal of demonstrating. such as slave transactors. functional-layer transactors. The protocol used in this primer was selected for its simplicity. This primer is designed to learn how to write VMM-compliant command-layer master transactors—transactors with a transactionlevel interface on one side and pin wiggling on the other. you should read it in a sequential fashion. step by step. This document is written in the same order you would implement a command-layer transactor. Once a certain minimum level of functionality is met. Other primers will eventually cover other aspects of developing VMM-compliant verification assets. VMM compliance is a matter of degree. But additional VMM functionality—such as VMM Primer: Writing Command-Layer Master Transactors 2 . Because of its simplicity. also known as bus-functional models (BFM). generators. as soon as a functional transactor is available.

If you are interested in learning more about the justifications of various techniques and approaches used in this primer. This primer will show how to apply the various VMM guidelines. VMM Primer: Writing Command-Layer Master Transactors 3 . the APB transactors should be written for the entire 32-bit of address and data information. you should refer to the VMM book under the relevant quoted rules and recommendations. The protocol specification can be found in the AMBA™ Specification (Rev 2. references are made to rule numbers and table numbers: Those are rules and tables in the Verification Methodology Manual for SystemVerilog. you should read—and apply—this primer in its entirety. not attempt to justify them or present alternatives. It is a simple single-master address-based parallel bus providing atomic individual read and write cycles. When writing a reusable transactor.0) available from ARM (http://arm. even though the device in this primer only supports 8 address bits and 16 data bits. not just the device you are using it for the first time. Therefore. Throughout the primer. The Protocol The protocol used in this primer is the AMBA™ Peripheral Bus (APB) protocol. Therefore.callbacks—will make it much easier to use in different verification environments. you have to think about all possible applications it may be used in.com).

There may be multiple slaves on an APB bus but there can only be one master. The name of the interface is prefixed with "apb_" to identify that it belongs to the APB protocol (Rule 4-5). A command-layer master transactor interfaces directly to the DUT signals and initiates transactions upon requests on a transaction-level interface. Slaves are differentiated by responding to different address ranges. Figure 1 Components Used in this Primer Interface Transaction Descriptors Channel Virtual Interface Stimulus Master Transactor DUT Step 1: The Interface The first step is to define the physical signals used by the protocol to exchange information between a master and a slave. The entire content of the file declaring VMM Primer: Writing Command-Layer Master Transactors 4 . The signals are declared inside an interface (Rule 4-4). A single exchange of information (a READ or a WRITE operation) is called a transaction.The Verification Components Figure 1 illustrates the various components that will be created throughout this primer.

are declared as wires (Rule 4-6) inside the interface. This is an old C trick that allows the file to be included multiple times..sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). wire [31:0] prdata. wire penable. File: apb/apb_if. .. whenever required. wire pwrite. wire [31:0] pwdata. listed in the AMBA™ Specification in Section 2..the interface is embedded in an `ifndef/`define/`endif construct.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if. . wire [31:0] paddr. 4-11). endinterface: apb_if `endif Because this is a synchronous protocol.. clocking blocks are used to define the direction and sampling of the signals (Rule 4-7. File: apb/apb_if. wire psel. endinterface: apb_if `endif The signals. VMM Primer: Writing Command-Layer Master Transactors 5 .4. without causing multipledefinition errors.

pwrite. wire [31:0] prdata. 4-11.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). 4-12). penable. wire [31:0] pwdata. endinterface: apb_if `endif The clocking block defining the synchronous signals is specified in the modport for the APB master transactor (Rule 4-9. wire [31:0] paddr. input prdata. wire [31:0] pwdata. clocking mck @(posedge pclk).File: apb/apb_if.. clocking mck @(posedge pclk). endclocking: mck . wire [31:0] paddr. wire psel. psel. wire penable. File: apb/apb_if. wire pwrite. wire pwrite. wire [31:0] prdata.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). VMM Primer: Writing Command-Layer Master Transactors 6 . output paddr. pwdata. wire penable.. wire psel. The clock signal need not be specified as it is implicit in the clocking block.

endinterface: apb_if `endif The interface declaration is now sufficient for writing a master APB transactor. when these transactors will be written.apb_rdata . pwdata. endclocking: mck modport master(clocking mck)..).)... penable. (apb0.sv module tb_top.. To be fully compliant. alongside of the DUT instantiation (Rule 413). pwrite. .apb_sel . It is instantiated in a top-level module. .paddr[7:0] ). VMM Primer: Writing Command-Layer Master Transactors 7 .pwdata[15:0]).apb_addr .pwrite ).. it should eventually include a modport for a slave and a passive monitor transactor (Rule 4-9). Step 2: Connecting to the DUT The interface may now be connected to the DUT. (apb0.. (apb0. slave_ip dut_slv(.apb_write .. File: Command_Master_Xactor/tb_top. apb_if apb0(. (apb0.. .output paddr.penable ).apb_wdata .psel ).. The connection to the DUT pins are specified using a hierarchical reference to the wires in the interface instance.. psel.. (apb0.prdata[15:0]).apb_enable . These can be added later. input prdata. (apb0.

apb_addr . File: apb/apb_master.apb_sel . (apb0.clk always #10 clk = ~clk...endmodule: tb_top This top-level module also contains the clock generators (Rule 415). one for the READ transaction and one for the WRITE transaction.psel). (clk)).apb_write . Traditionally. File: Command_Master_Xactor/tb_top. Step 3: The Transaction Descriptor The next step is to define the APB transaction descriptor. bit clk = 0. (apb0.paddr[7:0]). my_design dut(.sv module tb_top.sv task read(input bit [31:0] addr. apb_if apb0(clk). . using the bit type (Rule 4-17) and ensuring that no clock edges will occur at time zero (Rule 4-16). (apb0. (apb0.penable).. output logic [31:0] data). . tasks would have been defined.. (apb0.apb_rdata . VMM Primer: Writing Command-Layer Master Transactors 8 . . endmodule: tb_top apb0..apb_enable ..pwrite).prdata[15:0]).apb_wdata ..pwdata[15:0]).

rand bit [31:0] addr. 4-62).new(this. File: apb/apb_rw. rand logic [31:0] data. "class").task write(input bit [31:0] addr. 4-62) and public rand properties for each task argument (Rule 4-59. A random test requires a transaction descriptor (Rule 4-54). function new().... This works well for directed tests. This instance of the message service interface is passed to the vmm_data constructor (Rule 4-58). static vmm_log log = new("apb_rw". WRITE} kind.sv" class apb_rw extends vmm_data. endclass: apb_rw . If an argument is the same across multiple tasks. rand enum {READ.log). a single property can be used. It also needs a static vmm_log property instance used to issue messages from the transaction descriptor. endfunction: new .sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. input bit [31:0] data). but not at all for random tests. containing a public rand property enumerating the directed tasks (Rule 4-60.. super. This descriptor is a class (Rule 4-53) extended from the vmm_data class (Rule 4-55). `endif VMM Primer: Writing Command-Layer Master Transactors 9 .

rand bit [31:0] addr.. This is done using the `vmm_channel macro (Rule 4-56). it has the minimum functionality to be used by a transactor. Although the transaction descriptor is not yet VMM-compliant..sv" class apb_rw extends vmm_data. function new(). rand logic [31:0] data. endclass: apb_rw `vmm_channel(apb_rw) . The type for the data property is logic as it is a superset of the bit type and will allow the description of READ cycles to reflect unknown results. super.. `endif VMM Primer: Writing Command-Layer Master Transactors 10 .Note how the same property is used for data. A transactionlevel interface will be required to transfer transaction descriptors to a transactor to be executed. endfunction: new .sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. It will be interpreted differently depending on the transaction kind (Rule 4-71). rand enum {READ. WRITE} kind. static vmm_log log = new("apb_rw". File: apb/apb_rw. "class"). the random content is initially ignored and it will be replaced by the data value that was read. In a READ transaction. In a WRITE transaction..log). it is interpreted as the data to be written.new(this.

. class apb_master extends vmm_xactor. File: apb/apb_master. class apb_master extends vmm_xactor. input bit [31:0] data). `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if. . VMM Primer: Writing Command-Layer Master Transactors 11 . They are also declared protected to prevent them from being called from outside the class and create concurrent bus access problems.Step 4: The Master Transactor The master transactor can now be started.sv" .sv `ifndef APB_MASTER__SV `define APB_MASTER__SV ... endclass: apb_master `endif The task implementing the READ and WRITE cycles are implemented in this class. ..sv" `include "apb_rw. output logic [31:0] data). They are declared virtual so the transactor may be extended to modify the behavior of these tasks if so required... It is a class (Rule 4-91) derived from the vmm_xactor base class (4-92). . virtual protected task read(input bit [31:0] addr... endtask: read virtual protected task write(input bit [31:0] addr...

. File: apb/apb_master..sigs = sigs. 4-112).master sigs.sv" ..master sigs.. this.sv" `include "apb_rw. apb_rw_channel in_chan = null). if (in_chan == null) in_chan = new("APB Master Input Channel". endclass: apb_master `endif The transactor needs a transaction-level interface to receive transactions to be executed and a physical-level interface to wiggle pins. virtual apb_if. apb_rw_channel in_chan. endfunction: new .. endtask: write .. VMM Primer: Writing Command-Layer Master Transactors 12 .sv `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if. 4-113) and both are saved in public properties (Rule 4-109. Both are passed to the transactor as constructor arguments (Rule 4-108. super. virtual protected task read(input bit [31:0] addr. class apb_master extends vmm_xactor.in_chan = in_chan. The former is done using a vmm_channel instance and the latter is done using a virtual modport.. name. this. output logic [31:0] data).new("APB Master".. int unsigned stream_id. function new(string name. virtual apb_if... . name). stream_id).

. or explicit signal assignments may be used in the constructor.new("APB Master".master sigs. stream_id). apb_rw_channel in_chan = null)... the input channel must be flushed and the critical output signals must be driven to their idle state.. This is accomplished in the extension of the vmm_xactor::reset_xactor() method (Table A-8). endtask: write .. class apb_master extends vmm_xactor.. int unsigned stream_id. name).sigs = sigs.sv" .. VMM Primer: Writing Command-Layer Master Transactors 13 .sv" `include "apb_rw. virtual apb_if. File: apb/apb_master. super. . endtask: read virtual protected task write(input bit [31:0] addr. apb_rw_channel in_chan. virtual apb_if. this. if (in_chan == null) in_chan = new("APB Master Input Channel"..sv `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if. This method may be called in the transactor constructor to initialize the output signals to their idle state.master sigs. endclass: apb_master `endif When the transactor is reset. input bit [31:0] data). name.. function new(string name.

endfunction: reset_xactor .mck. this. nonblocking and out-of-order execution models (Rules 4-121.psel <= ’0. output logic [31:0] data). this.sigs. File: apb/apb_master.reset(rst_typ). super. endfunction: new virtual function void reset_xactor( reset_e rst_typ = SOFT_RST).sigs.. The most flexible transaction execution mechanism uses the active slot as it supports block. endtask: write .. endclass: apb_master `endif The transaction descriptors are pulled from the input channel and translated into method calls in the main() task (Rule 4-93). Because the protocol supports being suspended between transactions.sigs. virtual protected task read(input bit [31:0] addr.sv `ifndef APB_MASTER__SV VMM Primer: Writing Command-Layer Master Transactors 14 . this.in_chan.in_chan = in chan. input bit [31:0] data). this.psel <= ’0. .penable <= ’0...mck. the vmm_xactor::wait_if_stopped_or_empty() method is used to suspend the execution of the transactor if it is stopped. 4-122. .flush(). 4-123.. 4-124 and 4-129).this.mck.penable <= ’0... this.. endtask: read virtual protected task write(input bit [31:0] addr.sigs.mck.

main().sigs.psel <= ’0.in_chan). endfunction: reset_xactor virtual protected task main().mck).`define APB_MASTER__SV `include "apb_if.in_chan = in chan. tr. @ (this. this. .read(tr.reset(rst_typ).data). this. class apb_master extends vmm_xactor..data).sv" . forever begin apb_rw tr. super. tr. int unsigned stream_id.flush().. virtual apb_if.penable <= ’0.wait_if_stopped_or_empty(this.mck. virtual apb_if.master sigs.master sigs.sigs.mck. stream_id).new("APB Master".mck. this. name.in_chan.penable <= ’0. this. super.sigs. this. this.sigs. name).psel <= ’0. endfunction: new virtual function void reset_xactor( reset_e rst_typ = SOFT_RST).addr. apb_rw_channel in_chan = null). apb_rw::WRITE: this. VMM Primer: Writing Command-Layer Master Transactors 15 .in_chan..kind) apb_rw::READ : this.sigs = sigs.sigs.addr.. apb_rw_channel in_chan. case (tr. function new(string name. if (in_chan == null) in_chan = new("APB Master Input Channel". this. this. this.sv" `include "apb_rw.in_chan. this.start().mck.write(tr. super.activate(tr).

endtask: write endclass: apb_master `endif The READ and WRITE tasks are coded exactly as they would be if good old Verilog was used. not an edge of an input signal (Rule 4-7.sigs.paddr <= addr..mck..remove().sigs.. 4-12). end endtask: main virtual protected task read(input bit [31:0] addr. this.psel <= ’1. Similarly.. File: apb/apb_master.. . It is a simple matter of assigning output signals to the proper value then sampling input signals at the right point in time. this. output logic [31:0] data).. The only difference is that the physical signals are accessed through the clocking block of the virtual modport instead of pins on a module and they can only be assigned using nonblocking assignments.sv . virtual protected task read(input bit [31:0] addr. input bit [31:0] data).mck.sigs.endcase this.in_chan.complete().mck. .in_chan. this.. this.. output logic [31:0] data).pwrite <= ’0. the active clock edge is defined by waiting on the clocking block itself. . endtask: read virtual protected task write(input bit [31:0] addr. VMM Primer: Writing Command-Layer Master Transactors 16 .

penable <= ’1. @ (this. this.sigs.mck. the transactor can now be used to exercise the DUT. this.paddr <= addr. @ (this. data = this.psel <= ’1.sigs.psel <= ’0.mck).sigs.sigs.sigs.pwrite <= ’1.penable <= ’0. this. this.sigs.sigs.mck). this.@ (this.sigs.mck. endtask: write Step 5: The First Test Although not completely VMM-compliant.mck. Note that if the transactor is required to configure the DUT in the extension of the vmm_env::cfg_dut() method. @ (this.penable <= ’1. 4-35) and started in the extension of the vmm_env::start() method (Rule 4-41). VMM Primer: Writing Command-Layer Master Transactors 17 .sigs. The transactor is constructed in the extension of the vmm_env::build() method (Rule 4-34. It is instantiated in a verification environment class. input bit [31:0] data). extended from the vmm_env base class.mck. it needs to be started in that latter method. this. this.mck.sigs.penable <= ’0.mck).mck.psel <= ’0.mck. this.sigs.mck.sigs.mck. this.sigs. endtask: read virtual protected task write(input bit [31:0] addr.mck). this.sigs.mck.pwdata <= data.prdata. The reference to the interface encapsulating the APB physical signals is made using a hierarchical reference in the extension of the vmm_env::build() method.mck.sigs.

apb0). File: Command_Master_Xactor/test_simple. The test is written in a program (Rule 4-27) that instantiates the verification environment (Rule 4-28). The directed stimulus is created by instantiating transaction descriptors appropriately filled.start().sv `include "tb_env.sv" `include "apb_master. endclass: tb_env `endif A simple test to perform a write followed by a read of the same address can now be written and executed to verify the correct operation of the transactor and the DUT interface. virtual function void build().sv" class tb_env extends vmm_env.mst. 0. any additional property will be randomized instead of defaulting to always the same value. endfunction: build virtual task start().sv" VMM Primer: Writing Command-Layer Master Transactors 18 .. super.sv `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm. apb_master mst.mst = new("0". endtask: start .. super. This way. this.File: Command_Master_Xactor/tb_env. this.start_xactor(). only constraining those properties that are needed for the directed test. It is a good idea to randomize these descriptors. tb_top.build().

if (!ok) begin `vmm_fatal(log.program simple_test.data[15:0]) begin `vmm_error(log.randomize() with { kind == READ. if (rd. wr. end env. ok = rd. "Unable to randomize WRITE cycle").put(rd). }. vmm_log log = new("Test". end env. addr == wr. "Readback value != write value").mst.data[15:0] !== wr. ok = wr. env. initial begin apb_rw rd. "Simple").report().addr. tb_env env = new. wr = new. }.in_chan. rd = new.start(). $finish(). end log.randomize() with { kind == WRITE.mst.put(wr). end endprogram VMM Primer: Writing Command-Layer Master Transactors 19 . if (!ok) begin `vmm_fatal(log. "Unable to randomize READ cycle"). bit ok.in_chan.

The “pre-transaction” callback method allows errors to be injected and delays to be inserted. to verify the transactor and the DUT using different addresses. Step 6: Extension Points The transactor. The problem is that the transactor. to synchronize the start of a transaction with some other external event. will provide basic functionality. or modify a transaction to inject errors—without modifying the transactor itself or constantly rewrite the apb_master::read() and apb_master::write() virtual methods. The “post-transaction” callback method allows delays to be inserted and the result of the transaction to be recorded in a functional coverage model or checked against an expected response. Callback methods should be provided before and after a transaction executes (Recommendations 4-155 and 4-156).This test can be run many times. is not very reusable. each time with a different seed. A callback method allows a user to extend the behavior of a transactor without having to modify the transactor itself. as coded. It will not be possible to modify the behavior of this transactor—for example to introduce delays between transactions. You may be tempted to stop here because you now have a transactor that can perform READ and WRITE cycles with identical capabilities to one you would have written using the old Verilog language. as presently coded. The callback methods are first defined as virtual tasks or virtual void functions (Rule 4-160) in a callback façade class extended from the vmm_xactor_callbacks base class (Rule 4- VMM Primer: Writing Command-Layer Master Transactors 20 .

int unsigned stream_id. function new(string name.sv" `include "apb_rw.penable <= ’0.master sigs.mck. name. endfunction: new VMM Primer: Writing Command-Layer Master Transactors 21 . class apb_master_cbs extends vmm_xactor_callbacks.new("APB Master". this. It is a good idea to create a mechanism in the “pre-transaction” callback method to allow an entire transaction to be skipped or dropped (Rule 4-160). this.sigs.sigs = sigs. virtual task pre_cycle(apb_master xactor. super.in_chan = in chan. apb_rw_channel in_chan. apb_rw cycle). endtask: post_cycle endclass: apb_master_cbs class apb_master extends vmm_xactor. virtual apb_if.psel <= ’0.mck.master sigs. ref bit drop). stream_id).sv `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if.sigs.159). File: apb/apb_master. virtual apb_if.sv" typedef class apb_master. this. name). this. apb_rw_channel in_chan = null). apb_rw cycle. endtask: pre_cycle virtual task post_cycle(apb_master xactor. if (in_chan == null) in_chan = new("APB Master Input Channel".

read(tr.in_chan. endcase this..kind) apb_rw::READ : this.main().. tr.complete(). apb_rw::WRITE: this.remove().psel <= ’0. @ (this..mck). this.in_chan.sigs. this. this.reset(rst_typ). case (tr. this. .mck.penable <= ’0. super.data). forever begin apb_rw tr. input bit [31:0] data)..virtual function void reset_xactor( reset_e rst_typ = SOFT_RST).activate(tr). endtask: read virtual protected task write(input bit [31:0] addr.write(tr.sigs.. endtask: write endclass: apb_master `endif VMM Primer: Writing Command-Layer Master Transactors 22 . this.in_chan. super..sigs.in_chan..addr. this. output logic [31:0] data).mck. . endfunction: reset_xactor virtual protected task main().flush().addr.data).in_chan. end endtask: main virtual protected task read(input bit [31:0] addr. . .wait_if_stopped_or_empty(this.start(). this. tr.in_chan)..

function new(string name. apb_rw cycle.psel <= ’0.sv" typedef class apb_master. this. File: apb/apb_master.sv" `include "apb_rw. super.penable <= ’0. virtual apb_if.new("APB Master". name. stream_id). virtual task pre_cycle(apb_master xactor. endtask: pre_cycle virtual task post_cycle(apb_master xactor.Next. endtask: post_cycle endclass: apb_master_cbs class apb_master extends vmm_xactor.master sigs. apb_rw_channel in_chan. virtual apb_if.mck.in_chan = in chan. using the `vmm_callback() macro (Rule 4-163). the appropriate callback method needs to be invoked at the appropriate point in the execution of the transaction.master sigs.sigs = sigs. this. class apb_master_cbs extends vmm_xactor_callbacks. this.mck. ref bit drop). endfunction: new VMM Primer: Writing Command-Layer Master Transactors 23 . apb_rw_channel in_chan = null).sigs. if (in_chan == null) in_chan = new("APB Master Input Channel". name). int unsigned stream_id. apb_rw cycle).sigs.sv `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if. this.

this.read(tr. @ (this. if (drop) begin this.virtual function void reset_xactor( reset_e rst_typ = SOFT_RST).addr. forever begin apb_rw tr. apb_rw::WRITE: this.psel <= ’0. tr. super. this.data). continue.kind) apb_rw::READ : this. tr.reset(rst_typ).flush(). tr.complete().activate(tr). end this.sigs.mck.in_chan).addr. output logic [31:0] data).in_chan. `vmm_callback(apb_master_cbs. super. this.in_chan.in_chan.mck. drop = 0. post_cycle( this.remove().in_chan. this. `vmm_callback(apb_master_cbs.mck).sigs. this. tr)). case (tr. pre_cycle( this.write(tr. drop)).sigs.in_chan. endcase this.penable <= ’0. bit drop.start().main().in_chan.remove(). end endtask: main virtual protected task read(input bit [31:0] addr.data). endfunction: reset_xactor virtual protected task main(). this.wait_if_stopped_or_empty(this. VMM Primer: Writing Command-Layer Master Transactors 24 .

apb_rw cycle.. endtask: write endclass: apb_master `endif Step 7: Debug Messages To be truly reusable. virtual task pre_cycle(apb_master xactor. This capability may even be a basic requirement if you plan on shipping encrypted or compiled code. ref bit drop). is doing or has done. ..sv `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if.sv" typedef class apb_master.sv" `include "apb_rw. VMM Primer: Writing Command-Layer Master Transactors 25 . These debug messages are inserted using the `vmm_trace(). endtask: read virtual protected task write(input bit [31:0] addr.. Debug messages should be added at judicious points to indicate what the transactor is about to do.. class apb_master_cbs extends vmm_xactor_callbacks. File: apb/apb_master. input bit [31:0] data).. `vmm_debug() or `vmm_verbose() macros (Recommendation 4-51). it should be possible to understand what the transactor does and debug its operation without having to inspect the source code.

virtual apb_if.wait_if_stopped_or_empty(this.sigs. name). this.in_chan.master sigs. function new(string name.master sigs.penable <= ’0. super. this. if (in_chan == null) in_chan = new("APB Master Input Channel".mck. this.in_chan.mck.mck. name.mck).sigs. bit drop. this. super.activate(tr).in_chan = in chan. apb_rw cycle).flush(). this.endtask: pre_cycle virtual task post_cycle(apb_master xactor. stream_id).reset(rst_typ).in_chan).sigs.psel <= ’0. apb_rw_channel in_chan = null). this. VMM Primer: Writing Command-Layer Master Transactors 26 . apb_rw_channel in_chan. endfunction: new virtual function void reset_xactor( reset_e rst_typ = SOFT_RST).sigs.new("APB Master". this. super.penable <= ’0. forever begin apb_rw tr. int unsigned stream_id. this.psel <= ’0. @ (this. endfunction: reset_xactor virtual protected task main(). this. virtual apb_if.main(). endtask: post_cycle endclass: apb_master_cbs class apb_master extends vmm_xactor.sigs = sigs.sigs.mck.

\n".write(tr. {"Starting transaction.complete().. end `vmm_trace(log..remove().psdisplay(" ")}). apb_rw::WRITE: this. endtask: read virtual protected task write(input bit [31:0] addr.. `vmm_callback(apb_master_cbs. tr. tr. if (drop) begin `vmm_debug(log.. endtask: write endclass: apb_master VMM Primer: Writing Command-Layer Master Transactors 27 .addr.in_chan. continue. pre_cycle( this. endcase this. .psdisplay(" ")})..\n". {"Dropping transaction. tr. drop)).data).in_chan.kind) apb_rw::READ : this. tr. this.. tr)).drop = 0.in_chan. {"Completed transaction.read(tr.. `vmm_trace(log. end endtask: main virtual protected task read(input bit [31:0] addr.psdisplay(" ")}).in_chan. `vmm_callback(apb_master_cbs. . tr.data).. case (tr. output logic [31:0] data).\n". tr.addr. input bit [31:0] data). this.start().remove().. post_cycle( this. this..

rand logic [31:0] data. rand enum {READ.. File: apb/apb_rw. function new(). File: Command_Master_Xactor/Makefile % vcs –sverilog –ntb_opts vmm +vmm_log_default=trace . That’s because the psdisplay() method.log).`endif Step 8: Standard Methods You can now run the “simple test” with the latest version of the transactor and increase the message verbosity to see debug messages displayed as the test executes the various transactions. defined in the vmm_data base class does not know about the content of the APB transaction descriptor. "class"). WRITE} kind.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm.new(this. static vmm_log log = new("apb_rw". VMM Primer: Writing Command-Layer Master Transactors 28 . it is necessary to overload this method in the transaction descriptor class (Rule 4-76). super. For that method to display the information that is relevant for the APB transaction..sv" class apb_rw extends vmm_data. rand bit [31:0] addr. But the messages will be not very useful as they do not display the content of the executed transactions.

this.. VMM Primer: Writing Command-Layer Master Transactors 29 . $sformat(psdisplay.endfunction: new . copying transaction descriptors (vmm_data::copy()). virtual function string psdisplay(string prefix = "").addr.name().. this. The vmm_data::psdisplay() method is one of the pre-defined methods in the vmm_data base class that users expect to be provided to simplify and abstract common operations on transaction descriptors. this. `endif Re-running the test with increased verbosity now yields useful and meaningful debug messages. prefix. WRITE} kind. rand enum {READ. static vmm_log log = new("apb_rw". These common operations include creating transaction descriptors (vmm_data::allocate()).. rand bit [31:0] addr..data). rand logic [31:0] data.kind. endfunction: psdisplay .. endclass: apb_rw `vmm_channel(apb_rw) .. "class"). comparing transaction descriptors (vmm_data::compare()) and checking that the content of transaction descriptors is valid (vmm_data::is_valid()) (Rule 476).sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. "%sAPB %s @ 0x%h = 0x%h". File: apb/apb_rw.sv" class apb_rw extends vmm_data.

"%sAPB %s @ 0x%h = 0x%h".new(this. endfunction: is_valid virtual function bit compare(input vmm_data to. apb_rw tr. return tr. tr.addr. $sformat(psdisplay. endfunction: copy virtual function string psdisplay(string prefix = ""). "Cannot copy into non-apb_rw \n instance"). if (to == null) begin VMM Primer: Writing Command-Layer Master Transactors 30 . endfunction: psdisplay virtual function bit is_valid(bit silent = 1.function new(). prefix. return 1. return tr.data. to)) begin `vmm_fatal(log.kind = this. this.copy_data(tr). return null. apb_rw tr = new. endfunction: new virtual function vmm_data allocate(). else if (!$cast(tr. end super. tr.log).data = this. super.addr. endfunction: allocate virtual function vmm_data copy(vmm_data to = null). this. input int kind = -1).data). tr.kind. if (to == null) tr = new.kind. this. int kind = -1).addr = this.name(). apb_rw tr. output string diff.

addr.data. return 0. end if (this.addr !== tr. end if (this. "Cannot compare against \n non-apb_rw instance"). `endif VMM Primer: Writing Command-Layer Master Transactors 31 . return 0. this. to)) begin `vmm_fatal(log.addr) begin $sformat(diff.`vmm_fatal(log. tr. "Cannot compare to NULL \n reference"). "Addr 0x%h !== 0x%h".data) begin $sformat(diff. "Kind %s !== %s". end else if (!$cast(tr. "Data 0x%h !== 0x%h". end if (this. tr. return 0.kind. return 0. this..kind). endfunction: compare endclass: apb_rw `vmm_channel(apb_rw) . this. return 0.data !== tr. end return 1.kind != tr..kind) begin $sformat(diff.addr).data). tr.

sv" class apb_rw extends vmm_data. function new(). apb_rw tr = new.Three other standard methods. 524) in the transaction descriptor file. vmm_data::byte_pack() and vmm_data::byte_unpack() should also be overloaded for packet-oriented transactions. endfunction: allocate VMM Primer: Writing Command-Layer Master Transactors 32 . WRITE} kind.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. where the content of the transaction is transmitted over a physical interface (Recommendation 4-77). super. Step 9: Transaction Generator To promote the use of random stimulus. rand enum {READ. rand logic [31:0] data. it is a good idea to pre-define random transaction generators whenever transaction descriptors are defined. endfunction: new virtual function vmm_data allocate().log). vmm_data:byte_size(). return tr.new(this. File: apb/apb_rw. rand bit [31:0] addr. It is a simple matter of using the `vmm_atomic_gen() and `vmm_scenario_gen() macros (Recommendation 5-23. "class"). static vmm_log log = new("apb_rw".

virtual function vmm_data copy(vmm_data to = null); apb_rw tr; if (to == null) tr = new; else if (!$cast(tr, to)) begin `vmm_fatal(log, "Cannot copy into non-apb_rw \n instance"); return null; end super.copy_data(tr); tr.kind = this.kind; tr.addr = this.addr; tr.data = this.data; return tr; endfunction: copy virtual function string psdisplay(string prefix = ""); $sformat(psdisplay, "%sAPB %s @ 0x%h = 0x%h", prefix, this.kind.name(), this.addr, this.data); endfunction: psdisplay virtual function bit is_valid(bit silent = 1, int kind = -1); return 1; endfunction: is_valid virtual function bit compare(input vmm_data to, output string diff, input int kind = -1); apb_rw tr; if (to == null) begin `vmm_fatal(log, "Cannot compare to NULL \n reference"); return 0; end else if (!$cast(tr, to)) begin `vmm_fatal(log, "Cannot compare against \n non-apb_rw instance");

VMM Primer: Writing Command-Layer Master Transactors 33

return 0; end if (this.kind != tr.kind) begin $sformat(diff, "Kind %s !== %s", this.kind, tr.kind); return 0; end if (this.addr !== tr.addr) begin $sformat(diff, "Addr 0x%h !== 0x%h", this.addr, \n tr.addr); return 0; end if (this.data !== tr.data) begin $sformat(diff, "Data 0x%h !== 0x%h", this.data, \n tr.data); return 0; end return 1; endfunction: compare endclass: apb_rw `vmm_channel(apb_rw) `vmm_atomic_gen(apb_rw, "APB Bus Cycle") `vmm_scenario_gen(apb_rw, "APB Bus Cycle") `endif

Step 10: Top-Level File
To help users include all necessary files, without having to know the detailed filenames and file structure of the transactor, interface and transaction descriptor, it is a good idea to create a top-level file that will automatically include all source files that make up the verification IP for a protocol.
VMM Primer: Writing Command-Layer Master Transactors 34

File: apb/apb.sv
`ifndef APB__SV `define APB__SV `include `include `include `include `endif "vmm.sv" "apb_if.sv" "apb_rw.sv" "apb_master.sv"

In this example, we implemented only a master transactor; but a complete VIP for a protocol would also include a slave transactor and a passive monitor transactor. All of these transactors would be included in the top-level file.

Step 11: Congratulations!
You have now completed the creation of a VMM-compliant command-layer master transactor! Upon reading this primer, you probably realized that there is much code that is similar across different master transactors. Wouldn’t be nice if you could simply cut-and-paste from an existing VMMcompliant master transactor and only modify what is unique or different for your protocol? That can easily be done using the vmmgen tool provided with VCS 2006.06-6. Based on a few simple question and answers, it will create a template for various components of a VMM-compliant master transactor. Command
% vmmgen –l sv

VMM Primer: Writing Command-Layer Master Transactors 35

The relevant templates for writing command-layer master transactors are: 1. Physical interface declaration 2. Transaction Descriptor 3. Driver, Physical-level, Half-duplex Note that the menu number used to select the appropriate template may differ from the number in the above list. You may consider reading other publications in this series to learn how to write VMM-compliant command-layer slave transactors, command-layer passive monitor transactors, functional-layer transactors or verification environments.

VMM Primer: Writing Command-Layer Master Transactors 36

4 / May17.VMM Primer: Writing Command-Layer Slave Transactors Author: Janick Bergeron Version 0. 2007 1 VMM Primer: Writing Command-Layer Slave Transactors 1 .

It was designed to be a reference document that defines what is—and is not—compliant with the methodology described within it. You can use the same sequence to create your own specific transactor. It is sufficient to achieve the goal of demonstrating. Other primers will eventually cover other aspects of developing VMM-compliant verification assets.Introduction The Verification Methodology Manual for SystemVerilog book was never written as a training book. generators. how to create a simple VMMcompliant slave transactor. The protocol used in this primer was selected for its simplicity. you should read it in a sequential fashion. step by step. This primer is designed to learn how to write VMM-compliant command-layer slave transactors—transactors that react to pin wiggling and supply the necessary response back. it does not require the use of many elements of the VMM standard library. such as master transactors. as soon as a functional transactor is available. A word of caution however: it may be tempting to stop reading this primer half way through. assertions and verification environments. Once a certain minimum level of functionality is met. This document is written in the same order you would implement a command-layer slave transactor. functionallayer transactors. monitors. But additional VMM functionality—such as VMM Primer: Writing Command-Layer Slave Transactors 2 . As such. VMM compliance is a matter of degree. a slave transactor may be declared VMM compliant. Because of its simplicity.

Therefore.com). It is a simple single-master address-based parallel bus providing atomic individual read and write cycles. If you are interested in learning more about the justifications of various techniques and approaches used in this primer. The Protocol The protocol used in this primer is the AMBA™ Peripheral Bus (APB) protocol. you should refer to the VMM book under the relevant quoted rules and recommendations. The protocol specification can be found in the AMBA™ Specification (Rev 2. you should read—and apply—this primer in its entirety. the APB slave transactor should be written for the entire 32-bit of address and data information. you have to think about all possible applications it may be used in. Therefore. VMM Primer: Writing Command-Layer Slave Transactors 3 . When writing a reusable slave transactor. This primer will show how to apply the various VMM guidelines. not attempt to justify them or present alternatives.a request/response model to a higher-level transactor or callbacks— will make it much easier to use in different verification environments. even though the device you will be initially using with this transactor supports 8 address bits and 16 data bits. not just the device you are using it for the first time.0) available from ARM (http://arm.

The Verification Components Figure 1 illustrates the various components that will be created throughout this primer. The entire content of the file declaring the VMM Primer: Writing Command-Layer Slave Transactors 4 . Slaves are differentiated by responding to different address ranges. There may be multiple slaves on an APB bus but there can only be one master. The name of the interface is prefixed with “apb_” to identify that it belongs to the APB protocol (Rule 4-5). Figure 1 Components Used in This Primer Interface Virtual Interface Optional Response Channel RAM DUT Slave Transactor APB Transaction Descriptors Step 1: The Interface The first step is to define the physical signals used by the protocol to exchange information between a master and a slave. The signals are declared inside an interface (Rule 4-4). A single exchange of information (a READ or a WRITE operation) is called a transaction. A command-layer slave transactor interfaces directly to the DUT signals and optionally requests transaction data fulfillment on a transaction-level interface.

whenever required. File: apb/apb_if. wire psel. endinterface: apb_if `endif The signals. wire [31:0] prdata. VMM Primer: Writing Command-Layer Slave Transactors 5 .. wire [31:0] pwdata.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if.. wire penable.. without causing multiple-definition errors. wire [31:0] paddr. . are declared as wires (Rule 4-6) inside the interface. 4-11). clocking blocks are used to define the direction and sampling of the signals (Rule 4-7.. This is an old C trick that allows the file to be included multiple time. wire pwrite. listed in the AMBA™ Specification in Section 2. . endinterface: apb_if `endif Because this is a synchronous protocol.4.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). File: apb/apb_if.interface is embedded in an `ifndef/`define/`endif construct.

4-12). pwrite.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). endclocking: sck . psel. wire [31:0] paddr. wire [31:0] pwdata.. clocking sck @(posedge pclk). The clock signal need not be specified as it is implicit in the clocking block. wire pwrite. clocking sck @(posedge pclk). wire [31:0] prdata. wire [31:0] pwdata. File: apb/apb_if. psel. wire penable. wire psel. wire [31:0] prdata.. endclocking: sck VMM Primer: Writing Command-Layer Slave Transactors 6 . pwrite. wire pwrite. wire [31:0] paddr.File: apb/apb_if. pwdata. output prdata.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). wire penable. pwdata. wire psel. penable. output prdata. penable. input paddr. input paddr. endinterface: apb_if `endif The clocking block defining the synchronous signals is specified in the modport for the APB slave transactor (Rule 4-9. 4-11.

modport slave(clocking sck). These can be added later...apb_addr . (apb0.apb_rdata . endinterface: apb_if `endif The interface declaration is now sufficient for writing a APB slave transactor. (apb0..apb_write ..psel ). when these transactors will be written. endmodule: tb_top (apb0.. (apb0. (apb0..apb_sel . (apb0. The connection to the DUT pins are specified using a hierarchical reference to the wires in the interface instance. alongside of the DUT instantiation (Rule 4-13).apb_enable ..pwrite ).). To be fully compliant. Step 2: Connecting to the DUT The interface may now be connected to the DUT. .pwdata[15:0]). master_ip dut_mst(.apb_wdata .sv module tb_top.prdata[15:0]). File: Command_Slave_Xactor/tb_top...penable ).. apb_if apb0(.).. VMM Primer: Writing Command-Layer Slave Transactors 7 . it should eventually include a modport for a master and a passive monitor transactor (Rule 4-9). It is instantiated in a top-level module.paddr[7:0] ). . .

apb_rdata (apb0.apb_write (apb0.. 4-62) and public properties for each parameter or value in the transaction (Rule 4-59.apb_addr (apb0..penable ). apb_if apb0(clk). . using the bit type (Rule 4-17) and ensuring that no clock edges will occur at time zero (Rule 4-16). This descriptor is a class (Rule 4-53) extended from the vmm_data class (Rule 4-55)..psel ).apb_sel (apb0. 4-62). ..This top-level module also contains the clock generators (Rule 4-15).prdata[15:0]).sv module tb_top.paddr[7:0] ). This instance of the message service interface is passed to the vmm_data constructor (Rule 4-58).pwrite ). always #10 clk = ~clk.. .apb_wdata (apb0. File: Command_Slave_Xactor/tb_top. It also needs a static vmm_log property instance used to issue messages from the transaction descriptor. . .. . my_design dut(. containing a public property enumerating the various transactions that can be observed and reacted to by the slave transactor (Rule 4-60. bit clk = 0.clk (clk)). . endmodule: tb_top Step 3: The Transaction Descriptor The next step is to define the APB transaction descriptor (Rule 4-54). . .. VMM Primer: Writing Command-Layer Slave Transactors 8 .pwdata[15:0]).apb_enable (apb0.

. The type for the data property is logic as it will allow the description of READ cycles to reflect unknown results.File: apb/apb_rw. Because the APB does not support concurrent read/write transactions. endclass: apb_rw .new(this.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. super. it is the data value that is to be driven as the read value.log). Although the transaction descriptor is not yet VMM-compliant. WRITE} kind. function new(). VMM Primer: Writing Command-Layer Slave Transactors 9 .. logic [31:0] data. In a READ transaction. `endif A single property is used for “data”. enum {READ. there can only be one data value valid at any given time. "class"). The data class property is interpreted differently depending on the transaction kind (Rule 4-71).sv" class apb_rw extends vmm_data. it is interpreted as the data to be written. endfunction: new . In a WRITE transaction.. despite the fact that the APB bus has separate read and write data buses. bit [31:0] addr. it is has the minimum functionality to be used by a slave transactor. static vmm_log log = new("apb_rw"..

The physical-level interface is done using a virtual modport passed to the transactor as a constructor argument (Rule 4-108) and is saved in a public property (Rule 4109). File: apb/apb_slave..Step 4: The Slave Transactor The slave transactor can now be started. virtual apb_if.slave sigs.. ..slave sigs. .sv" `include "apb_rw. .sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. int unsigned stream_id. class apb_slave extends vmm_xactor. function new(string name.. File: apb/apb_slave. virtual apb_if. super.. It is a class (Rule 4-91) derived from the vmm_xactor base class (4-92)..sigs = sigs... stream_id)..sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV . .)... this.new("APB Slave". name. VMM Primer: Writing Command-Layer Slave Transactors 10 . class apb_slave extends vmm_xactor..sv" . endclass: apb_slave `endif The transactor needs a physical-level interface to observe and react to transactions.

.main(). class apb_slave extends vmm_xactor. not an edge of an input signal (Rule 4-7. .. super. virtual apb_if... .. endfunction: new . 4-12).. The only difference is that the physical signals are accessed through the clocking block of the virtual modport instead of pins on a module. virtual apb_if.endfunction: new . File: apb/apb_slave. The active clock edge is defined by waiting on the clocking block itself. super.sv" . It is a simple matter of sampling input signals and driving an appropriate response at the right point in time. int unsigned stream_id... The observation and reaction to READ and WRITE transactions is coded exactly as it would be if good old Verilog was used.sigs = sigs.. endclass: apb_slave `endif The transaction descriptors are filled in as much as possible from observations on the physical interface and a response is determined and driven in reply in the main() task (Rule 4-93). forever begin VMM Primer: Writing Command-Layer Slave Transactors 11 .slave sigs... this. stream_id)..)..slave sigs.new("APB Slave".sv" `include "apb_rw.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. name. virtual protected task main(). function new(string name.

sigs.sck.data.paddr.sck. while (this. tr.kind = (this.sigs.sck. tr = new. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). VMM Primer: Writing Command-Layer Slave Transactors 12 . // Wait for a SETUP cycle do @ (this. end else begin @ (this.kind == apb_rw::READ) begin . Otherwise.. .sck.sigs.penable !== 1'b1) begin `vmm_error(this. it will violate the protocol. @ (this.. the vmm_xactor::wait_if_stopped() method is called only before the beginning of transaction..sck). end endtask: main endclass: apb_slave `endif Once a slave transactor recognizes the start of a transaction. end if (this.. tr.sigs.sck.sigs.psel !== 1'b1 || this. tr.addr = this.pwrite) ? apb_rw::WRITE : apb_rw::READ.sigs.data = this. this. this. it must not be stopped until it completes the APB transaction.pwdata.sigs.prdata <= ’z.. if (tr.sck.sigs.. end . Therefore.sigs. .sigs. if the transactor is stopped in the middle of a transaction stream.penable !== 1'b0)..log.apb_rw tr.sck).prdata <= tr.sck).sigs.sck.sck..

. // Wait for a SETUP cycle do @ (this. endfunction: new .sck.sck). .sck.paddr. function new(string name.wait_if_stopped().kind == apb_rw::READ) begin .data.sigs.pwdata..sigs = sigs..File: apb/apb_slave. name.prdata <= ’z. super.sigs.pwrite) ? apb_rw::WRITE : apb_rw::READ. tr.slave sigs. this.).sigs..sigs.sck.psel !== 1'b1 || this. this..penable !== 1'b0). stream_id).sv" `include "apb_rw.. while (this.main().sigs.kind = (this.data = this.sck).sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. tr.sck.new("APB Slave". if (tr. tr.prdata <= tr.sigs..addr = this. tr = new.. class apb_slave extends vmm_xactor.. this. .sigs.sck.sigs.sv" .sck). ... this.sigs.sck. @ (this. forever begin apb_rw tr. end else begin @ (this.slave sigs. virtual protected task main(). virtual apb_if.sigs.sck.. VMM Primer: Writing Command-Layer Slave Transactors 13 .. int unsigned stream_id... super. virtual apb_if. . ..

virtual apb_if. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").). Data returned in READ cycles is taken from the specified address... local bit [31:0] ram[*].sv" .sigs = sigs. end endtask: main endclass: apb_slave `endif But what is the slave to do with the transaction data? The simplest behavior to implement is a RAM response.sv" `include "apb_rw.end if (this.log. endfunction: new VMM Primer: Writing Command-Layer Slave Transactors 14 . Instead.. It is unrealistic to model a 32-bit RAM of 32-bit words as a fixed-sized array: it would take a lot of memory when only a very few locations needed to be used.new("APB Slave". function new(string name..sck. super. Data from WRITE cycles is written at the specified address. name. class apb_slave extends vmm_xactor. If an address is read that has never been written before. File: apb/apb_slave. . an associative array should be used. unknowns (’bx) are returned.sigs. . this.. end . virtual apb_if... stream_id).sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if.slave sigs.penable !== 1'b1) begin `vmm_error(this... .slave sigs. int unsigned stream_id..

.ram[tr.sck). end else begin @ (this. . "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").pwrite) ? apb_rw::WRITE : apb_rw::READ.sigs.addr = this. end . end endtask: main endclass: apb_slave `endif VMM Primer: Writing Command-Layer Slave Transactors 15 . end if (this.data = this. forever begin apb_rw tr.sigs. this.sigs..sck..exists(tr.penable !== 1'b1) begin `vmm_error(this. this. while (this.data.sigs.prdata <= tr.penable !== 1'b0).sck.main().data = ’x. tr. else tr.kind == apb_rw::READ) begin if (!this..sigs.sigs. tr..ram.pwdata.sck.sigs.addr)) tr. virtual protected task main().sigs. this.addr] = tr.sigs..ram[tr.prdata <= ’z.paddr.sck. tr = new.sck. @ (this.log.sck. if (tr.sigs.sck.sigs..wait_if_stopped()....sck.sck).addr]. .sck). this.. // Wait for a SETUP cycle do @ (this.. tr.data.kind = (this.psel !== 1'b1 || this.data = this. super.

sigs = sigs. is attached to a newly defined “RESPONSE” notification and can be recovered by all interested parties waiting on the notification.. Simply displaying the transactions is not very useful as it will require that the results be manually checked every time.configure(RESPONSE).. int unsigned stream_id. File: apb/apb_slave. typedef enum {RESPONSE} notifications_e....new("APB Slave".. function new(string name.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. super.sv" `include "apb_rw.. . VMM Primer: Writing Command-Layer Slave Transactors 16 .notify. .main().. stream_id).).. local bit [31:0] ram[*]. ...slave sigs. name. class apb_slave extends vmm_xactor. Observed transactions can be reported by indicating a notification in the vmm_xactor::notify property. being derived from vmm_data.slave sigs. super. virtual apb_if. The observed transaction.sv" . virtual apb_if.. endfunction: new . this. virtual protected task main().Step 5: Reporting Transactions The slave transactor must have the ability to inform the testbench that a specific transaction has been observed and reacted to and what—if any—data was supplied in answer to the transaction. . this.

tr).sigs.addr = this. tr. .sigs.paddr.wait_if_stopped(). this.prdata <= ’z. end endtask: main endclass: apb_slave `endif VMM Primer: Writing Command-Layer Slave Transactors 17 ..data.exists(tr.sigs.. this.sck.sck.sigs.sck.psel !== 1'b1 || this. end else begin @ (this...sck.. end .notify.sigs.data = this.sck).penable !== 1'b1) begin `vmm_error(this. end if (this.sigs.forever begin apb_rw tr. // Wait for a SETUP cycle do @ (this.pwdata.sck).log. this. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").indicate(RESPONSE.addr)) tr. if (tr.sck. .sigs. @ (this. this.kind = (this.ram.kind == apb_rw::READ) begin if (!this.penable !== 1'b0).pwrite) ? apb_rw::WRITE : apb_rw::READ.. this. else tr.data = ’x.data = this.sigs.prdata <= tr.addr]. tr.sck.sck.sck.addr] = tr.sck).sigs.ram[tr. tr.data.. .sigs. while (this.sigs.ram[tr. tr = new..

this. File: Command_Slave_Xactor/tb_env.. this.sv" class tb_env extends vmm_env.start_xactor().Step 6: The First Test Although not completely VMM-compliant.. It is instantiated in a verification environment class. virtual function void build(). The reference to the interface encapsulating the APB physical signals is made using a hierarchical reference in the extension of the vmm_env::build() method.. . super. The slave transactor is constructed in the extension of the vmm_env::build() method (Rule 4-34. 4-35) and started in the extension of the vmm_env::start() method (Rule 4-41). tb_top. extended from the vmm_env base class.slv = new("0".apb0). endtask: start .start().slv.sv" `include "apb_slave.. 0. the slave transactor can now be used to reply to activity on an APB bus. apb_slave slv. super. . endclass: tb_env `endif VMM Primer: Writing Command-Layer Slave Transactors 18 . endfunction: build virtual task start().build()...sv `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm.

if (this.end_test). tb_top.wait_for(apb_slave::RESPONSE).display("Responded: "). this. $cast(tr.slv.stop_after--. 0. this. tr. super. File: Command_Slave_Xactor/tb_env.build().stop_after <= 0) -> this. this. endfunction: build virtual task start(). fork forever begin apb_rw tr.status(apb_slave::RESPONSE)).start().notify. the slave transactor reports the completed transactions. int stop_after = 10.apb0). VMM Primer: Writing Command-Layer Slave Transactors 19 .slv = new("0". this.start_xactor().end_test.notify. super.sv `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm. @ (this. The responses of the slave transactor can also be used to determine that it is time to end the test when enough APB transactions have been executed. this.wait_for_end().sv" `include "apb_slave.slv. virtual function void build(). super. end join_none endtask: start virtual task wait_for_end().slv.sv" class tb_env extends vmm_env. apb_slave slv.As the simulation progress.

vmm_log log = new("Test". For that method to display the information that is relevant for the APB transaction.sv" program simple_test. it is necessary to overload this method in the transaction descriptor class (Rule 4-76).stop_after = 5. File: Command_Slave_Xactor/test_simple `include "tb_env.endtask: wait_for_end endclass: tb_env `endif A test can control how long the simulation should run by simply setting the value of the tb_env::stop_after property. initial begin env. $finish().run(). The test is written in a program (Rule 4-27) that instantiates the verification environment (Rule 4-28). tb_env env = new. defined in the vmm_data base class does not know about the content of the APB transaction descriptor. env. VMM Primer: Writing Command-Layer Slave Transactors 20 . end endprogram Step 7: Standard Methods The transaction responses reported by the slave transactors are not very useful as they do not show the content of the transactions. That’s because the psdisplay() method. "Simple").

bit [31:0] addr. These common operations include creating transaction descriptors (vmm_data::allocate()). endfunction: psdisplay ..addr. "class"). The vmm_data::psdisplay() method is one of the pre-defined methods in the vmm_data base class that users expect to be provided to simplify and abstract common operations on transaction descriptors. enum {READ. logic [31:0] data. $sformat(psdisplay. comparing transaction descriptors (vmm_data::compare()) and checking that the content of transaction descriptors is valid (vmm_data::is_valid()) (Rule 4-76). this. `endif Re-running the test now yields useful and meaningful transaction response reporting. File: apb/apb_rw.. endfunction: new .data). copying transaction descriptors (vmm_data::copy()). function new(). endclass: apb_rw . static vmm_log log = new("apb_rw".. virtual function string psdisplay(string prefix = "").sv `ifndef APB_RW__SV `define APB_RW__SV VMM Primer: Writing Command-Layer Slave Transactors 21 .new(this. this.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm.log). this.File: apb/apb_rw.kind.. super.sv" class apb_rw extends vmm_data. "%sAPB %s @ 0x%h = 0x%h"... prefix.name(). WRITE} kind.

static vmm_log log = new("apb_rw". enum {READ. tr. logic [31:0] data. tr. endfunction: copy virtual function string psdisplay(string prefix = ""). return null. super.name(). bit [31:0] addr. prefix. apb_rw tr. endfunction: new virtual function vmm_data allocate().sv" class apb_rw extends vmm_data. "%sAPB %s @ 0x%h = 0x%h". $sformat(psdisplay.kind = this.new(this. int kind = -1). apb_rw tr = new. WRITE} kind. "Cannot copy into non-apb_rw \n instance"). endfunction: is_valid VMM Primer: Writing Command-Layer Slave Transactors 22 . return tr. endfunction: allocate virtual function vmm_data copy(vmm_data to = null). end super. if (to == null) tr = new. to)) begin `vmm_fatal(log.addr. "class").data. function new(). this.kind. return tr.data = this. tr.copy_data(tr).`include "vmm. this.addr. return 1.log).kind.data). this.addr = this. else if (!$cast(tr. endfunction: psdisplay virtual function bit is_valid(bit silent = 1.

this. end if (this.. return 0. `endif VMM Primer: Writing Command-Layer Slave Transactors 23 .addr !== tr. end else if (!$cast(tr. apb_rw tr.kind) begin $sformat(diff. if (to == null) begin `vmm_fatal(log. "Addr 0x%h !== 0x%h".addr). input int kind = -1). output string diff. tr. this.data. this. "Data 0x%h !== 0x%h".kind). end if (this. tr.virtual function bit compare(input vmm_data to.addr. "Kind %s !== %s". return 0.data !== tr. tr. "Cannot compare against \n non-apb_rw instance").. return 0. end return 1. to)) begin `vmm_fatal(log. end if (this. endfunction: compare endclass: apb_rw .kind != tr.addr) begin $sformat(diff.data) begin $sformat(diff. return 0. return 0.kind.data). "Cannot compare to NULL reference").

File: apb/apb_slave. int unsigned stream_id.. vmm_data::byte_pack() and vmm_data::byte_unpack() should also be overloaded for packet-oriented transactions.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. `vmm_debug() or `vmm_verbose() macros (Recommendation 451).slave sigs.sv" `include "apb_rw.. VMM Primer: Writing Command-Layer Slave Transactors 24 . class apb_slave extends vmm_xactor. . typedef enum {RESPONSE} notifications_e. vmm_data:byte_size().. it should be possible to understand what the slave transactor does and debug its operation without having to inspect the source code.. These debug messages are inserted using the `vmm_trace(). is doing or has done. local bit [31:0] ram[*]. Debug messages should be added at judicious points to indicate what the slave transactor is about to do. This capability may even be a basic requirement if you plan on shipping encrypted or compiled code. virtual apb_if.Three other standard methods. virtual apb_if.sv" . where the content of the transaction is transmitted over a physical interface (Recommendation 4-77). Step 8: Debug Messages To be truly reusable. ...slave sigs. function new(string name.

\n". name.. // Wait for a SETUP cycle do @ (this. .sigs. super..addr].wait_if_stopped(). tr = new.sck). forever begin apb_rw tr. "Waiting for start of transaction...sigs. else tr..penable !== 1'b1) begin `vmm_error(this.sck.pwdata.. tr. tr.data.addr = this. virtual protected task main()... @ (this. `vmm_trace(log.sck.data = this.notify.."). this.sigs = sigs.data = ’x..new("APB Slave".ram..sck. . end else begin @ (this.prdata <= ’z. this.sigs.sck..psdisplay(" ")}).pwrite) ? apb_rw::WRITE : apb_rw::READ.. end if (this.kind = (this.sigs. tr.sigs. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").data.sigs.sck..addr)) tr.. this. .sigs.. if (tr.kind == apb_rw::READ) begin if (!this.main().sigs. tr. `vmm_trace(log. {"Responding to transaction.configure(RESPONSE).sck.addr] = tr. while (this.paddr.. endfunction: new . stream_id). this.sck.sigs. end VMM Primer: Writing Command-Layer Slave Transactors 25 .sck).exists(tr.ram[tr. .data = this.).ram[tr. this.sigs.psel !== 1'b1 || this.sck.sigs.penable !== 1'b0).sck).prdata <= tr. super.log. this.

sv `ifndef APB_SLAVE__SV VMM Primer: Writing Command-Layer Slave Transactors 26 .notify.\n".. `vmm_trace(log. A constraint block is specified to guarantee that any random configuration is valid (Rule 4-80). The start and end addresses are given default values for the entire address range but can also be randomized to create a random slave configuration. Step 9: Slave Configuration The slave transactor. end endtask: main endclass: apb_slave `endif You can now run the “simple test” with the latest version of the slave transactor and increase the message verbosity to see debug messages displayed as the slave transactors observes and responds to the various transactions. It would not be able to cooperate with other slaves on the same bus mapped to different address ranges. The slave must thus be configurable to limit its response to a specified address range (Rule 4-104). {"Responded to transaction.. File: Makefile % vcs –sverilog –ntb_opts vmm +vmm_log_default=trace . this.. tr). tr.. as currently coded. responds to any and all transactions on the APB bus... File: apb/apb_slave.indicate(RESPONSE. The slave configuration is specified as an optional argument to the constructor (Rule 4-106).. The slave is configured using a configuration descriptor (Rule 4-105).psdisplay(" ")}).

class apb_slave extends vmm_xactor. endclass: apb_slave_cfg .new("APB Slave".sv" class apb_slave_cfg. super. } .. virtual apb_if. virtual apb_if...main()..slave sigs.. this.. this.. rand bit [31:0] start_addr = 32’h0000_0000..notify.. . forever begin apb_rw tr. local bit [31:0] ram[*].slave sigs. endfunction: new .sigs.. . this.sv" `include "apb_rw. .cfg = cfg. rand bit [31:0] end_addr = 32’hFFFF_FFFF. constraint apb_slave_cfg_valid { end_addr >= start_addr.. apb_slave_cfg cfg = null. virtual protected task main(). VMM Primer: Writing Command-Layer Slave Transactors 27 . super. function new(string name.wait_if_stopped().`define APB_SLAVE__SV `include "apb_if.). if (cfg == null) cfg = new."). typedef enum {RESPONSE} notifications_e... `vmm_trace(log..sigs = sigs..prdata <= ’z. name.sck.. local apb_slave_cfg cfg. . stream_id). "Waiting for start of transaction. this. int unsigned stream_id. this.configure(RESPONSE).

`vmm_trace(log.sigs.addr]... end else begin @ (this..exists(tr. this.sck.sck.indicate(RESPONSE..pwrite) ? apb_rw::WRITE : apb_rw::READ. .\n"..\n".sigs. tr.sck. end endtask: main endclass: apb_slave `endif The configuration descriptor is saved in a local class property in the transactor to avoid the configuration from being modified by directly modifying the configuration descriptor. tr.cfg.data = ’x.sck. else tr.paddr.paddr > this.addr = this.penable !== 1'b1) begin `vmm_error(this..sigs. `vmm_trace(log. This occurrence of a direct VMM Primer: Writing Command-Layer Slave Transactors 28 . tr)... if (tr.sck. while (this. @ (this.sigs.sck).psel !== 1'b1 || this. tr = new.penable !== 1'b0 || this. end if (this.sigs.sck. {"Responding to transaction.addr)) tr.cfg.sigs. end ..prdata <= tr. tr. ..data = this.sigs. tr.pwdata.data.addr] = tr.end_addr).start_addr || this.data = this. this.sck.psdisplay(" ")}).sck).ram..kind == apb_rw::READ) begin if (!this.sck.data. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").log.notify.ram[tr.sck).sigs..sigs. this.sck.sigs. {"Responded to transaction.sigs. // Wait for a SETUP cycle do @ (this.kind = (this.ram[tr.paddr < this.psdisplay(" ")}). tr..sigs.

but transactors implementing more complex protocols may very well need to be told that they are being reconfigured.. .modification would not be visible to the slave transactor. this. int unsigned stream_id.). if (cfg == null) cfg = new. typedef enum {RESPONSE} notifications_e. . function new(string name. File: apb/apb_slave.slave sigs.. } . super.. rand bit [31:0] start_addr = 32’h0000_0000. class apb_slave extends vmm_xactor. local apb_slave_cfg cfg.. this.. constraint apb_slave_cfg_valid { end_addr >= start_addr. VMM Primer: Writing Command-Layer Slave Transactors 29 . endclass: apb_slave_cfg .sv" `include "apb_rw. it would not have any ill effects.sv" class apb_slave_cfg. . apb_slave_cfg cfg = null.. virtual apb_if.. virtual apb_if. Because of the simplicity of the slave functionality and its configuration.sigs = sigs. To that effect. name.cfg = cfg.. local bit [31:0] ram[*]. . stream_id). a reconfigure() method is provided (Rule 4-107).....new("APB Slave". rand bit [31:0] end_addr = 32’hFFFF_FFFF.slave sigs.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if.

sck.. this. `vmm_trace(log.pwdata. . tr. "Waiting for start of transaction.sck..sck.data = ’x..sigs.sck. this.paddr > this..cfg. end if (this..log.configure(RESPONSE). end else begin @ (this.sck.sigs.sck). virtual protected task main(). this.sigs.sck.sigs.cfg = cfg.sigs. super. // Wait for a SETUP cycle do @ (this.sigs.notify.ram.main().data = this.exists(tr.start_addr || this. endfunction: reconfigure . {"Responding to transaction. forever begin apb_rw tr.prdata <= tr.psel !== 1'b1 || this.data.paddr < this. "APB protocol violation:\n VMM Primer: Writing Command-Layer Slave Transactors 30 . else tr. .sigs.sck)..this.. this.kind = (this. while (this.sigs. .end_addr). endfunction: new virtual function void reconfigure(apb_slave_cfg cfg).sigs.penable !== 1'b0 || this. if (tr.pwrite) ? apb_rw::WRITE : apb_rw::READ.sigs.ram[tr.sck.. tr..sck.sck.kind == apb_rw::READ) begin if (!this.sck.addr)) tr.paddr. @ (this. tr.wait_if_stopped().sck).sigs. `vmm_trace(log. tr = new.addr].data.prdata <= ’z.cfg.penable !== 1'b1) begin `vmm_error(this.ram[tr.psdisplay(" ")}).sigs.addr] = tr. this.")..\n".data = this..sigs..addr = this. tr.

SETUP cycle not followed by ENABLE cycle").indicate(RESPONSE. end endtask: main endclass: apb_slave `endif It is not necessary to derive the configuration descriptor from vmm_data as it is not a transaction descriptor and is not called upon to flow through vmm_channels or be attached to vmm_notify indications. with a signature identical to vmm::psdisplay() to make it easier to report the current transactor configuration during simulation. "%sAPB Slave Config: [‘h%h-‘h%h]". tr). {"Responded to transaction. class apb_slave extends vmm_xactor. endfunction: psdisplay endclass: apb_slave_cfg . $sformat(psdisplay. } virtual function string psdisplay(string prefix = "")..sv" class apb_slave_cfg.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. rand bit [31:0] start_addr = 32’h0000_0000. end .. this.sv" `include "apb_rw.. constraint apb_slave_cfg_valid { end_addr >= start_addr. `vmm_trace(log..end_addr). this. the psdisplay() method should be provided.notify. Nevertheless. File: apb/apb_slave.start_addr. prefix.\n"..psdisplay(" ")}). rand bit [31:0] end_addr = 32’hFFFF_FFFF. this. tr.. VMM Primer: Writing Command-Layer Slave Transactors 31 .

typedef enum {RESPONSE} notifications_e.prdata <= ’z. `vmm_trace(log. forever begin apb_rw tr. tr = new.cfg = cfg.sigs..new("APB Slave".sck. super.. virtual protected task main(). this. this.sck..sck. if (cfg == null) cfg = new.main().start_addr || this.sck.. local apb_slave_cfg cfg.penable !== 1'b0 || this.slave sigs.).paddr > this.cfg. this.sck.kind = (this...paddr. super.notify. "Waiting for start of transaction. function new(string name.addr = this. .cfg.cfg = cfg. virtual apb_if.end_addr).sigs..sigs.").virtual apb_if.sigs.sck).sigs..sigs. apb_slave_cfg cfg = null. name.. // Wait for a SETUP cycle do @ (this..sigs. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg).configure(RESPONSE). while (this. tr. . this. endfunction: reconfigure . this.psel !== 1'b1 || this.sigs. tr..sck.. int unsigned stream_id..sck. VMM Primer: Writing Command-Layer Slave Transactors 32 .wait_if_stopped(). . .pwrite) ? apb_rw::WRITE : apb_rw::READ. .slave sigs. this.sigs = sigs. local bit [31:0] ram[*]. stream_id).paddr < this..

`vmm_trace(log. tr). this.sigs.sigs.prdata <= tr. if (tr.ram[tr.psdisplay(" ")}).data. this.. VMM Primer: Writing Command-Layer Slave Transactors 33 .kind == apb_rw::READ) begin if (!this. end .addr] = tr. tr..data = this.sck)..psdisplay(" ")}). it is useful to have a procedural interface to the content of the RAM.penable !== 1'b1) begin `vmm_error(this.sigs.pwdata..\n".addr]..sigs. {"Responded to transaction.notify. a procedure could be provided to delete the data value at a specified address to reclaim its memory from the associated array.data. end else begin @ (this.data = ’x.sigs.sck.sck).log. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). {"Responding to transaction. this.. tr... end if (this. else tr. `vmm_trace(log. end endtask: main endclass: apb_slave `endif Step 10: Backdoor Interface To help predict or control the response of the slave.indicate(RESPONSE...sck. @ (this. . Optionally.\n".data = this.ram. tr.ram[tr. . This interface allows querying the RAM at a specific address as well as setting values at specific addresses.sck.exists(tr.addr)) tr.

. endfunction: reconfigure VMM Primer: Writing Command-Layer Slave Transactors 34 . this..end_addr)... . this. prefix. this. stream_id). super. typedef enum {RESPONSE} notifications_e. this.sv" class apb_slave_cfg.notify. this. $sformat(psdisplay. local bit [31:0] ram[*]..slave sigs.).cfg = cfg. if (cfg == null) cfg = new.configure(RESPONSE). rand bit [31:0] end_addr = 32’hFFFF_FFFF. virtual apb_if. ... endfunction: new virtual function void reconfigure(apb_slave_cfg cfg). } virtual function string psdisplay(string prefix = ""). local apb_slave_cfg cfg.cfg = cfg. function new(string name. this.sigs = sigs.. apb_slave_cfg cfg = null. "%sAPB Slave Config: [‘h%h-‘h%h]". int unsigned stream_id.slave sigs. virtual apb_if. . rand bit [31:0] start_addr = 32’h0000_0000.start_addr..new("APB Slave".sv" `include "apb_rw. class apb_slave extends vmm_xactor. name..File: apb/apb_slave. . constraint apb_slave_cfg_valid { end_addr >= start_addr.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. endfunction: psdisplay endclass: apb_slave_cfg .

return.sigs. super.sigs. endfunction: peek ..").start_addr || addr > this.paddr < this..paddr > this.exists(tr..sigs.log.start_addr || addr > this..sigs. "Out-of-range poke").data = ’x. this.sigs. .sck.sck.addr)) tr.sck). end return (this. if (tr.wait_if_stopped().main(). tr.cfg.paddr..sigs. VMM Primer: Writing Command-Layer Slave Transactors 35 .psel !== 1'b1 || this.sck. tr.psdisplay(" ")}). return ’x. "Waiting for start of transaction.cfg. `vmm_trace(log.cfg.start_addr || this.penable !== 1'b0 || this. end this. {"Responding to transaction.end_addr) begin `vmm_error(this. if (addr < this.kind = (this.ram.kind == apb_rw::READ) begin if (!this.exists(addr)) ? this...ram.cfg.cfg. tr.sigs. while (this.sck.log.sck.sck.ram[addr] : ’x. if (addr < this.. virtual protected task main(). bit [31:0] data). `vmm_trace(log.end_addr).virtual function void poke(bit [31:0] addr.\n".addr = this.sigs.cfg. forever begin apb_rw tr. // Wait for a SETUP cycle do @ (this. "Out-of-range peek").end_addr) begin `vmm_error(this.pwrite) ? apb_rw::WRITE : apb_rw::READ. tr = new.prdata <= ’z.sck.ram[addr] = data. endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr). this.

addr].. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). `vmm_trace(log.sck. this.sigs. Although the correct behavior is desired most of the time...addr] = tr.data.notify.data.sigs. .. the only errors that could be injected are wrong addresses and wrong data.indicate(RESPONSE. end endtask: main endclass: apb_slave `endif Step 11: Extension Points This slave transactor should behave correctly by default.sigs.ram[tr.sck. Callback methods should be provided before the response to a transaction is sent back (Recommendation 4-156. tr..sigs.else tr.. end .data = this. {"Responded to transaction. @ (this. 4-158) and after the transaction VMM Primer: Writing Command-Layer Slave Transactors 36 .sck).\n".pwdata.penable !== 1'b1) begin `vmm_error(this..psdisplay(" ")}). A more complex protocol would probably have parity bits that could be corrupted or responses that could be delayed. functional verification also entails the injection of errors to verify that the design reacts properly to those errors.prdata <= tr.sck. In this simple protocol..ram[tr. .data = this. end else begin @ (this. tr. end if (this. tr). this.log. A callback method allows a user to extend the behavior of a slave transactor without having to modify the transactor itself. this.sck).sigs.

apb_rw cycle). The callback methods are first defined as virtual void functions (Rule 4-160) in a callback façade class extended from the vmm_xactor_callbacks base class (Rule 4-159). The “post-response” callback method allows delays to be inserted and the result of the transaction to be recorded in a functional coverage model or checked against an expected response.end_addr).response has completed (Recommendation 4-155). "%sAPB Slave Config: [‘h%h-‘h%h]". the appropriate callback method needs to be invoked at the appropriate point in the execution of the monitor. using the `vmm_callback() macro (Rule 4-163). The “preresponse” callback method allows errors to be injected and delays to be inserted. virtual function void pre_response(apb_slave xact. rand bit [31:0] start_addr = 32’h0000_0000. endfunction: psdisplay endclass: apb_slave_cfg typedef class apb_slave.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. rand bit [31:0] end_addr = 32’hFFFF_FFFF. this. class apb_slave_cbs extends vmm_xactor_callbacks. $sformat(psdisplay.start_addr. prefix. VMM Primer: Writing Command-Layer Slave Transactors 37 .sv" class apb_slave_cfg.sv" `include "apb_rw. this. File: apb/apb_slave. } virtual function string psdisplay(string prefix = ""). Next. constraint apb_slave_cfg_valid { end_addr >= start_addr.

endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr)..slave sigs. stream_id).cfg. . int unsigned stream_id. name.endfunction: pre_response virtual function void post_response(apb_slave xactor.start_addr || addr > this... apb_slave_cfg cfg = null.cfg = cfg.new("APB Slave".slave sigs. return. if (addr < this. this. apb_rw cycle).. virtual apb_if. function new(string name.notify. bit [31:0] data).cfg.end_addr) begin VMM Primer: Writing Command-Layer Slave Transactors 38 . this. virtual apb_if. local apb_slave_cfg cfg. "Out-of-range poke"). if (addr < this. .end_addr) begin `vmm_error(this.cfg.log.). end this.cfg = cfg. this.configure(RESPONSE).sigs = sigs.. this.. super.start_addr || addr > this.. local bit [31:0] ram[*]. endfunction: reconfigure virtual function void poke(bit [31:0] addr. . endfunction: new virtual function void reconfigure(apb_slave_cfg cfg). typedef enum {RESPONSE} notifications_e.cfg.ram[addr] = data. endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor. if (cfg == null) cfg = new. ..

log.sigs. while (this.data = this.kind == apb_rw::READ) begin if (!this. tr.ram.paddr > this. tr)). VMM Primer: Writing Command-Layer Slave Transactors 39 .sigs. {"Responding to transaction.. endfunction: peek .paddr.exists(tr.addr = this. tr.`vmm_error(this.addr]. tr.sigs.kind = (this. else tr.sck.sck.sck. super.sck. `vmm_trace(log. this.start_addr || this. end return (this...ram[tr.exists(addr)) ? this.sigs. if (tr.end_addr).sck.sigs. tr)).sigs.cfg.ram[addr] : ’x. this.paddr < this.wait_if_stopped().prdata <= ’z. return ’x.sck.sigs.sck.main(). @ (this.data = ’x. `vmm_callback(apb_slave_cbs.sck).\n".sigs..ram.sck).data. // Wait for a SETUP cycle do @ (this. `vmm_trace(log.pwrite) ? apb_rw::WRITE : apb_rw::READ..psdisplay(" ")}).. tr = new.sigs.sck. "Out-of-range peek").pwdata.prdata <= tr.sigs."). end else begin @ (this. tr. `vmm_callback(apb_slave_cbs. pre_response(this.sigs. forever begin apb_rw tr. "Waiting for start of transaction.data = this.. . this.sck.psel !== 1'b1 || this..cfg.addr)) tr. pre_response(this. virtual protected task main().sck).sigs.penable !== 1'b0 || this.

ram[tr.\n".penable !== 1'b1) begin `vmm_error(this. tr. VMM Primer: Writing Command-Layer Slave Transactors 40 . tr). A transaction-level interface can be used to request a response to an APB transaction from a higher-level transactor or model (Rule 4-111).. end endtask: main endclass: apb_slave `endif Step 12: Transaction Response The slave transactor is currently hard-coded to respond like a RAM. tr)). File: apb/apb_rw. end `vmm_callback(apb_slave_cbs.this.data.indicate(RESPONSE.notify.addr] = tr.log.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm.psdisplay(" ")}). but it makes the transactor unsuitable for providing a different response or to use it to write transaction-level models of slave devices. That is fine in most cases. `vmm_trace(log. end if (this. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). post_response(this.sv" class apb_rw extends vmm_data.. {"Responded to transaction.sck. A transaction-level interface carrying APB transaction descriptor is defined by using the `vmm_channel macro. this.sigs.

if (to == null) tr = new. this. rand enum {READ. function new(). output string diff. input int kind = -1). apb_rw tr. prefix. WRITE} kind. "%sAPB %s @ 0x%h = 0x%h".data.data). super. "Cannot copy into non-apb_rw \n instance").copy_data(tr). VMM Primer: Writing Command-Layer Slave Transactors 41 . tr. endfunction: allocate virtual function vmm_data copy(vmm_data to = null). tr. tr. endfunction: psdisplay virtual function bit is_valid(bit silent = 1. return null.addr. apb_rw tr = new. endfunction: is_valid virtual function bit compare(input vmm_data to. int kind = -1).data = this. return tr.kind. this. return tr.name().new(this. $sformat(psdisplay.log). endfunction: copy virtual function string psdisplay(string prefix = ""). end super.kind = this. rand logic [31:0] data. else if (!$cast(tr.kind. "class"). return 1. rand bit [31:0] addr. this.static vmm_log log = new("apb_rw". endfunction: new virtual function vmm_data allocate(). to)) begin `vmm_fatal(log.addr.addr = this.

tr.addr).addr) begin $sformat(diff. endfunction: compare endclass: apb_rw `vmm_channel(apb_rw) `endif The response request channel is specified as an optional argument to the constructor (Rule 4-113) and stored in a local property (Rule 4-112).data) begin $sformat(diff.addr !== tr. this. "Cannot compare to NULL reference").data.apb_rw tr. tr. tr. "Data 0x%h !== 0x%h". end if (this. return 0.kind). to)) begin `vmm_fatal(log. return 0. VMM Primer: Writing Command-Layer Slave Transactors 42 .data !== tr. this. end if (this. "Addr 0x%h !== 0x%h". return 0. return 0.addr.kind != tr. If no channel is specified.kind) begin $sformat(diff. the response defaults to the RAM response. "Kind %s !== %s".data). if (to == null) begin `vmm_fatal(log. end return 1. "Cannot compare against \n non-apb_rw instance"). end else if (!$cast(tr. this.kind. end if (this. return 0.

prefix. local bit [31:0] ram[*]. "%sAPB Slave Config: [‘h%h-‘h%h]". name. rand bit [31:0] end_addr = 32’hFFFF_FFFF.. resp_chan = null. constraint apb_slave_cfg_valid { end_addr >= start_addr.slave apb_slave_cfg apb_rw_channel . endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor. local apb_slave_cfg cfg. endfunction: psdisplay endclass: apb_slave_cfg typedef class apb_slave.sv" class apb_slave_cfg. rand bit [31:0] start_addr = 32’h0000_0000.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. this. $sformat(psdisplay. . virtual apb_if.).. } virtual function string psdisplay(string prefix = ""). apb_rw_channel resp_chan. virtual function void pre_response(apb_slave xact. sigs. class apb_slave_cbs extends vmm_xactor_callbacks. cfg = null. function new(string int unsigned virtual apb_if. endfunction: pre_response virtual function void post_response(apb_slave xactor. apb_rw cycle).end_addr).slave sigs. this.sv" `include "apb_rw.start_addr. stream_id. VMM Primer: Writing Command-Layer Slave Transactors 43 ..File: apb/apb_slave. typedef enum {RESPONSE} notifications_e. apb_rw cycle)..

while (this. .ram[addr] : ’x. `vmm_trace(log."). if (cfg == null) cfg = new.cfg. "Out-of-range peek").log.wait_if_stopped(). this.. endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr). "Out-of-range poke"). name.psel !== 1'b1 || VMM Primer: Writing Command-Layer Slave Transactors 44 .sigs = sigs. end return (this..cfg = cfg.ram.start_addr || addr > this.notify. bit [31:0] data)..ram[addr] = data.start_addr || addr > this. // Wait for a SETUP cycle do @ (this.. this. forever begin apb_rw tr. stream_id). if (addr < this.super. end this.main().sck..log.sigs. if (addr < this.resp_chan = resp_chan.end_addr) begin `vmm_error(this.sigs.configure(RESPONSE).sck. "Waiting for start of transaction.cfg... this. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg). .cfg. endfunction: peek . return ’x. return.sigs.prdata <= ’z.exists(addr)) ? this. this. this. endfunction: reconfigure virtual function void poke(bit [31:0] addr. this. this.end_addr) begin `vmm_error(this.sck)..cfg = cfg.cfg. virtual protected task main(). super.new("APB Slave".

sigs.sck).sck).kind = (this. pre_response(this.penable !== 1'b0 || this.data. `vmm_callback(apb_slave_cbs. end else begin @ (this. if (tr.log.psdisplay(" ")}). "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").prdata <= tr. end `vmm_callback(apb_slave_cbs.end_addr). end `vmm_callback(apb_slave_cbs.resp_chan. tr)).put(tr).pwrite) ? apb_rw::WRITE : apb_rw::READ.ram[tr.paddr. tr = new.ram.resp_chan == null) this.pwdata.sigs. this. this.addr] = tr.exists(tr.kind == apb_rw::READ) begin if (this..cfg.data. end if (this.paddr < this. tr.paddr > this.sigs.data = ’x. post_response(this...sigs.sneak(tr). `vmm_trace(log. tr)).ram[tr. else this.penable !== 1'b1) begin `vmm_error(this.resp_chan == null) begin if (!this. end else begin .sigs. else tr. tr. .sck.. @ (this.sck.sck. tr.. VMM Primer: Writing Command-Layer Slave Transactors 45 .data = this. if (this.cfg.sigs. tr.sck.sck.sigs.sigs.sck.\n".addr)) tr. tr)).addr]..sck.sck.resp_chan.addr = this.data = this.start_addr || this.sigs. pre_response(this.sigs.this. {"Responding to transaction.

the slave transactor simply notifies the response transactor of the WRITE transaction and does not need to wait for a response.indicate(RESPONSE. the transaction descriptor contains the data to return to the DUT. File: apb/apb_slave. When requesting the response to a READ transaction. the slave transactor uses the vmm_channel::sneak() method to put the transaction descriptor on the response request channel. this.`vmm_trace(log. It is assumed that consumer at the other end of resp_chan provides the read data and ensures that put() method blocks till the read data is provided. It is a good idea to ensure that the response is provided in a timely fashion by the response transactor by forking a timer thread. When the vmm_channel::put() method returns. a blocking response model is expected from the response transactor. constraint apb_slave_cfg_valid { end_addr >= start_addr. Because there is no feedback on WRITE cycles in the APB protocol. VMM Primer: Writing Command-Layer Slave Transactors 46 . tr.sv" `include "apb_rw. rand bit [31:0] start_addr = 32’h0000_0000.\n".. tr).sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if.notify..psdisplay(" ")}). end endtask: main endclass: apb_slave `endif When requesting the response to a WRITE transaction.sv" class apb_slave_cfg. {"Responded to transaction. rand bit [31:0] end_addr = 32’hFFFF_FFFF.

. stream_id). . $sformat(psdisplay. . typedef enum {RESPONSE} notifications_e. apb_rw_channel resp_chan = null.} virtual function string psdisplay(string prefix = "").slave sigs. super.. class apb_slave_cbs extends vmm_xactor_callbacks..sigs = sigs. endfunction: reconfigure VMM Primer: Writing Command-Layer Slave Transactors 47 .end_addr).).cfg = cfg. virtual apb_if.cfg = cfg. this. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg). endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor. . local bit [31:0] ram[*].configure(RESPONSE). name. endfunction: pre_response virtual function void post_response(apb_slave xactor. virtual apb_if. virtual function void pre_response(apb_slave xact. if (cfg == null) cfg = new.. apb_rw cycle). "%sAPB Slave Config: [‘h%h-‘h%h]". this. prefix. this. this. apb_slave_cfg cfg = null. apb_rw_channel resp_chan.start_addr.notify.new("APB Slave". function new(string name. this. this. apb_rw cycle). endfunction: psdisplay endclass: apb_slave_cfg typedef class apb_slave.. this. int unsigned stream_id.resp_chan = resp_chan.slave sigs.. local apb_slave_cfg cfg.

forever begin apb_rw tr.kind = (this.cfg.resp_chan == null) begin VMM Primer: Writing Command-Layer Slave Transactors 48 .cfg.log.sck. // Wait for a SETUP cycle do @ (this.").cfg. . "Waiting for start of transaction.sck).sigs.sigs.. while (this.prdata <= ’z..log.paddr. virtual protected task main(). if (addr < this. "Out-of-range peek").ram[addr] : ’x. this.sigs. bit [31:0] data)..cfg.. return ’x.psdisplay(" ")}).paddr > this.sck.\n".addr = this. tr. `vmm_trace(log. tr = new.start_addr || addr > this.exists(addr)) ? this.sck.ram[addr] = data. end return (this.sck. end this..end_addr) begin `vmm_error(this.start_addr || this.start_addr || addr > this.end_addr) begin `vmm_error(this.kind == apb_rw::READ) begin if (this.penable !== 1'b0 || this. if (tr. if (addr < this.main().sck.sigs.sigs. endfunction: peek . tr. this. endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr). "Out-of-range poke").. return.sck. tr.pwrite) ? apb_rw::WRITE : apb_rw::READ.cfg.sck.virtual function void poke(bit [31:0] addr. super. {"Responding to transaction.sigs.paddr < this.wait_if_stopped().sigs...ram.cfg.sigs.psel !== 1'b1 || this.end_addr). `vmm_trace(log.

abort = 1.exists(tr.sigs.sigs. end `vmm_callback(apb_slave_cbs.sck. end if (this.sigs.data.sck.sigs.sigs.ram[tr. end else begin bit abort = 0.log.penable !== 1'b1) begin `vmm_error(this. else tr.sigs. pre_response(this. end this. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). if (this. end join if (abort) continue.data = this. VMM Primer: Writing Command-Layer Slave Transactors 49 .put(tr).sck).sck. tr.resp_chan.resp_chan == null) this. end `vmm_callback(apb_slave_cbs.log. `vmm_error(this. join_any disable fork.prdata <= tr. end else begin @ (this.ram[tr.if (!this. else this. @ (this.pwdata.addr].data = this.sck).sneak(tr).data.ram. fork begin fork begin @ (this. `vmm_callback(apb_slave_cbs.sck). post_response(this.data = ’x. tr)). "No response in time").addr)) tr. pre_response(this. this.addr] = tr. tr)). tr)).resp_chan.

`vmm_trace(log. tr.. endfunction: psdisplay endclass: apb_slave_cfg typedef class apb_slave. VMM Primer: Writing Command-Layer Slave Transactors 50 . apb_rw cycle). this. rand bit [31:0] end_addr = 32’hFFFF_FFFF. endfunction: pre_response virtual function void post_response(apb_slave xactor.sv" `include "apb_rw.\n". constraint apb_slave_cfg_valid { end_addr >= start_addr.start_addr. {"Responded to transaction. tr). File: apb/apb_slave. end endtask: main endclass: apb_slave `endif When the transactor is reset. apb_rw cycle). class apb_slave_cbs extends vmm_xactor_callbacks. virtual function void pre_response(apb_slave xact. } virtual function string psdisplay(string prefix = ""). rand bit [31:0] start_addr = 32’h0000_0000..sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. the response channel must be flushed and the output signals must be driven to their idle state. prefix.sv" class apb_slave_cfg. this. "%sAPB Slave Config: [‘h%h-‘h%h]". $sformat(psdisplay.indicate(RESPONSE. this.end_addr).notify. This behavior is accomplished in the extension of the vmm_xactor::reset_xactor() method (Table A-8).psdisplay(" ")}).

cfg. .notify. this.ram[addr] = data. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg).log.. name. if (addr < this. apb_slave_cfg cfg = null.log.cfg. if (cfg == null) cfg = new. bit [31:0] data). apb_rw_channel resp_chan = null. this. local apb_slave_cfg cfg.start_addr || addr > this.endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor. if (addr < this. endfunction: reconfigure virtual function void poke(bit [31:0] addr. function new(string name. super. "Out-of-range peek"). local bit [31:0] ram[*]... virtual apb_if.). int unsigned stream_id.end_addr) begin `vmm_error(this... . this. endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr).slave sigs. VMM Primer: Writing Command-Layer Slave Transactors 51 . typedef enum {RESPONSE} notifications_e. stream_id).cfg.cfg = cfg. this.. apb_rw_channel resp_chan.start_addr || addr > this. . end this.cfg.resp_chan = resp_chan.slave sigs.configure(RESPONSE).new("APB Slave". return ’x. return.sigs = sigs. this.end_addr) begin `vmm_error(this. "Out-of-range poke").cfg = cfg. virtual apb_if.

`vmm_trace(log..exists(tr.end_addr).sigs. tr = new.kind = (this.addr = this.prdata <= ’z.ram[addr] : ’x.sck.psel !== 1'b1 || this.psdisplay(" ")}). if (tr.pwrite) ? apb_rw::WRITE : apb_rw::READ.start_addr || this. end else begin bit abort = 0.ram. endfunction: peek virtual function void reset_xactor(reset_e rst_typ = SOFT_RST). tr.resp_chan. fork begin fork begin VMM Primer: Writing Command-Layer Slave Transactors 52 . this. "Waiting for start of transaction.resp_chan == null) begin if (!this.wait_if_stopped().sigs. forever begin apb_rw tr.reset_xactor(rst_typ).cfg..sigs.sigs. tr.sck. // Wait for a SETUP cycle do @ (this..sigs. super.sck.. while (this.sck.kind == apb_rw::READ) begin if (this.\n".sck).data = this.prdata <= ’z. {"Responding to transaction.").sigs.flush().ram. else tr.paddr.ram[tr.exists(addr)) ? this.sck.main(). super. this.data = ’x.sigs.cfg.addr].sck. tr.sck.sigs.addr)) tr.end return (this.paddr < this.sigs.paddr > this. endfunction: reset_xactor virtual protected task main().sck. this. this.penable !== 1'b0 || this. `vmm_trace(log.

sigs.data.sigs.resp_chan.sck).sigs. if (this. pre_response(this tr)).prdata <= tr. `vmm_callback(apb_slave_cbs.sck).log.sck). end `vmm_callback(apb_slave_cbs. tr)).sigs.pwdata. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").resp_chan. `vmm_trace(log. post_response(this. this.@ (this. tr)). end this. end if (this. tr. pre_response(this. abort = 1.data = this. "No response in time").resp_chan == null) this. tr.sneak(tr).penable !== 1'b1) begin `vmm_error(this. end join if (abort) continue.put(tr).ram[tr.. join_any disable fork.sck. tr). end endtask: main endclass: apb_slave `endif VMM Primer: Writing Command-Layer Slave Transactors 53 .\n".sck.sigs. `vmm_error(this.addr] = tr.sck.psdisplay(" ")}).sigs..notify. this. end else begin @ (this. @ (this. end `vmm_callback(apb_slave_cbs. else this.log. {"Responded to transaction.indicate(RESPONSE.data.

rand bit [31:0] end_addr = 32’hFFFF_FFFF. "%sAPB Slave Config: [‘h%h-‘h%h]". apb_rw cycle).sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. this.end_addr).sv" `include "apb_rw. File: apb/apb_slave. virtual apb_if. virtual function void pre_response(apb_slave xact. These transaction endpoints are recorded in the transaction descriptor itself by the slave transactor indicating the vmm_data::STARTED and vmm_data::ENDED notifications (Rule 4-142). apb_rw cycle). VMM Primer: Writing Command-Layer Slave Transactors 54 . class apb_slave_cbs extends vmm_xactor_callbacks. constraint apb_slave_cfg_valid { end_addr >= start_addr.It may be useful for the higher-level functions using the transactions reported by the slave transactor to know when the transaction was started and when it ended. this. prefix. $sformat(psdisplay. rand bit [31:0] start_addr = 32’h0000_0000.start_addr. endfunction: psdisplay endclass: apb_slave_cfg typedef class apb_slave.sv" class apb_slave_cfg. apb_rw_channel resp_chan. } virtual function string psdisplay(string prefix = ""). local apb_slave_cfg cfg. endfunction: pre_response virtual function void post_response(apb_slave xactor. endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor.slave sigs.

. . . endfunction: reconfigure virtual function void poke(bit [31:0] addr. function new(string name.exists(addr)) ? this. this.end_addr) begin `vmm_error(this.notify. name. if (cfg == null) cfg = new. if (addr < this.. endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr). "Out-of-range poke")..ram.cfg = cfg. apb_slave_cfg cfg = null. stream_id).cfg = cfg. return.).log. this.resp_chan = resp_chan..cfg. this.sigs = sigs.cfg.new("APB Slave".start_addr || addr > this. super. local bit [31:0] ram[*]. apb_rw_channel resp_chan = null. "Out-of-range peek"). endfunction: peek virtual function void reset_xactor(reset_e rst_typ = SOFT_RST). endfunction: new virtual function void reconfigure(apb_slave_cfg cfg).log.configure(RESPONSE). return ’x.reset_xactor(rst_typ).ram[addr] = data.. if (addr < this.ram[addr] : ’x. this. virtual apb_if.start_addr || addr > this. VMM Primer: Writing Command-Layer Slave Transactors 55 . end return (this.cfg.cfg.end_addr) begin `vmm_error(this. bit [31:0] data). end this.. . super. int unsigned stream_id. this.typedef enum {RESPONSE} notifications_e.slave sigs.

while (this.wait_if_stopped().sigs.cfg. super.sck).log.main().ram[tr. `vmm_trace(log.data = this.sck.sck.\n".this. // Wait for a SETUP cycle do @ (this.start_addr || this.prdata <= ’z. else tr.addr = this. {"Responding to transaction.sck.resp_chan == null) begin if (!this.sigs..sigs. tr.resp_chan.addr]. `vmm_error(this.sck. abort = 1.kind == apb_rw::READ) begin if (this.addr)) tr.resp_chan.sigs..notify.sck. join_any VMM Primer: Writing Command-Layer Slave Transactors 56 .kind = (this.end_addr).flush().prdata <= ’z. endfunction: reset_xactor virtual protected task main()..sigs.put(tr).paddr > this. tr.penable !== 1'b0 || this.sck. tr.sck).indicate(vmm_data::STARTED). tr = new.paddr.pwrite) ? apb_rw::WRITE : apb_rw::READ.sck. this.exists(tr.ram. fork begin fork begin @ (this.sigs.sigs.. forever begin apb_rw tr.sigs. `vmm_trace(log.psdisplay(" ")}).paddr < this.").sigs. "Waiting for start of transaction.data = ’x. tr. end this. end else begin bit abort = 0.psel !== 1'b1 || this.sck. if (tr. this.cfg. "No response in time").sigs. this.

pre_response(this. tr. tr)). end else begin @ (this.addr] = tr.. this.sck. `vmm_trace(log. else this. end join if (abort) continue.sneak(tr).notify.\n". "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). {"Responded to transaction.notify. end tr. end if (this.sck).indicate(vmm_data::ENDED). end `vmm_callback(apb_slave_cbs.data.ram[tr.resp_chan. pre_response(this.disable fork. tr).. `vmm_callback(apb_slave_cbs. tr)).data = this. if (this.sigs.sck.psdisplay(" ")}).indicate(RESPONSE.sck).penable !== 1'b1) begin `vmm_error(this. this.sigs.data.resp_chan == null) this.prdata <= tr.pwdata.sigs. end endtask: main endclass: apb_slave `endif VMM Primer: Writing Command-Layer Slave Transactors 57 .log. tr)). `vmm_callback(apb_slave_cbs.sigs.sigs. post_response(this.sck. @ (this. tr.

log).new(this. if (to == null) tr = new. File: apb/apb_rw. 4-62).Step 13: Random Responses To promote the use of random stimulus and make the same transaction descriptor usable in transaction generators. VMM Primer: Writing Command-Layer Slave Transactors 58 . rand bit [31:0] addr. 4-60. WRITE} kind.sv" class apb_rw extends vmm_data. apb_rw tr = new. super. endfunction: new virtual function vmm_data allocate(). "Cannot copy into non-apb_rw \n instance"). all public properties in a transaction descriptor should be declared as rand (Rules 4-59. to)) begin `vmm_fatal(log. return tr. It is a simple matter of using the `vmm_atomic_gen() and `vmm_scenario_gen() macros (Recommendation 5-23. endfunction: allocate virtual function vmm_data copy(vmm_data to = null). else if (!$cast(tr. rand logic [31:0] data. It is also a good idea to pre-define random transaction generators whenever transaction descriptors are defined. "class"). 5-24) in the transaction descriptor file. apb_rw tr. function new().sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. rand enum {READ. static vmm_log log = new("apb_rw".

"Kind %s !== %s". return 1. this.kind != tr.addr = this. this.kind).kind = this. this.data). tr. VMM Primer: Writing Command-Layer Slave Transactors 59 . return 0. "Cannot compare against non-apb_rw \n instance"). $sformat(psdisplay.copy_data(tr). end if (this. "Cannot compare to NULL reference").data.data = this. this. endfunction: is_valid virtual function bit compare(input vmm_data to.kind.kind.kind) begin $sformat(diff. endfunction: psdisplay virtual function bit is_valid(bit silent = 1.addr). to)) begin `vmm_fatal(log. "%sAPB %s @ 0x%h = 0x%h". prefix. output string diff. return 0.addr. endfunction: copy virtual function string psdisplay(string prefix = ""). tr.return null.kind. tr. return tr.addr.addr !== tr. return 0. tr. end else if (!$cast(tr. this.addr) begin $sformat(diff. return 0. int kind = -1). end super. "Addr 0x%h !== 0x%h". apb_rw tr. input int kind = -1). end if (this. tr.name(). if (to == null) begin `vmm_fatal(log.addr.

endfunction: build VMM Primer: Writing Command-Layer Slave Transactors 60 . super.end if (this. "0").sv" class tb_env extends vmm_env. 0. this. virtual function void build(). apb_slave slv. end return 1. null. tb_top. apb_rw_channel resp_chan. This effect can be accomplished by turning off the rand_mode for the apb_rw::kind and apb_rw::addr properties then randomizing the remaining properties. File: Command_Slave_Xactor/tb_env. endfunction: compare endclass: apb_rw `vmm_channel(apb_rw) `vmm_atomic_gen(apb_rw. return 0.data.sv" `include "apb_slave.resp_chan).data) begin $sformat(diff.data).data !== tr. "Data 0x%h !== 0x%h".resp_chan = new("APB Response". "APB Bus Cycle") `vmm_scenario_gen(apb_rw. "APB Bus Cycle") `endif Random stimulus can also be used in generating the response to READ cycles.apb0.build(). this. this.sv `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm. this.slv = new("0". tr. int stop_after = 10.

get(tr). "Unable to randomize APB response").virtual task start(). end end this.stop_after <= 0) -> this.randomize()) begin `vmm_error(log.start(). super.addr.kind. this. fork forever begin apb_rw tr.peek(tr).resp_chan.wait_for(apb_slave::RESPONSE).slv.kind == apb_rw::READ) begin tr. if (this.resp_chan.notify. this. if (!tr. end join_none endtask: start virtual task wait_for_end(). if (tr.end_test. super.notify.rand_mode(0).display("Responded: ").end_test).slv. this. this. endtask: wait_for_end endclass: tb_env `endif VMM Primer: Writing Command-Layer Slave Transactors 61 . tr. end forever begin apb_rw tr. $cast(tr. @ (this. tr.slv.rand_mode(0).status(apb_slave::RESPONSE)).start_xactor(). this.stop_after--.wait_for_end().

rand bit [31:0] start_addr = 32’h0000_0000.sv" class apb_slave_cfg. this.end_addr). } virtual function string psdisplay(string prefix = ""). However. a user may wish to annotate the transaction descriptor with additional information inside an extension of the “pre_response” callback method. rand bit [31:0] end_addr = 32’hFFFF_FFFF. Using a factory pattern. the apw_rw transaction descriptor cannot be written to meet the unpredictable needs of users for annotating it with arbitrary information. $sformat(psdisplay.Step 14: Annotating Responses The transaction descriptor created by the slave transactor is always of type apb_rw because of the following statement: tr = new. File: apb/apb_slave. constraint apb_slave_cfg_valid { end_addr >= start_addr. a user can cause the slave transactor to instantiate an extension of the apb_rw class (Rule 4-115) that will then be filled in by the transactor but can also provide additional properties and methods for user-specified annotations.sv" `include "apb_rw. "%sAPB Slave Config: [‘h%h-‘h%h]". This allocates a new instance of an object of the same type as the “tr” variable. endfunction: psdisplay VMM Primer: Writing Command-Layer Slave Transactors 62 . prefix.start_addr. this.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. Unfortunately.

apb_rw_channel resp_chan = null. apb_rw_channel resp_chan.new("APB Slave". virtual apb_if. apb_rw cycle). class apb_slave_cbs extends vmm_xactor_callbacks.resp_chan = resp_chan. this. this. VMM Primer: Writing Command-Layer Slave Transactors 63 .notify. endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor. apb_slave_cfg cfg = null. virtual apb_if. virtual function void pre_response(apb_slave xact.cfg = cfg. function new(string name.sigs = sigs.slave sigs. int unsigned stream_id. this. endfunction: reconfigure virtual function void poke(bit [31:0] addr. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg).endclass: apb_slave_cfg typedef class apb_slave. apb_rw tr_factory = null).slave sigs.tr_factory = tr_factory. endfunction: pre_response virtual function void post_response(apb_slave xactor. this. stream_id). local apb_slave_cfg cfg. if (tr_factory == null) tr_factory = new. local bit [31:0] ram[*]. this. name. super.configure(RESPONSE). if (cfg == null) cfg = new. apb_rw tr_factory. apb_rw cycle). typedef enum {RESPONSE} notifications_e.cfg = cfg. this.

resp_chan.paddr > this.sigs.cfg. end this. return. this.paddr < this. if (addr < this.cfg. forever begin apb_rw tr. // Wait for a SETUP cycle do @ (this.end_addr) begin `vmm_error(this. "Waiting for start of transaction.sigs. endfunction: reset_xactor virtual protected task main().log.start_addr || addr > this.start_addr || this.wait_if_stopped().ram.log.sck.pwrite) ? apb_rw::WRITE : apb_rw::READ.exists(addr)) ? this.cfg.sck).sigs. if (addr < this.end_addr).sck. this.end_addr) begin `vmm_error(this. VMM Primer: Writing Command-Layer Slave Transactors 64 . while (this. endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr). return ’x.sck. super.tr_factory. "Out-of-range peek").sigs. tr. tr.reset_xactor(rst_typ).ram[addr] = data.sigs.start_addr || addr > this.main().indicate(vmm_data::STARTED).penable !== 1'b0 || this. this.sigs.").sck.ram[addr] : ’x. this.bit [31:0] data).cfg.prdata <= ’z. $cast(tr. end return (this. "Out-of-range poke").sck.sigs.psel !== 1'b1 || this.prdata <= ’z..notify.sigs.cfg. this.kind = (this.sck. `vmm_trace(log.allocate()). super.sck.cfg..flush(). endfunction: peek virtual function void reset_xactor(reset_e rst_typ = SOFT_RST).

tr)). `vmm_callback(apb_slave_cbs. if (tr.addr] = tr.addr].psdisplay(" ")}).sck).log.data = ’x. else tr.ram[tr.kind == apb_rw::READ) begin if (this.data. fork begin fork begin @ (this. tr)).ram[tr.data = this.sck). tr.put(tr). end this. end `vmm_callback(apb_slave_cbs. end if (this.sigs.penable !== 1'b1) begin `vmm_error(this. {"Responding to transaction.sigs.sigs.resp_chan.addr)) tr. end else begin @ (this. @ (this.sigs.sigs.sck. if (this.tr.resp_chan == null) this.paddr.sck. abort = 1. tr.sck.log.exists(tr. "No response in time"). end else begin bit abort = 0.sck).\n".resp_chan.pwdata.ram.data = this. end join if (abort) continue. join_any disable fork. pre_response(this.sneak(tr).prdata <= tr.sigs. else this..resp_chan == null) begin if (!this. this.sck. pre_response(this. "APB protocol violation:\n VMM Primer: Writing Command-Layer Slave Transactors 65 .sigs.addr = this.data. `vmm_error(this. `vmm_trace(log..

if (to == null) tr = new. else if (!$cast(tr. virtual function vmm_data allocate().notify. a test may now add user-defined information to a transaction descriptor that may be used to determine the transaction response.sv `include "tb_env. tr). File: Command_Slave_Xactor/test_annotate.sv" program annotate_test. `vmm_callback(apb_slave_cbs. end tr. post_response(this. annotated_apb_rw tr = new.indicate(RESPONSE. VMM Primer: Writing Command-Layer Slave Transactors 66 . return tr.. `vmm_trace(log. annotated_apb_rw tr.indicate(vmm_data::ENDED). {"Responded to transaction. this.notify.. "Cannot copy to a \n non-annotated_apb_rw instance").psdisplay(" ")}). tr)). to)) begin `vmm_fatal(log.\n". tr. end endtask: main endclass: apb_slave `endif With the transaction descriptors allocated using a factory pattern and the transaction descriptor accessible in a callback method before a response is returned. class annotated_apb_rw extends apb_rw. string note.SETUP cycle not followed by ENABLE cycle"). endfunction virtual function vmm_data copy(vmm_data to = null).

local string format = "[-%0d-]".psdisplay(prefix). apb_rw cycle).note = this.note. return. env. ")"}.note. psdisplay = {super.return null. tr. function new(string format = ""). if (!$cast(tr. endfunction virtual function string psdisplay(string prefix = ""). local int seq = 0.build(). annotated_apb_rw tr. "Annotate"). end super. cycle)) begin `vmm_error(xactor. return tr. begin annotated_apb_rw tr = new. this.copy(tr). endfunction endclass class annotate_tr extends apb_slave_cbs. " (".format. if (format != "") this.stop_after = 5. tb_env env = new. this.format = format. endfunction: pre_response endclass vmm_log log = new("Test".seq++). VMM Primer: Writing Command-Layer Slave Transactors 67 . initial begin env. endfunction virtual function void pre_response(apb_slave xactor. end $sformat(tr. this.note.log. "Transaction descriptor \n is not a annotated_apb_rw").

VMM Primer: Writing Command-Layer Slave Transactors 68 . end endprogram Step 15: Top-Level File To help users include all necessary files without having to know the detailed filenames and file structure of the transactor. interface and transaction descriptor.sv" "apb_if.sv" "apb_rw. but a complete VIP for a protocol would also include a master transactor and a passive monitor. All of these transactors would be included in the top-level file.annotate_tr cb = new.run().sv" In this example.sv `ifndef APB__SV `define APB__SV `include `include `include `include `endif "vmm. File: apb/apb.append_callback(cb).slv.slv. it is a good idea to create a top-level file that will automatically include all source files that make up the verification IP for a protocol. env. end env. env.sv" "apb_slave. $finish(). we implemented only a slave transactor.tr_factory = tr.

functional-layer transactors or verification environments.Step 16: Congratulations! You have now completed the creation of a VMM-compliant command-layer slave transactor! Upon reading this primer. Based on a few simple question and answers. you probably realized that there is much code that is similar across different monitors. Command % vmmgen –l sv The relevant templates for writing command-layer slave transactors are: • • • Physical interface declaration Transaction Descriptor Reactive Driver. Half-duplex Note that the menu number used to select the appropriate template may differ from the number in the above list. Physical-level. VMM Primer: Writing Command-Layer Slave Transactors 69 . it will create a template for various components of a VMM-compliant monitor. You may consider reading other publications in this series to learn how to write VMM-compliant command-layer master transactors. command-layer monitors. Wouldn’t be nice if you could simply cut-and-paste from an existing VMM-compliant monitor and only modify what is unique or different for your protocol? That can easily be done using the “vmmgen” tool provided with VCS 2006.06-6.

VMM Primer: Writing Command-Layer Slave Transactors 70 .

3 / Dec 01. 2006 1 VMM Primer: Writing Command-Layer Monitors 1 .VMM Primer: Writing Command-Layer Monitors Author: Janick Bergeron Version 0.

how to create a simple VMMcompliant master transactor. you should read—and apply—this primer in its entirety. It was designed to be a reference document that defines what is—and is not—compliant with the methodology described within it. It is sufficient to achieve the goal of demonstrating. Therefore. You can use the same sequence to create your own specific transactor.Introduction The Verification Methodology Manual for SystemVerilog book was never written as a training book. such as master and slave transactors. This document is written in the same order you would implement a command-layer monitor. assertions and verification environments. The protocol used in this primer was selected for its simplicity. as soon as a functional monitor is available. Once a certain minimum level of functionality is met. VMM compliance is a matter of degree. it does not require the use of many elements of the VMM standard library. a monitor may be declared VMM compliant. This primer is designed to learn how to write VMM-compliant command-layer monitors—transactors that observe pin wiggling on one side and report the observed transactions on a transaction-level interface on the other side. Other primers will eventually cover other aspects of developing VMM-compliant verification assets. As such. step-by-step. Because of its simplicity. A word of caution however: it may be tempting to stop reading this primer half way through. functional-layer transactors. you should read it in a sequential fashion. But additional VMM functionality—such as callbacks— will make it much easier to use in different verification environments. VMM Primer: Writing Command-Layer Monitors 2 . generators.

When writing a reusable monitor. the APB monitor should be written for the entire 32-bit of address and data information.This primer will show how to apply the various VMM guidelines. The Verification Components Figure 1 illustrates the various components that will be created throughout this primer. It is a simple single-master address-based parallel bus providing atomic individual read and write cycles. Therefore.com). you have to think about all possible applications it may be used in. A command-layer monitor interfaces directly to the DUT signals and reports all observed transactions on a transaction-level interface. If you are interested in learning more about the justifications of various techniques and approaches used in this primer. The protocol specification can be found in the AMBA™ Specification (Rev 2. VMM Primer: Writing Command-Layer Monitors 3 . not attempt to justify them or present alternatives. not just the device you are using it for the first time. even though the device in this primer only supports 8 address bits and 16 data bits. you should refer to the VMM book under the relevant quoted rules and recommendations. The Protocol The protocol used in this primer is the AMBA™ Peripheral Bus (APB) protocol.0) available from ARM (http://arm.

There may be multiple slaves on an APB bus but there can only be one master. Slaves are differentiated by responding to different address ranges. The entire content of the file declaring the interface is embedded in an `ifndef/`define/`endif construct. A single exchange of information (a READ or a WRITE operation) is called a transaction. This is an old C trick that allows the file to be included multiple times. VMM Primer: Writing Command-Layer Monitors 4 . The signals are declared inside an interface (Rule 4-4). without causing multipledefinition errors. whenever required. The name of the interface is prefixed with “apb_” to identify that it belongs to the APB protocol (Rule 4-5).Figure 1 Components Used in this Primer Transaction Descriptors Channel Notification Monitor Transaction Descriptor Virtual Interface Transactor or DUT DUT Interface APB Step 1: The Interface The first step is to define the physical signals used by the protocol to exchange information between a master and a slave.

clocking blocks are used to define the direction and sampling of the signals (Rule 4-7. wire [31:0] paddr. 4-11).. wire [31:0] prdata. wire [31:0] paddr. wire pwrite. File: apb/apb_if. wire psel.. .sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk).4. File: apb/apb_if.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). endinterface: apb_if `endif Because this is a synchronous protocol. wire penable.. wire psel.File: apb/apb_if. VMM Primer: Writing Command-Layer Monitors 5 . endinterface: apb_if `endif The signals. wire [31:0] pwdata. wire penable.. are declared as wires (Rule 4-6) inside the interface. listed in the AMBA™ Specification in Section 2. .sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if.

wire [31:0] pwdata. clocking pck @(posedge pclk). prdata. input paddr.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). penable. wire [31:0] pwdata. endclocking: pck modport passive(clocking pck). prdata. File: apb/apb_if. wire pwrite. endinterface: apb_if `endif VMM Primer: Writing Command-Layer Monitors 6 . psel. psel. endclocking: pck . input paddr.wire pwrite. pwrite. pwdata. The clock signal need not be specified as it is implicit in the clocking block. wire [31:0] prdata. clocking pck @(posedge pclk). 4-12).. wire psel. pwrite. endinterface: apb_if `endif The clocking block defining the synchronous signals is specified in the modport for the APB monitor (Rule 4-9. 4-11. penable. wire penable.. pwdata. wire [31:0] prdata. wire [31:0] paddr.

.apb_sel (apb0. it should eventually include a modport for a master and a slave monitor transactor (Rule 4-9).apb_enable (apb0..prdata[15:0]). .. . ...pwdata[15:0]). File: Command_Monitor_Xactor/tb_top..psel )..pwrite ). To be fully compliant..prdata[15:0]).apb_addr (apb0.apb_enable (apb0.. . .. when these transactors will be written.apb_addr (apb0. . alongside of the DUT instantiation (Rule 4-13).apb_write (apb0.pwdata[15:0]). .apb_wdata (apb0. Step 2: Connecting to the DUT The interface may now be connected to the DUT..sv module tb_top. These can be added later.apb_rdata (apb0. .apb_sel (apb0. . The connection to the DUT pins are specified using a hierarchical reference to the wires in the interface instance..The interface declaration is now sufficient for writing a passive APB monitor..). . slave_ip dut_slv(.).apb_rdata (apb0. . apb_if apb0(. . master_ip dut_mst(. ..paddr[7:0] ). It is instantiated in a top-level module.penable ).. .apb_wdata (apb0.)..apb_write (apb0. endmodule: tb_top VMM Primer: Writing Command-Layer Monitors 7 .pwrite ).paddr[7:0] ). .psel )..penable ).

. File: Command_Monitor_Xactor/tb_top. .paddr[7:0] ).apb_rdata (apb0. . endmodule: tb_top Step 3: The Transaction Descriptor The next step is to define the APB transaction descriptor (Rule 4-54).psel ).pwdata[15:0]). containing a public property enumerating the various transactions that can be observed by the monitor (Rule 4-60. bit clk = 0. . This descriptor is a class (Rule 4-53) extended from the vmm_data class (Rule 4-55). 4-62)..apb_enable (apb0.pwrite ). . apb_if apb0(clk). always #10 clk = ~clk.This top-level module also contains the clock generators (Rule 4-15)..apb_addr (apb0. It also needs a static vmm_log property instance used to issue messages from the transaction descriptor.. VMM Primer: Writing Command-Layer Monitors 8 .apb_write (apb0. .. my_design dut(.sv module tb_top. . 4-62) and public properties for each parameter or value in the transaction (Rule 4-59.penable ).clk (clk)). .apb_sel (apb0.apb_wdata (apb0.. This instance of the message service interface is passed to the vmm_data constructor (Rule 4-58). using the bit type (Rule 4-17) and ensuring that no clock edges will occur at time zero (Rule 4-16)..prdata[15:0]).. .

In a READ transaction. A transactionlevel interface will be required to transfer transaction descriptors to a transactor to be executed.new(this. Although the transaction descriptor is not yet VMM-compliant. Because the APB does not support concurrent read/write transactions. endfunction: new . there can only be one data value valid at any given time. enum {READ.log). In a WRITE transaction. endclass: apb_rw .sv" class apb_rw extends vmm_data. static vmm_log log = new("apb_rw". despite the fact that the APB bus has separate read and write data buses. VMM Primer: Writing Command-Layer Monitors 9 . super. `endif A single property is used for data. WRITE} kind. "class"). logic [31:0] data. it is the data value that was read. The data class property is interpreted differently depending on the transaction kind (Rule 4-71). function new().. it is interpreted as the data to be written.. it is has the minimum functionality to be used by a monitor. bit [31:0] addr.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm.. This is done using the `vmm_channel macro (Recommendation 4-56). The type for the data property is logic as it will allow the description of READ cycles to reflect unknown results..File: apb/apb_rw.

logic [31:0] data. . super. "class")..log). WRITE} kind. endclass: apb_monitor `endif VMM Primer: Writing Command-Layer Monitors 10 .. bit [31:0] addr.. static vmm_log log = new("apb_rw".new(this. class apb_monitor extends vmm_xactor. It is a class (Rule 4-91) derived from the vmm_xactor base class (4-92).. endfunction: new ..sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV . enum {READ... endclass: apb_rw `vmm_channel(apb_rw) . File: apb/apb_monitor. function new().. `endif Step 4: The Monitor The monitor transactor can now be started.File: apb/apb_rw.sv" class apb_rw extends vmm_data.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm.

not an edge of an input signal (Rule 4-7.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if.). virtual apb_if. endclass: apb_monitor `endif The transaction descriptors are filled in from observations on the physical interface in the main() task (Rule 4-93). name.. stream_id).. 4-12). virtual apb_if. The physical-level interface is done using a virtual modport passed to the transactor as a constructor argument (Rule 4-108) and is saved in a public property (Rule 4-109). endfunction: new . .passive sigs. super..sv" `include "apb_rw.. File: apb/apb_monitor... The observation of READ and WRITE transactions is coded exactly as it would be if good old Verilog was used. class apb_monitor extends vmm_xactor.The transactor needs a physical-level interface to observe transactions. .sv" . The active clock edge is defined by waiting on the clocking block itself.sigs = sigs.. function new(string name. VMM Primer: Writing Command-Layer Monitors 11 . .passive sigs. int unsigned stream_id. The only difference is that the physical signals are accessed through the clocking block of the virtual modport instead of pins on a module. this.... It is a simple matter of sampling input signals at the right point in time.new("APB Monitor".

if (this.). virtual protected task main().pck. .pck.File: apb/apb_monitor.pwrite) ? apb_rw::WRITE : apb_rw::READ. stream_id).sv" `include "apb_rw. endfunction: new . class apb_monitor extends vmm_xactor. int unsigned stream_id. .data = (tr. tr = new.sigs..pck.pck. .sigs = sigs. ..sigs. virtual apb_if... function new(string name.paddr.addr = this. tr.. while (this.sigs.penable !== 1'b1) begin `vmm_error(this.pck)..kind == apb_rw::READ) ? this.passive sigs.sigs.sigs.sigs.psel !== 1'b1 || this....penable !== 1'b0). // Wait for a SETUP cycle do @ (this.passive sigs.sigs. super. .. name.main(). super.. end tr.pck.pck).new("APB Monitor". this.pck.sigs.sigs.prdata : this...kind = (this.pwdata... virtual apb_if.pck..sv" . end endtask: main endclass: apb_monitor VMM Primer: Writing Command-Layer Monitors 12 . "APB protocol violation: SETUP cycle not followed by ENABLE cycle"). tr.log. forever begin apb_rw tr.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if. . @ (this.

int unsigned stream_id.master sigs.. // Wait for a SETUP cycle do @ (this. if the monitor is stopped in the middle of a transaction stream. forever begin apb_rw tr.. stream_id). super. it will report a transaction error at best or transactions composed of information from two different transactions.penable !== 1'b0).. super... .main(). it must not be stopped until the end of the transaction is observed and the entire transaction is reported.psel !== 1'b1 || this.`endif Once a monitor recognizes the start of a transaction. function new(string name. virtual apb_if. File: apb/apb_monitor.. class apb_monitor extends vmm_xactor. name.sigs = sigs.new("APB Master"..wait_if_stopped(). . VMM Primer: Writing Command-Layer Monitors 13 .passive sigs. . while (this. ..). Therefore.sigs.pck)..sigs.sv" .pck. endfunction: new .. virtual protected task main(). this. the vmm_xactor::wait_if_stopped() method is called only before the beginning of transaction.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if.. virtual apb_if..pck.sigs.sv" `include "apb_rw. Otherwise. tr = new. this.

paddr.pck.sv" `include "apb_rw.addr = this.sigs.data = (tr. being derived from vmm_data.kind = (this.. Observed transactions can be reported by indicating a notification in the vmm_xactor::notify property.penable !== 1'b1) begin `vmm_error(this. if (this.sigs. @ (this.pck. Simply displaying the observed transactions is not very useful as it will require that the results be manually checked every time.kind == apb_rw::READ) ? this.pck.pck. "APB protocol violation: SETUP cycle not followed by ENABLE cycle").sigs..pck.pwdata.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if. tr. end endtask: main endclass: apb_monitor `endif Step 5: Reporting Transactions The monitor must have the ability to inform the testbench that a specific transaction has been observed.sigs. is attached to a newly defined OBSERVED notification and can be recovered by all interested parties waiting on the notification. The observed transaction.pwrite) ? apb_rw::WRITE : apb_rw::READ.pck).sv" ..log. end tr. File: apb/apb_monitor.tr. .sigs.sigs.prdata : this.. VMM Primer: Writing Command-Layer Monitors 14 .

..pck..pck.sigs..pck.pwrite) ? apb_rw::WRITE : apb_rw::READ..log..kind = (this.paddr.indicate(OBSERVED.sigs.notify. // Wait for a SETUP cycle do @ (this. this. forever begin apb_rw tr.sigs.wait_if_stopped().sigs = sigs.pck). .kind == apb_rw::READ) ? this.prdata : this. end tr.main(). tr).pck). . . super. super.notify. tr.sigs..addr = this. int unsigned stream_id. virtual protected task main(). if (this.pck. this.sigs.sigs. this.class apb_monitor extends vmm_xactor.. function new(string name.sigs. typedef enum {OBSERVED} notifications_e. tr.configure(OBSERVED). . virtual apb_if.. endfunction: new .penable !== 1'b0).sigs. while (this. tr = new.master sigs. "APB protocol violation: SETUP cycle not followed by ENABLE cycle").. .pwdata...pck. . .sigs.penable !== 1'b1) begin `vmm_error(this. virtual apb_if.).passive sigs.psel !== 1'b1 || this. stream_id). @ (this... this. name.pck..new("APB Master". end endtask: main endclass: apb_monitor VMM Primer: Writing Command-Layer Monitors 15 .data = (tr.pck..

this. . virtual function void build().`endif Step 6: The First Test Although not completely VMM-compliant. 4-35) and started in the extension of the vmm_env::start() method (Rule 4-41). endtask: start ... apb_monitor mon... 0. VMM Primer: Writing Command-Layer Monitors 16 . It is instantiated in a verification environment class.build(). ..apb0). The monitor is constructed in the extension of the vmm_env::build() method (Rule 4-34. The reference to the interface encapsulating the APB physical signals is made using a hierarchical reference in the extension of the vmm_env::build() method.sv" class tb_env extends vmm_env... the monitor can now be used to monitor the activity on an APB bus.start_xactor(). endfunction: build virtual task start()..start(). super.. . this. tb_top.mon.. File: Command_Monitor_Xactor/tb_env. extended from the vmm_env base class. . super.mon = new("0".sv `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm.sv" `include "apb_monitor.

display("Notified: "). the monitor can report the observed transactions. this. 0. super. fork .start_xactor(). if (this.mon..build().status(apb_monitor::OBSERVED))... this.mon = new("0". $cast(tr. . int stop_after = 10.end_test.apb0). endfunction: build virtual task start().notify.. virtual function void build(). this. `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm.. apb_monitor mon. .stop_after <= 0) -> this.mon.stop_after--. forever begin apb_rw tr.notify.. this.endclass: tb_env `endif As the simulation progress.wait_for(apb_monitor::OBSERVED). end join_none VMM Primer: Writing Command-Layer Monitors 17 . The monitor can also be used to determine when it is time to end the test by reporting when enough APB transactions have been observed. .start().sv" `include "apb_monitor.mon. tb_top. tr. super...sv" class tb_env extends vmm_env.. this.. .

endtask: start virtual task wait_for_end(); super.wait_for_end(); @ (this.end_test); endtask: wait_for_end ... endclass: tb_env `endif

A test can control how long the simulation should run by simply setting the value of the tb_env::stop_after property. The test is written in a program (Rule 4-27) that instantiates the verification environment (Rule 4-28). File: Command_Monitor_Xactor/test_simple.sv
`include "tb_env.sv" program simple_test; vmm_log log = new("Test", "Simple"); tb_env env = new; initial begin env.stop_after = 5; env.run(); $finish(); end endprogram

Step 7: Standard Methods
The transactions reported by the monitor are not very useful as they do not show the content of the observed transactions. That’s because the psdisplay() method, defined in the vmm_data base class does not know about the content of the APB transaction
VMM Primer: Writing Command-Layer Monitors 18

descriptor. For that method to display the information that is relevant for the APB transaction, it is necessary to overload this method in the transaction descriptor class (Rule 4-76). File: apb/apb_rw.sv
`ifndef APB_RW__SV `define APB_RW__SV `include "vmm.sv" class apb_rw extends vmm_data; static vmm_log log = new("apb_rw", "class"); enum {READ, WRITE} kind; bit [31:0] addr; logic [31:0] data; function new(); super.new(this.log); endfunction: new ... virtual function string psdisplay(string prefix = ""); $sformat(psdisplay, "%sAPB %s @ 0x%h = 0x%h", prefix, this.kind.name(), this.addr, this.data); endfunction: psdisplay ... endclass: apb_rw `vmm_channel(apb_rw) ... `endif

Re-running the test now yields useful and meaningful transaction monitoring. The vmm_data::psdisplay() method is one of the pre-defined methods in the vmm_data base class that users expect to be provided to simplify and abstract common operations on transaction descriptors. These common operations include creating transaction descriptors (vmm_data::allocate()), copying transaction descriptors (vmm_data::copy()), comparing

VMM Primer: Writing Command-Layer Monitors 19

transaction descriptors (vmm_data::compare()) and checking that the content of transaction descriptors is valid (vmm_data::is_valid()) (Rule 4-76). File: apb/apb_rw.sv
`ifndef APB_RW__SV `define APB_RW__SV `include "vmm.sv" class apb_rw extends vmm_data; static vmm_log log = new("apb_rw", "class"); enum {READ, WRITE} kind; bit [31:0] addr; logic [31:0] data; function new(); super.new(this.log); endfunction: new virtual function vmm_data allocate(); apb_rw tr = new; return tr; endfunction: allocate virtual function vmm_data copy(vmm_data to = null); apb_rw tr; if (to == null) tr = new; else if (!$cast(tr, to)) begin `vmm_fatal(log, "Cannot copy into non-apb_rw instance"); return null; end super.copy_data(tr); tr.kind = this.kind; tr.addr = this.addr; tr.data = this.data; return tr; endfunction: copy

VMM Primer: Writing Command-Layer Monitors 20

virtual function string psdisplay(string prefix = ""); $sformat(psdisplay, "%sAPB %s @ 0x%h = 0x%h", prefix, this.kind.name(), this.addr, this.data); endfunction: psdisplay virtual function bit is_valid(bit silent = 1, int kind = -1); return 1; endfunction: is_valid virtual function bit compare(input vmm_data to, output string diff, input int kind = -1); apb_rw tr; if (to == null) begin `vmm_fatal(log, "Cannot compare to NULL reference"); return 0; end else if (!$cast(tr, to)) begin `vmm_fatal(log, "Cannot compare against non-apb_rw instance"); return 0; end if (this.kind != tr.kind) begin $sformat(diff, "Kind %s !== %s", this.kind, tr.kind); return 0; end if (this.addr !== tr.addr) begin $sformat(diff, "Addr 0x%h !== 0x%h", this.addr, tr.addr); return 0; end if (this.data !== tr.data) begin $sformat(diff, "Data 0x%h !== 0x%h", this.data, tr.data); return 0; end return 1; endfunction: compare endclass: apb_rw

VMM Primer: Writing Command-Layer Monitors 21

. vmm_data:byte_size(). It is sunk by default to avoid accidental memory leakage. However. a channel with no consumer will accumulate transaction descriptors. vmm_data::byte_pack() and vmm_data::byte_unpack() should also be overloaded for packet-oriented transactions. Transactions reported via a vmm_notify can be consumed by an arbitrary number of consumers but must be consumed in zero time.`vmm_channel(apb_rw) . where the content of the transaction is transmitted over a physical interface (Recommendation 4-77). Transactions reported via a vmm_channel can only be consumed by a single consumer. Should a higher-level thread or transactor have the potential to block. it is possible that transactions will be missed. Step 8: More on Transactions The vmm_notify notification service interface can notify an arbitrary number of threads or transactors but it can store only one transaction at a time. unless it is sunk using the vmm_channel::sink() method. passed to the transactor as constructor arguments (Recommendation 4-113). `endif Three other standard methods. Transaction VMM Primer: Writing Command-Layer Monitors 22 .. where the transaction descriptors are kept until they are explicitly consumed. but that consumer can only consume transactions at its own pace. is saved in a public property (Rule 4-112). The vmm_channel transaction-level interface is used to provide a buffering reporting mechanism. a vmm_notify mechanism will not accumulate transaction descriptors. The output channel. Should there be no consumers.

pck)..notify.descriptors are added to the output channel using the vmm_channel::sneak() method (Rule 4-140) to avoid the monitor from blocking on a full channel and missing transactions.).master sigs. class apb_monitor extends vmm_xactor. virtual protected task main(). super. out_chan..sigs. // Wait for a SETUP cycle do @ (this.sv" .out_chan = out_chan. endfunction: new . .psel !== 1'b1 || this. while (this. stream_id).new("APB Master".pck..sigs = sigs.pck.passive sigs. this. function new(string name..sink(). File: apb/apb_monitor. this. . name. VMM Primer: Writing Command-Layer Monitors 23 . forever begin apb_rw tr. typedef enum {OBSERVED} notifications_e. this..wait_if_stopped().sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if. virtual apb_if. end this. name).sigs...configure(OBSERVED).sigs.main(). apb_rw_channel out_chan = null . apb_rw_channel out_chan.. if (out_chan == null) begin out_chan = new("APB Monitor Output Channel".sv" `include "apb_rw. int unsigned stream_id. virtual apb_if.. super.penable !== 1'b0)..

tr..data = (tr. .sv" `include "apb_rw.log. if (this. VMM Primer: Writing Command-Layer Monitors 24 .sigs.. .pck.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if. .sigs.. typedef enum {OBSERVED} notifications_e. int unsigned stream_id..indicate(OBSERVED.pck. the output channel must be flushed.pck. end tr.pck). File: apb/apb_monitor. class apb_monitor extends vmm_xactor.pck.notify. function new(string name.sv" .sigs.. virtual apb_if.sigs.out_chan. apb_rw_channel out_chan. end endtask: main endclass: apb_monitor `endif When the transactor is reset.prdata : this. "APB protocol violation: SETUP cycle not followed by ENABLE cycle"). this.sigs.addr = this.sneak(tr).master sigs. this. This is accomplished in the extension of the vmm_xactor::reset_xactor() method (Table A-8).pwrite) ? apb_rw::WRITE : apb_rw::READ.. @ (this.kind == apb_rw::READ) ? this.passive sigs.paddr. virtual apb_if..tr = new..kind = (this. tr.pwdata.sigs. tr).pck.penable !== 1'b1) begin `vmm_error(this.

this.kind == apb_rw::READ) ? this.sigs. . stream_id). VMM Primer: Writing Command-Layer Monitors 25 .sigs. while (this.apb_rw_channel out_chan = null .sigs. end this. tr). endfunction: new virtual function void reset_xactor(reset_e rst_typ = SOFT_RST)..pck).out_chan = out_chan. "APB protocol violation: SETUP cycle not followed by ENABLE cycle").pck.sigs. forever begin apb_rw tr.paddr.indicate(OBSERVED.. super. // Wait for a SETUP cycle do @ (this. out_chan. this. .log. super.pck.sigs.prdata : this.).new("APB Master".wait_if_stopped().sigs.. name.configure(OBSERVED).flush(). if (out_chan == null) begin out_chan = new("APB Monitor Output Channel".pwrite) ? apb_rw::WRITE : apb_rw::READ.. end tr. name).pck.sink().data = (tr.main(). this.pck.penable !== 1'b0). @ (this.psel !== 1'b1 || this. super.pck).kind = (this.sigs. endfunction: reset_xactor virtual protected task main(). this.notify. tr... tr = new.pck. .addr = this.reset_xactor(rst_typ).notify. if (this..pck..sigs. tr.pck.sigs.pwdata.out_chan. this.sigs = sigs.penable !== 1'b1) begin `vmm_error(this.

typedef enum {OBSERVED} notifications_e... out_chan.. class apb_monitor extends vmm_xactor. apb_rw_channel out_chan. this.new("APB Master".sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if.. if (out_chan == null) begin out_chan = new("APB Monitor Output Channel". name). These transaction endpoints are recorded in the transaction descriptor itself by indicating the vmm_data::STARTED and vmm_data::ENDED notifications (Rule 4-142).). function new(string name.configure(OBSERVED).sv" `include "apb_rw. endfunction: new VMM Primer: Writing Command-Layer Monitors 26 .sv" . File: apb/apb_monitor.sigs = sigs.out_chan = out_chan. stream_id). end this. this... int unsigned stream_id. virtual apb_if... .passive sigs.sneak(tr). . name.sink(). end endtask: main endclass: apb_monitor `endif It may be useful for the higher-level functions using the transactions reported by the monitor to know when the transaction was started and when it ended.master sigs. virtual apb_if.notify. super. apb_rw_channel out_chan = null .this.out_chan.

addr = this.pck).sigs.pck.penable !== 1'b1) begin `vmm_error(this.indicate(vmm_data::STARTED). endfunction: reset_xactor virtual protected task main().indicate(OBSERVED.virtual function void reset_xactor(reset_e rst_typ = SOFT_RST).paddr.pck. tr = new.sigs.sigs.reset_xactor(rst_typ).main().sigs.data = (tr. . forever begin apb_rw tr. // Wait for a SETUP cycle do @ (this.sigs.indicate(vmm_data::ENDED). if (this. end endtask: main endclass: apb_monitor `endif VMM Primer: Writing Command-Layer Monitors 27 .sneak(tr). tr.kind = (this. tr).notify.log.prdata : this..pck.flush(). this.kind == apb_rw::READ) ? this. while (this.penable !== 1'b0).pck.pck.pck.pck).pwdata.notify. tr.sigs. super. super. "APB protocol violation: SETUP cycle not followed by ENABLE cycle").psel !== 1'b1 || this. end tr. this. tr.sigs.notify.out_chan. this. tr.wait_if_stopped(). @ (this.. this.pck.sigs.out_chan.pwrite) ? apb_rw::WRITE : apb_rw::READ.sigs.

. These debug messages are inserted using the `vmm_trace(). int unsigned stream_id.passive sigs. super. stream_id).sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if.out_chan = out_chan.Step 9: Debug Messages To be truly reusable...notify. is doing or has done. this.. apb_rw_channel out_chan. end this. Debug messages should be added at judicious points to indicate what the monitor is about to do. virtual apb_if. VMM Primer: Writing Command-Layer Monitors 28 . File: apb/apb_monitor.sv" `include "apb_rw. name).new("APB Master".master sigs..). This capability may even be a basic requirement if you plan on shipping encrypted or compiled code. if (out_chan == null) begin out_chan = new("APB Monitor Output Channel". this. typedef enum {OBSERVED} notifications_e. it should be possible to understand what the monitor does and debug its operation without having to inspect the source code. out_chan.sv" .configure(OBSERVED). class apb_monitor extends vmm_xactor. apb_rw_channel out_chan = null .sigs = sigs. `vmm_debug() or `vmm_verbose() macros (Recommendation 4-51).... name. virtual apb_if. . . function new(string name.sink().

this.out_chan. tr.addr = this.sigs.wait_if_stopped(). end endtask: main endclass: apb_monitor `endif VMM Primer: Writing Command-Layer Monitors 29 .penable !== 1'b0). . // Wait for a SETUP cycle do @ (this.indicate(OBSERVED.kind == apb_rw::READ) ? this.main().out_chan.reset_xactor(rst_typ).. this. tr. tr = new.psdisplay(" ")}). tr. if (this. `vmm_trace(log..prdata : this. forever begin apb_rw tr.indicate(vmm_data::STARTED).indicate(vmm_data::ENDED).paddr.pwrite) ? apb_rw::WRITE : apb_rw::READ. @ (this.\n". super.psel !== 1'b1 || this.sigs.pck).sigs.notify.. this.sigs.sigs.sigs. {"Observed transaction.").log. while (this.flush().sigs.penable !== 1'b1) begin `vmm_error(this.pck..notify.data = (tr.pck).pck.notify.pck. end tr.pck.sigs.pck. "Waiting for start of transaction..pck. `vmm_trace(log.. "APB protocol violation: SETUP cycle not followed by ENABLE cycle").sneak(tr). tr.pwdata.sigs.kind = (this.pck. tr). tr. super. endfunction: reset_xactor virtual protected task main().endfunction: new virtual function void reset_xactor(reset_e rst_typ = SOFT_RST). this.

the callback method needs to be invoked at the appropriate point in the execution of the monitor. A callback method should be provided after a transaction has been observed (Recommendation 4-155).sv" typedef class apb_monitor. File: apb/apb_monitor. VMM Primer: Writing Command-Layer Monitors 30 .. Step 10: Extension Points A callback method is a third mechanism for reporting observed transactions. File: Command_Monitor_Xactor/Makefile % vcs –sverilog –ntb_opts rmm +vmm_log_default=trace . using the `vmm_callback() macro (Rule 4-163). Next.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if. This callback method allows the observed transaction to be recorded in a functional coverage model or checked against an expected response..sv" `include "apb_rw. The callback method is first defined as a virtual void function (Rule 4-160) in a callback façade class extended from the vmm_xactor_callbacks base class (Rule 4-159).You can now run the “simple test” with the latest version of the monitor and increase the message verbosity to see debug messages displayed as the monitor observes the various transactions. class apb_monitor_cbs extends vmm_xactor_callbacks.

.. // Wait for a SETUP cycle do @ (this. `vmm_trace(log.psel !== 1'b1 || this.main(). out_chan.penable !== 1'b0). name. if (out_chan == null) begin out_chan = new("APB Monitor Output Channel". VMM Primer: Writing Command-Layer Monitors 31 . .reset_xactor(rst_typ).pck).sigs.sigs.new("APB Master". forever begin apb_rw tr.sink().wait_if_stopped(). apb_rw cycle). this. apb_rw_channel out_chan = null. super. function new(string name. virtual apb_if.. super. virtual apb_if.).pck..").pck.. endfunction: reset_xactor virtual protected task main().. .. this.out_chan.configure(OBSERVED). end this.passive sigs.notify. apb_rw_channel out_chan... name). endfunction: new virtual function void reset_xactor(reset_e rst_typ = SOFT_RST). stream_id). this. this. typedef enum {OBSERVED} notifications_e.master sigs.sigs.flush().sigs = sigs. while (this. int unsigned stream_id. super. "Waiting for start of transaction.virtual function void post_cycle(apb_monitor xactor.out_chan = out_chan. endfunction: post_cycle endclass: apb_monitor_cbs class apb_monitor extends vmm_xactor.

pck.notify.sigs.. tr..penable !== 1'b1) begin `vmm_error(this.pwdata. this.sneak(tr). This allocates a new instance of an object of the same type as the tr variable. However. Unfortunately.tr = new.log. end tr.pck. `vmm_trace(log. `vmm_callback(apb_monitor_cbs. tr)).addr = this.paddr.pck. tr. @ (this. tr). end endtask: main endclass: apb_monitor `endif The transaction descriptor created by the monitor is always of type apb_rw because of the following statement: tr = new. "APB protocol violation: SETUP cycle not followed by ENABLE cycle"). the apw_rw transaction descriptor cannot be written to meet the unpredictable needs of users for annotating it with arbitrary information.kind == apb_rw::READ) ? this. tr.pck). VMM Primer: Writing Command-Layer Monitors 32 .notify.sigs. tr.sigs.indicate(vmm_data::ENDED).data = (tr. post_cycle(this.kind = (this. a user may wish to annotate the transaction descriptor with additional information inside an extension of the “post_cycle” callback method.sigs.pck.sigs. this.pwrite) ? apb_rw::WRITE : apb_rw::READ.sigs. tr.out_chan.indicate(OBSERVED.indicate(vmm_data::STARTED).prdata : this.pck.notify. if (this.\n". {"Observed transaction.psdisplay(" ")}).

a user can cause the monitor to instantiate an extension of the apb_rw class (Rule 4-115) that will then be filled in by the monitor but can also provide additional properties and methods for user-specified annotations. out_chan. apb_rw_channel out_chan.configure(OBSERVED).Using a factory pattern. this. function new(string name. int unsigned stream_id.tr_factory = tr_factory. endfunction: post_cycle endclass: apb_monitor_cbs class apb_monitor extends vmm_xactor. class apb_monitor_cbs extends vmm_xactor_callbacks.sv" `include "apb_rw.sv" typedef class apb_monitor. File: apb/apb_monitor. end this. this.sigs = sigs.notify. name). this.master sigs. name.new("APB Master". VMM Primer: Writing Command-Layer Monitors 33 . apb_rw tr_factory.passive sigs. super. stream_id). virtual function void post_cycle(apb_monitor xactor. typedef enum {OBSERVED} notifications_e. apb_rw tr_factory = null). virtual apb_if.sink().sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if.out_chan = out_chan. if (out_chan == null) begin out_chan = new("APB Monitor Output Channel". virtual apb_if. apb_rw cycle). if (tr_factory == null) tr_factory = new. apb_rw_channel out_chan = null.

pck.main(). $cast(tr..pwdata.notify.pck. forever begin apb_rw tr.\n". `vmm_trace(log.indicate(OBSERVED. `vmm_trace(log. @ (this. this.notify.sigs.penable !== 1'b0).out_chan.pck. if (this. tr.pck.allocate()).sigs. endfunction: reset_xactor virtual protected task main(). this.kind == apb_rw::READ) ? this.tr_factory. tr. tr.indicate(vmm_data::STARTED). this.sigs..sigs. this. `vmm_callback(apb_monitor_cbs. tr.psel !== 1'b1 || this."). // Wait for a SETUP cycle do @ (this.sneak(tr).kind = (this. VMM Primer: Writing Command-Layer Monitors 34 . super.prdata : this.data = (tr..penable !== 1'b1) begin `vmm_error(this.wait_if_stopped().notify.pck). end tr.sigs. tr.indicate(vmm_data::ENDED).addr = this.pck.log.pck.sigs.pck.pck).sigs. this.out_chan. tr)). tr). while (this.sigs.flush().endfunction: new virtual function void reset_xactor(reset_e rst_typ = SOFT_RST).sigs. "APB protocol violation: SETUP cycle not followed by ENABLE cycle"). post_cycle(this.paddr.reset_xactor(rst_typ). "Waiting for start of transaction.pwrite) ? apb_rw::WRITE : apb_rw::READ.psdisplay(" ")}).. {"Observed transaction. super.

File: Command_Monitor_Xactor/test_annotate. class annotated_apb_rw extends apb_rw. return tr.end endtask: main endclass: apb_monitor `endif With the transaction descriptors allocated using a factory pattern and the transaction descriptor accessible in a callback method before it is reported to other transactors. endfunction virtual function string psdisplay(string prefix = ""). virtual function vmm_data allocate(). annotated_apb_rw tr = new. ")"}. annotated_apb_rw tr. psdisplay = {super.sv `include "tb_env. endfunction virtual function vmm_data copy(vmm_data to = null).note = this. VMM Primer: Writing Command-Layer Monitors 35 .copy(tr). tr. to)) begin `vmm_fatal(log.psdisplay(prefix). this. if (to == null) tr = new. "Cannot copy to a non. end super.note. a test may now add user-defined information to a transaction descriptor.sv" program annotate_test. " (". string note.\n annotated_apb_rw instance"). else if (!$cast(tr. return null. return tr.note.

append_callback(cb). annotate_tr cb = new.mon. if (!$cast(tr. cycle)) begin `vmm_error(xactor.log. end $sformat(tr. end env. endfunction virtual function void post_cycle(apb_monitor xactor. local string format = "[-%0d-]". env.format = format. env.note. annotated_apb_rw tr.seq++). "Annotate"). end endprogram VMM Primer: Writing Command-Layer Monitors 36 .run(). function new(string format = "").mon. return.format. endfunction: post_cycle endclass vmm_log log = new("Test".tr_factory = tr. $finish(). if (format != "") this. begin annotated_apb_rw tr = new. this. "Transaction descriptor \n is not a annotated_apb_rw"). local int seq = 0.stop_after = 5. apb_rw cycle). tb_env env = new.build(). initial begin env.endfunction endclass class annotate_tr extends apb_monitor_cbs. env. this.

It is also a good idea to pre-define random transaction generators whenever transaction descriptors are defined. return tr. rand enum {READ. if (to == null) tr = new.log). 4-60.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. apb_rw tr = new. "Cannot copy into non-apb_rw \n instance"). WRITE} kind. 5-24) in the transaction descriptor file.sv" class apb_rw extends vmm_data.Step 11: Random Transactions To promote the use of random stimulus and make the same transaction descriptor usable in transaction generators. File: apb/apb_rw. 4-62). rand logic [31:0] data. "class").new(this. static vmm_log log = new("apb_rw". function new(). endfunction: new virtual function vmm_data allocate(). super. It is a simple matter of using the `vmm_atomic_gen() and `vmm_scenario_gen() macros (Recommendation 5-23. else if (!$cast(tr. apb_rw tr. VMM Primer: Writing Command-Layer Monitors 37 . all public properties in a transaction descriptor should be declared as rand (Rules 4-59. endfunction: allocate virtual function vmm_data copy(vmm_data to = null). to)) begin `vmm_fatal(log. rand bit [31:0] addr.

addr. end super. tr. return 1. endfunction: copy virtual function string psdisplay(string prefix = "").addr. this. this. this. "%sAPB %s @ 0x%h = 0x%h".addr.kind != tr. return 0. if (to == null) begin `vmm_fatal(log. return 0.addr). tr.addr) begin $sformat(diff.addr !== tr. "Addr 0x%h !== 0x%h".addr = this. output string diff. to)) begin `vmm_fatal(log. this. this. $sformat(psdisplay.kind) begin $sformat(diff.data. endfunction: psdisplay virtual function bit is_valid(bit silent = 1.name(). return 0. return tr.kind. tr. end if (this.data). end if (this. end VMM Primer: Writing Command-Layer Monitors 38 . tr.return null. apb_rw tr. "Kind %s !== %s".kind). endfunction: is_valid virtual function bit compare(input vmm_data to. "Cannot compare against non. tr.copy_data(tr). input int kind = -1). prefix. "Cannot compare to NULL reference"). end else if (!$cast(tr.kind. return 0.kind = this.\n apb_rw instance").data = this. int kind = -1).kind.

data !== tr. "Data 0x%h !== 0x%h".sv" VMM Primer: Writing Command-Layer Monitors 39 . it is a good idea to create a top-level file that will automatically include all source files that make up the verification IP for a protocol.sv `ifndef APB__SV `define APB__SV `include `include `include `include `endif "vmm.data. interface and transaction descriptor.data) begin $sformat(diff. end return 1. tr.sv" "apb_monitor.sv" "apb_rw.sv" "apb_if.data). "APB Bus Cycle") `endif Step 12: Top-Level File To help users include all necessary files without having to know the detailed filenames and file structure of the transactor.if (this. "APB Bus Cycle") `vmm_scenario_gen(apb_rw. return 0. File: apb/apb. endfunction: compare endclass: apb_rw `vmm_channel(apb_rw) `vmm_atomic_gen(apb_rw. this.

you probably realized that there is much code that is similar across different monitors. we implemented only a monitor.In this example. Physical-level. Step 13: Congratulations! You have now completed the creation of a VMM-compliant command-layer monitor transactor! Upon reading this primer. All of these transactors would be included in the top-level file. it will create a template for various components of a VMM-compliant monitor. Based on a few simple question and answers. Half-duplex Note that the menu number used to select the appropriate template may differ from the number in the above list. Wouldn’t be nice if you could simply cut-and-paste from an existing VMM-compliant monitor and only modify what is unique or different for your protocol? That can easily be done using the “vmmgen” tool provided with VMM open source ($VMM_HOME/shared/bin/vmmgen) and in VCS. VMM Primer: Writing Command-Layer Monitors 40 . Command % vmmgen –l sv The relevant templates for writing command-layer monitors are: • • • Physical interface declaration Transaction Descriptor Monitor. but a complete VIP for a protocol would also include a master transactor and a slave transactor.

command-layer slave transactors.You may consider reading other publications in this series to learn how to write VMM-compliant command-layer master transactors. functional-layer transactors or verification environments. VMM Primer: Writing Command-Layer Monitors 41 .

VMM Primer: Writing Command-Layer Monitors 42 .

2006 1 VMM Primer: Using the Data Stream Scoreboard 1 .1 / May 17.VMM Primer: Using the Data Stream Scoreboard Author: Janick Bergeron Version 1.

Introduction
The VMM Data Stream Scoreboard is an application package that can be used to simply the creation of a self-checking structure of “data mover” designs. Data mover designs are design that take data on one side, transform it, and then produce data on the other side. Routers, modems, codec, DSP functions, bridges and busses are all data mover designs. This primer is designed to learn how to create a scoreboard based on the Data Stream Scoreboard foundation classes, and how to integrate this scoreboard in a verification environment. Other primers cover other aspects of developing VMM-compliant verification assets, such as transactors, generators, assertions and verification environments. This primer assumes that you are familiar with VMM. If you need to ramp-up on VMM itself, you should read the other primers in this series, such as “Writing a Command-Layer Command Transactor”. The DUT used in this primer was selected for its simplicity. Because of its simplicity, it does not require the use of many elements of the VMM Data Stream Scoreboard package. It is sufficient to achieve the goal of demonstrating, step by step, how to use create a scoreboard and use it to verify a design. This document is written in the same order you would develop a scoreboard, integrate it in a verification environment and verify your design. As such, you should read it in a sequential fashion. You can use the same sequence to create your own scoreboard and use it to verify your design.

VMM Primer: Using the Data Stream Scoreboard 2

The Verification Environment
The Design Under Test used in this primer is an AMBA™ Peripheral Bus (APB), with a single master port and three slave ports. It is a simple address decoding device with the following address map: Table 1 Address Map
paddr[9:8] 2’b00 2’b01 2’b10 Slave Slave #0, paddr[7:0] Slave #1, paddr[7:0] Slave #2, paddr[7:0]

The simplicity of the APB bus does not require the use of a full-fledge scoreboard to verify the correctness of its operation. Since it is not pipelined and does not support out-of-order execution, a simple global variable would suffice. However, the purpose of this primer is to show how to use the Data Stream Scoreboard foundation classes, not verify a complex functionality. This simple design serves the purpose quite well. Let’s ignore the fact that the design is a trivial bus and assume that it is a complex pipelined bus that can execute multiple transactions simultaneously. As shown in Figure 1, the design is exercised by an APB master transactor and the responses are provided by three APB slave transactors. These are the same transactors that were created in the primers on writing command-layer transactors. Transactions are created by an atomic generator. As is, this verification environment is not self-checking.

VMM Primer: Using the Data Stream Scoreboard 3

Figure 1 Verification Environment
SLAVE

GEN

MSTR

SLAVE

SLAVE

Beside its simplicity, there is another reason for selecting this design as the DUT for this primer. The name “Data Stream Scoreboard” seems to imply that it is suited only for data networking applications. The documentation and method arguments themselves refer to the data to be verified as “packets”. But “packet” and “data stream” are abstract notions. They are used to identify transactions and flows of transaction and do not imply any particular technology or application. Using a bus as the DUT give you the opportunity to break any fallacious mental association between the Data Stream Scoreboard package and data networking applications. In this example, a “packet” is an APB transaction and a stream is a transaction executed between the master and one of the slaves. There are thus two types of packets in the design: READ and WRITE cycles. And there are three streams, one for each slave.

Step 1: The Self-Checking Strategy
A scoreboard is only an implementation medium. It must be properly used and fit in an overall self-checking strategy to be effective at verifying the response of a design.

VMM Primer: Using the Data Stream Scoreboard 4

One possible strategy would be to use the default RAM-like behavior of each slave and perform a series of WRITE-READ cycles. The correctness would be verified by checking that the data that was read back was indeed the data that was written. But given the nature of the design, this is a rather poor self-checking strategy. It would fail to uncover several classes of functional bugs, such as misconnected address and data busses and incorrect address decoding. It also requires that a READ cycle targets an address that was previously written to, making it impossible to verify the correct operation of two back-to-back WRITE cycles to the same address. A better strategy is to use the optional response channel in the slave transactor. When present, it allows a higher-level transactor—in this case a response generator—to provide an arbitrary response to any READ cycle. A RAM behavior is only one of the possible responses, one that happens to be built in the slave transactor. Figure 2 shows the structure of the verification environment with slave response generators attached to each slave. These response generators do not react to WRITE cycles but provide a random response to READ cycles. Figure 2 Random Slave Response Environment
SLAVE RESP

GEN

MSTR

SLAVE

RESP

SLAVE

RESP

The slave response generator can be implemented as a VMM compliant transactor and be reusable. Because the intent of this primer is not to show how to write VMM-compliant random response

VMM Primer: Using the Data Stream Scoreboard 5

resp_chan[2] = new("Response".resp_chan[j]. apb_rw_channel resp_chan[3]. endfunction: build virtual function start().resp_chan[2]). tb_top. It can be coded directly in the environment as a forked-off thread. this.sv . class tb_env extends vmm_env. a simple non-reusable and unconstrainable response generator will be used. "2"). File: DateStream_DB/tb_env. fork forever begin apb_rw tr...slv[0] = new("Slave". 2. foreach (this.get(tr). this. this. . virtual function build(). .resp_chan[i]) begin int j = i. .kind == apb_rw::READ) begin tr. this. apb_slave slv[3]. this.. this..resp_chan[1] = new("Response". this. 0. tb_top.resp_chan[1])... 1. .s0..slv[2] = new("Slave". . this. "0").resp_chan[0] = new("Response".. // Poor random // strategy! . .generator. this...resp_chan[0])..resp_chan[j].data = $random().slv[1] = new("Slave".. .s1. end VMM Primer: Using the Data Stream Scoreboard 6 . tb_top. .s2.. if (tr. "1"). this. end this..peek(tr).

`include "vmm_sb. super. VMM Primer: Using the Data Stream Scoreboard 7 .join_none end endfunction: start . A unique stream identifier must be assigned to each stream... the other in the slaves-to-master direction. It is necessary to carry the expected response from the master to the slave (to verify that the right slave is targeted with the right address and write data) and from the slave to the master (to verify the read data). This requires two scoreboards: one in the master-to-slaves direction. This is implemented using the Data Stream Scoreboard foundation class by defining one input stream and three expected streams. function new(). it is no longer possible to predict the expected response strictly from the master’s interface. In this case. The stream identifier is chosen to facilitate the association of a slave with its corresponding stream during integration. Step 2: Master-to-Slaves The master-to-slaves scoreboard requires one queue of input transaction and three queues of expected transaction.new("Master->Slave").. endclass: tb_env With random responses.sv" class m2s_sb extends vmm_sb_ds.sv .. the stream identifier corresponds to the value of ‘j’ in the response generator thread. one per slave. File: DataStream_SB/tb_env.

File: DataStream_SB/tb_env. this.new("Master->Slave"). out_pkts[0] = tr. It is thus necessary to perform the same transformation in the scoreboard. VMM Primer: Using the Data Stream Scoreboard 8 .define_stream(0. "Slave 1". INPUT).paddr[31:8] = ‘0. will be different from the one injected by the master. endfunction: new . Because each slave only has 256 addressable locations. bits [31:8] of the address will always be zero.sv . this. The expected transaction.define_stream(1. INPUT).this.define_stream(2. "Master". virtual function bit transform(input vmm_data in_pkt.define_stream(0. EXPECT). this.. output vmm_data out_pkts[]). this.. EXPECT). as observed by the slaves. "Slave 0". super. function new(). `include "vmm_sb.define_stream(2. "Slave 2". tr. "Slave 1".define_stream(1. apb_rw tr. EXPECT). Any bits set by the master will be masked. EXPECT).sv" class m2s_sb extends vmm_sb_ds. this. in_pkt. out_pkts = new [1]. EXPECT)..copy()). endfunction: new "Master". endclass: m2s_sb . this..define_stream(0. "Slave 2".define_stream(0. $cast(tr. "Slave 0". EXPECT)... this.

. This is done by overloading the vmm_sb_ds::compare() method. File: DataStream_SB/tb_env.addr[31:8] = ‘0. It is thus necessary to specify a custom comparisons function in the master-to-slave scoreboard. virtual function bit transform(input vmm_data in_pkt.. endfunction: transform VMM Primer: Using the Data Stream Scoreboard 9 .sv ... endfunction: new "Master". this.endfunction: transform ..define_stream(0. $cast(tr.copy()). EXPECT). out_pkts[0] = tr. However. By default.sv" class m2s_sb extends vmm_sb_ds. this. output vmm_data out_pkts[]). "Slave 1". when verifying the response of the master-to-slave path.define_stream(2. "Slave 0".. apb_rw tr. in_pkt. EXPECT). function new(). out_pkts = new [1]. the data value can only be compared against an expected value for WRITE cycles. "Slave 2". `include "vmm_sb. tr.new("Master->Slave"). this. super.define_stream(0. INPUT). endclass: m2s_sb . this. the comparison function use by the Data Stream Scoreboard foundation class is the vmm_data::compare() method defined for the transaction descriptor.define_stream(1. EXPECT).

kind == apb_rw::WRITE) begin string diff. endfunction: compare endclass: m2s_sb . if (act. expected). $cast(exp. exp.kind) && (act. it must then be integrated with the rest of the verification environment.addr == exp. Step 3: Integration Once the functionality of the scoreboard is defined. VMM Primer: Using the Data Stream Scoreboard 10 . The fact that the readback data is not valid when this callback method is invoked is a nonissue since it is not compared against expected values..kind == exp. diff). apb_rw act.addr). The stimulus transaction executed by the master can be extracted using either the pre_cycle() or post_cycle() callback method. end return (act. return act. $cast(act. Because the post_cycle() method will only be invoked when the transaction will have completed—and hence after the slave has responded—it will be too late to check the transaction that the slave sees against what the master is executing. vmm_data expected). The pre_cycle() method is thus the proper integration point. actual)..compare(exp.virtual function bit compare(vmm_data actual.

endfunction: new virtual task pre_cycle(apb_master xactor.. endclass: apb_master_to_sb class tb_env extends vmm_env. begin apb_master_to_sb cbs = new(this. . identifying the stream this response has been observed on... this... .. .mst = new("Master". ... tb_top. endtask: pre_cycle .. this.m2s = m2s.).. vmm_sb_ds::INPUT). . end ..out_chan). m2s_sb m2s = new.append_callback(cbs). this. It is only necessary to invoke the proper checking function. ref bit drop).sv . apb_rw cycle. this. VMM Primer: Using the Data Stream Scoreboard 11 . 0. virtual function build(). endclass: tb_env Integrating the checking part is easier since the response generator is implemented directly in the environment.. m2s_sb m2s. function new(m2s_sb m2s.insert(cycle...File: DataStream_SB/tb_env.m.. class apb_master_to_sb extends apb_master_cbs.gen.). . endfunction: build .. this..mst.m2s...m2s..

A unique stream identifier must be assigned to each stream. This is implemented using the Data Stream Scoreboard foundation class by defining three input streams..File: DataStream_SB/tb_env.get(tr). fork forever begin apb_rw tr.expect_in_order(tr.. virtual function start(). VMM Primer: Using the Data Stream Scoreboard 12 . end this. the stream identifier corresponds to the value of ‘j’ in the response generator thread. . class tb_env extends vmm_env.m2s. if (tr. end join_none end endfunction: start . // Poor random // strategy! . this.. The stream identifier is chosen to facilitate the association of a slave with its corresponding stream during integration..resp_chan[i]) begin int j = i. j)..resp_chan[j]. In this case.kind == apb_rw::READ) begin tr.resp_chan[j].. foreach (this.. this.sv ..data = $random()... endclass: tb_env Step 4: Slaves-to-Master The slaves-to-master scoreboard also requires three queues of input transactions. . one per slave.peek(tr).

define_stream(2.define_stream(0. super. "Slave 2". INPUT). this.sv .define_stream(2. this. this.File: DataStream_SB/tb_env.. endfunction: new . this.define_stream(0. "Slave 1". "Slave 0". endclass: s2m_sb . will be different from the one replied by the slaves. EXPECT). `include "vmm_sb. EXPECT). as reported by the master.. this.define_stream(0.sv" class s2m_sb extends vmm_sb_ds.sv" class s2m_sb extends vmm_sb_ds. `include "vmm_sb. The completed transactions. The simplest approach is to leave the response transaction as-is and only compare the data value of READ cycles. EXPECT)... EXPECT). "Master". VMM Primer: Using the Data Stream Scoreboard 13 . EXPECT).new("Master->Slave"). File: DataStream_SB/tb_env. INPUT).define_stream(1. "Slave 2"... "Master".. function new(). this. function new(). this.new("Slave->Master"). this. All of the remaining information is unmodified.. endfunction: new "Slave 0". INPUT). "Slave 1".define_stream(0. The only information that is transferred from a slave to the master is the read back data. super.define_stream(1.sv .

vmm_data expected). The completed transaction observed by the master can be extracted using the post_cycle() callback method and used to compare against expected transaction if a response was expected. actual). File: DataStream_SB/tb_env. this.. expected).sv ..s2m = s2m. s2m_sb s2m). class apb_master_to_sb extends apb_master_cbs. ref bit drop).m2s. apb_rw act. endfunction: new virtual task pre_cycle(apb_master xactor. s2m_sb s2m. m2s_sb m2s. Step 5: Integration Once the functionality of the scoreboard is defined. this. $cast(exp.m2s = m2s. exp.data == exp.virtual function bit compare(vmm_data actual. $cast(act.data). endfunction: compare endclass: s2m_sb . apb_rw cycle.kind == apb_rw::WRITE) return 1. vmm_sb_ds::INPUT). it must then be integrated with the rest of the verification environment. function new(m2s_sb m2s..insert(cycle. return (act. if (act. this.. VMM Primer: Using the Data Stream Scoreboard 14 .

this.. this.addr[9:8])).. endtask: post_cycle endclass: apb_master_to_sb class tb_env extends vmm_env. ...m.out_chan).kind == apb_rw::WRITE) return. . this.s2m. VMM Primer: Using the Data Stream Scoreboard 15 .mst = new("Master". m2s_sb m2s = new. .sv . . this. It is only necessary to invoke the insertion function.m2s..endtask: pre_cycle virtual task post_cycle(apb_master xactor. identifying the stream this response has been observed on.gen. File: DataStream_SB/tb_env.inp_stream_id(cycle.addr[9:8] == 2'b11) return..expect_in_order(cycle. if (cycle.. class tb_env extends vmm_env.append_callback(cbs)...s2m).... end .. virtual function build(). this. if (cycle. virtual function start(). tb_top. . apb_rw cycle). endclass: tb_env Integrating the checking part is again easier since the response generator is implemented directly in the environment. 0. endfunction: build . begin apb_master_to_sb cbs = new(this. s2m_sb s2m = new.mst..

endclass: tb_env Step 6: Congratulations! You have now completed the development and integration of not one but two scoreboards using the VMM Data Stream Scoreboard application package. verification environments or integrate a Register Abstraction Layer model.expect_in_order(tr. this. end this.. if (tr.resp_chan[i]) begin int j = i. You can verify the correct operation of the design by simulating the now self-checking verification environment. // Poor random // strategy! this.inp_stream_id(j)). end join_none end endfunction: start . .m2s.resp_chan[j]. fork forever begin apb_rw tr.. j).get(tr).peek(tr).foreach (this.s2m.data = $random(). vmm_sb_ds::INPUT.resp_chan[j].kind == apb_rw::READ) begin tr. VMM Primer: Using the Data Stream Scoreboard 16 . this.insert(tr. You may consider reading other publications in this series to learn how to write VMM-compliant command-layer transactors.

VMM Primer: Using the Register Abstraction Layer
Author: Janick Bergeron Updated By: John Choi Brett Kobernat Version 1.4 / March 27, 2008

1

VMM Primer: Using the Register Abstraction Layer 1

Introduction
The VMM Register Abstraction Layer is an application package that can be used to automate the creation of an object-oriented abstract model of the registers and memories inside a design. It also includes pre-defined tests to verify the correct implementation of the registers and memories, as specified as well as a functional coverage model to ensure that every bit of every register has been exercised. This primer is designed to teach how to create a RAL model of the registers and memories in a design, how to integrate this model in a verification environment and how to verify the implementation of those registers and memories using the pre-defined tests. It will also show how the RAL model can be used to model the configuration and DUT driver code so it can be reusable in a system-level environment. Finally, it shows how the RAL model is used to implement additional functional tests. Other primers cover other aspects of developing VMM-compliant verification assets, such as transactors, generators, assertions and verification environments. This primer assumes that you are familiar with VMM. If you need to ramp-up on VMM itself, you should read the other primers in this series, such as “Writing a Command-Layer Command Transactor”. The DUT used in this primer was selected for its simplicity. As a result, it does not require the use of many elements of the VMM Register Abstraction Layer application package. The DUT has enough features to show the steps needed to create a RAL model to verify the design.

VMM Primer: Using the Register Abstraction Layer 2

This document is written in the same order you would develop a RAL model, integrate it in a verification environment and verify your design. As such, you should read it in a sequential fashion. You can use the same sequence to create your own RAL model and use it to verify your design.

The DUT
The Design Under Test used in this primer is an AMBA™ Peripheral Bus (APB) slave device. It is a simple single-master device with a few registers and a memory, as described in Table 1. The data bus is 32-bit wide. Table 1 Address Map
Name CHIP_ID STATUS MASK COUNTERS DMA RAM

Address 0x0000 0x0010 0x0014 0x1000-0x13FF 0x2000-0x2FFF

Table 2 through Table 5 define the various fields found in each registers. “RW” indicates a field that can be read and written by the firmware. “RO” indicates a field that can be read but not written by the firmware. “W1C” indicates a field that can be read and written by the firmware, but writing a ‘0’ has no effect and writing a ‘1’ clears the corresponding bit if it is set.

VMM Primer: Using the Register Abstraction Layer 3

Table 2
Bits

CHIP_ID Register
Reserved 31-28 RO 0x0 PRODUCT_ID 27-16 RO 0x176 CHIP_ID 15-8 RO 0x5A REVISION_ID 7-0 RO 0x03

Field

Access Reset

Table 3
Bits

STATUS Register
Reserved 31-17 RO 0x0000 READY 16 W1C 0x0 Reserved 15-5 RO 0x0000 MODE 4-2 RW 0x0 TXEN 1 RW 0x0 BUSY 0 RO 0x0

Field

Access Reset

Table 4
Bits

MASK Register
Reserved 31-17 RO 0x0000 READY 16 RW 0x0 Reserved 15-0 RO 0x0000

Field

Access Reset

These registers are statistic counters that are incremented by the DUT under the appropriate circumstance. There are 256 such counters. Table 5
Bits Access Reset

COUNTER Registers
COUNT 31-0 RO 0x0000

Field

VMM Primer: Using the Register Abstraction Layer 4

There are two possibilities for interpreting these two addresses. the address space of the DUT would look like Table 7. MASK[31:16] 24’h000000. If the address is specified using a BYTE granularity. Table 6 BYTE Address Granularity Data (32 bits) STATUS[31:0] MASK[7:0]. STATUS[31:8] MASK[15:0]. VMM Primer: Using the Register Abstraction Layer 5 . The address granularity refers to the minimum number of bytes that can be uniquely addressed. as defined in Table 1. the address space of the DUT would look like Table 6. STATUS[31:16] MASK[23:0]. MASK[31:24] Address 0x0010 0x0011 0x0012 0x0013 0x0014 0x0015 0x0016 0x0017 If the address is specified using DWORD (32 bits) granularity. MASK[31:8] 16’h0000. assuming it is LITTLE_ENDIAN.Address Granularity It is important to understand the address granularity of the DUT. STATUS[31:24] MASK[31:0] 8’h00. Consider the address of the STATUS and MASK registers.

Table 7 DWORD Address Granularity Data (32 bits) STATUS[31:0] Unspecified Unspecified Unspecified MASK[31:0] Unspecified Unspecified Unspecified Address 0x0010 0x0011 0x0012 0x0013 0x0014 0x0015 0x0016 0x0017 Because the data bus is 32 bits. Its address space is thus illustrated by Table 8 and effectively implements a DWORD granularity. Table 8 Shifter BYTE Address Granularity Data (32 bits) STATUS[31:0] MASK[31:0] Address[15:2] 0x0004 0x0005 The DUT used in this primer uses a BYTE granularity but does not implement the two least significant bits. VMM Primer: Using the Register Abstraction Layer 6 . This effectively shifts the address value left by two bits and creates a DWORD granularity. a design with BYTE granularity will often not implement the least significant two bits of the address bus. as illustrated in Table 8.

The smallest unit that can be used to represent a design in a RALF description is the block. The name of the block should be relevant and somewhat unique. } The registers are declared in the block using a register description. The address offset of each register within the blocks is also specified at the same time. being identical and located at consecutive addresses can be specified using a register array. It can be captured by hand from the specification above or it could be automatically generated from a suitably formatted specification document. The bytes attribute defines the width of the physical data path when accessing registers and memories in the block. Do not name your block “DUT”—that is simply begging to collide with the name of another block or system similarly badly named.Step 1: The RALF File The Register Abstraction Layer Format (RALF) file is a specification of the host-accessible registers and memories available in your design. File: RAL/slave. . such as an Excel spreadsheet. The COUNTERS registers.ralf block slave { bytes 4.. VMM Primer: Using the Register Abstraction Layer 7 . a DWORD address granularity will be assumed. Since the data path of the DUT is 32-bits.. This will allow the RALF description of the block to be included in a RALF description of the system that instantiates it. RAL assumes that the address granularity is equal to the width of the data path.

not the documented BYTE granularity address value. } . File: RAL/slave. } register .File: RAL/slave.e.. it is assumed to be incremented by one from the previous register address offset. { CHIP_ID @’h0000 { STATUS @’h0004 { MASK @’h0005 { VMM Primer: Using the Register Abstraction Layer 8 . } { CHIP_ID @’h0000 { STATUS @’h0010 { MASK @’h0014 { COUNTERS[256] @’h1000 { If no address offset is specified for a register.. } register ... } register ... To avoid this problem.. register . } register .. the DWORD granularity address values (i. Because the block is assumed to have a DWORD granularity.. register .ralf block slave bytes 4. In some cases. the shifting may have to be undone in the translation transactor (see Step 4)... This creates a problem for the COUNTERS register array as the address of each subsequent register in the array is assumed to be incremented by one.... } register . the shifted values) should be specified. the address offset increment refers to the shifted address value...ralf block slave bytes 4.

} register COUNTERS[256] @’h0400 { .. VMM Primer: Using the Register Abstraction Layer 9 . File: RAL/slave. Fields are assumed to be contiguous. and justified in the least significant bits. reset ’h5A. } The fields inside each register are then specified using a field description. } field PRODUCT_ID { bits 10. } field CHIP_ID { bits 8. access ro.ralf block slave { bytes 4.. A field is the smallest of information and describes a set of consecutive bits with identical behavior. It is not necessary to specify unused or reserved bits if they are read-only and read as zeroes. } .. reset ’h176. access ro. Fields can be positioned at a specific bit offset within a register by specifying the bit number in the register that corresponds to the least significant bit of the field. } } register STATUS @’h0004 { field BUSY (BUSY) { bits 1. reset ’h03. register CHIP_ID @’h0000 { field REVISION_ID { bits 8.. The last remaining element to be specified is the DMA RAM. access ro. access ro.

reset ’h0. reset ’h0. access rw. access rw. } } register COUNTERS[256] @’h0400 { field value { bits 32. reset ’h0. } } memory DMA_RAM (DMA) @’h0800 { size 1k. access rw. reset 3’h0. } } register MASK @’h0005 { field READY (RDY_MSK) @16 { bits 1. backdoor access code for those registers and memories can be automatically added to the RAL model. access ru. access rw. } field MODE (MODE) { bits 3. } } When (HDL_PATH) is specified for registers and memories. The backdoor access code allows the model to directly access registers and memories without VMM Primer: Using the Register Abstraction Layer 10 . access w1c. reset ’h0. } field TXEN (TXEN) { bits 1. } field READY (RDY) @16 { bits 1.reset ’h0. bits 32.

.. } } ... } field TXEN (TXEN) { . memory DMA_RAM (DMA) @’h0800 { . File: RAL/slave.... } field READY (RDY) @16 { ... register CHIP_ID @’h0000 { . } register STATUS @’h0004 { field BUSY (BUSY) { ...going through the physical interface bus functional model.ralf block slave { bytes 4. } field MODE (MODE) { .. Therefore... } } VMM Primer: Using the Register Abstraction Layer 11 . the registers and the memories can be read or written without penalty of simulation cycles. } } register MASK @’h0005 { field READY (RDY_MSK) @16 { ...

sv: Command: % ralgen –b –l sv –t slave slave. rand vmm_ral_field READY. rand vmm_ral_field MODE.... rand vmm_ral_field PRODUCT_ID.ralf The generated code is not designed to be read or subsequently manually modified. the ralgen script is used to generate the corresponding RAL model. class ral_reg_slave_STATUS extends vmm_ral_reg. rand vmm_ral_field READY. endclass : ral_reg_slave_CHIP_ID .. However... rand vmm_ral_field REVISION_ID. VMM Primer: Using the Register Abstraction Layer 12 .. The following generated RAL model corresponds to the documented output. the structure of the generated RAL model will be outlined to demonstrate how it mirrors the RALF description.. class ral_reg_slave_MASK extends vmm_ral_reg.. RAL/ral_slave. . . The following command will generate a SystemVerilog RAL model of the slave block in the file ral_slave. endclass : ral_reg_slave_STATUS .. rand vmm_ral_field CHIP_ID. All other lines not shown are not explicitly documented and should not be relied upon.sv class ral_reg_slave_CHIP_ID extends vmm_ral_reg.Step 2: Model Generation Once the registers and memories have been specified in a RALF file. . rand vmm_ral_field TXEN. rand vmm_ral_field BUSY.

COUNTERS_value[256]. rand vmm_ral_field CHIP_ID_CHIP_ID.STATUS_BUSY.STATUS_MODE. rand ral_reg_slave_COUNTERS COUNTERS[256]. rand ral_reg_slave_CHIP_ID CHIP_ID. vmm_ral_field BUSY. vmm_ral_field MODE.sv . rand vmm_ral_field REVISION_ID. VMM Primer: Using the Register Abstraction Layer 13 . function new(int cover_on = vmm_ral::NO_COVERAGE.. The block abstraction class contains a property for each register in the block that refers to an abstraction class for that register. rand vmm_ral_field value. rand rand rand rand rand ral_reg_slave_STATUS STATUS. . rand vmm_ral_field MASK_READY. endfunction: new endclass: ral_block_slave The first thing to notice about the RAL model is the abstraction class that corresponds to the block... endclass: ral_reg_slave_COUNTERS class ral_block_slave extends vmm_ral_block. The register array is modeled using an array of abstraction classes. rand vmm_ral_field PRODUCT_ID... rand ral_mem_slave_DMA_RAM DMA_RAM. .CHIP_ID_PRODUCT_ID. . class ral_block_slave extends vmm_ral_block. vmm_ral_field TXEN. ).STATUS_TXEN. RAL/ral_slave.endclass : ral_reg_slave_MASK class ral_reg_slave_COUNTERS extends vmm_ral_reg. rand vmm_ral_field value[256].. rand ral_reg_slave_MASK MASK. vmm_ral_field STATUS_READY.. rand ral_reg_slave_CHIP_ID CHIP_ID.CHIP_ID_REVISION_ID..

CHIP_ID_PRODUCT_ID. There is also a property for each field in a register in the block abstraction class. rand ral_reg_slave_CHIP_ID CHIP_ID. For example.CHIP_ID_REVISION_ID. the register abstraction class for a register contains a property for each field it contains. ... Similarly. This allows fields to be referenced without regards to their location in a specific register. class ral_block_slave extends vmm_ral_block. rand vmm_ral_field REVISION_ID.. rand ral_mem_slave_DMA_RAM DMA_RAM. there is no class property named CHIP_ID for the field in the block abstraction class... endclass : ral_reg_slave_CHIP_ID . RAL/ral_slave.. . rand ral_reg_slave_COUNTERS COUNTERS[256]. rand vmm_ral_field CHIP_ID_CHIP_ID. .. endclass: ral_block_slave Similarly. because there are two fields named READY in different registers. endclass: ral_block_slave VMM Primer: Using the Register Abstraction Layer 14 . rand vmm_ral_field PRODUCT_ID. rand vmm_ral_field PRODUCT_ID.. thus allowing them to be relocated without having to modify the code that use them.... rand vmm_ral_field REVISION_ID. because the field named CHIP_ID in the register named CHIP_ID conflicts with the register of the same name. . However.. rand ral_reg_slave_MASK MASK.sv class ral_reg_slave_CHIP_ID extends vmm_ral_reg... rand ral_reg_slave_STATUS STATUS.... . this requires that the field name be unique within the block. there are no class properties of that name in the block abstraction class. rand vmm_ral_field CHIP_ID. .

field TXEN (TXEN) { bits 1... constraint status_reg_valid { (MODE. However. Each class contains virtual task read() and virtual task write() for backdoor read and write access. access rw..Every class property in the abstraction classes has the rand attribute. File: RAL/slave.. a backdoor access class is automatically generated. access rw. reset 3’h0.value != 1’b1. reset ’h0. } For every register and memory with (HDL_PATH) specified... } } . VMM Primer: Using the Register Abstraction Layer 15 . constraint valid {} } field MODE (MODE) { bits 1.. register STATUS @’h0004 { . This allows the content of a RAL model to be randomized. this attribute is turned off by default in all fields unless a constraint—even an empty one—has been specified for that field.. Both tasks utilize the compiler directive ‘SLAVE_TOP_PATH to define the hierarchical path to registers in the DUT.ralf block slave { .value == 3’h5) -> TXEN. } } . constraint valid { value < 3’h6.

‘SLAVE_TOP_PATH. .BUSY. end status = vmm_rw::IS_OK.MODE. data[1:1] = ‘SLAVE_TOP_PATH. begin ‘SLAVE_TOP_PATH.TXEN..sv .RDY = data[16:16]. Lastly. File: RAL/ral_slave.. endfunction: new endclass: ral_block_slave VMM Primer: Using the Register Abstraction Layer 16 . .. ‘SLAVE_TOP_PATH..RDY. class ral_reg_slave_STATUS_bkdr extends vmm_ral_reg_backdoor. By default.). no functional coverage model is included. function new(int cover_on = vmm_ral::NO_COVERAGE.. . end status = vmm_rw::IS_OK. vitual task read(output vmm_rw::status_e status.).. data[4:2] = ‘SLAVE_TOP_PATH. class ral_block_slave extends vmm_ral_block... data[16:16] = ‘SLAVE_TOP_PATH. data[0:0] = ‘SLAVE_TOP_PATH...MODE = data[4:2].. . the constructor for the block abstraction class has a default argument vmm_ral::NO_COVERAGE. endtask vitual task write(output vmm_rw::status_e status..)..File: RAL/ral_slave. endtask endclass .sv . begin data = ‘VMM_RAL_DATA_WIDTH’h0... ..TXEN = data[1:1].

pwrite ).. . .apb_wdata . (apb0.psel )..apb_sel . VMM Primer: Using the Register Abstraction Layer 17 .apb_write . Only block and system abstraction classes are intended as end-user RAL models. apb_if apb0(.prdata[31:0]). (apb0.paddr[15:2] ).apb_addr . The DUT and the relevant interfaces are instantiated in a top-level module (Rule 4-13). That is because they are not documented and not intended to be used directly... File: RAL/tb_top... (apb0.sv module tb_top. Notice how the two least significant bits of the address are not used by the DUT to implement the BYTE granularity with a DWORD data bus.apb_enable . The clock generator ensures that no clock edges will occur at time zero (Rule 4-16).apb_rdata .). slave_ip dut_slv(.pwdata[31:0]). Step 3: Top-Level Module The DUT must be instantiated in a top-level module and connected to protocol-specific interfaces corresponding to the command-level transactors that drive and monitor the DUT’s signals.Note that the constructors for the other abstraction classes are not shown. endmodule: tb_top (apb0.penable ). The connection to the DUT pins are specified using a hierarchical reference to the wires in the interface instance. This top-level module also contains the clock generators (Rule 4-15) and reset signal. (apb0. using the bit type (Rule 4-17).).. (apb0..

bit clk = 0. class apb_rw_xlate extends vmm_rw_xactor.prdata[31:0]). It issues generic read and write transaction requests at specific addresses but these generic transactions need to be executed on whatever physical interface is provided by the DUT.apb_addr . apb_if apb0(clk).apb_rdata . slave_ip dut_slv(.apb_wdata . (apb0.rst always #10 clk = ~clk. (clk).pwdata[31:0]). (apb0.. (apb0.sv . endmodule: tb_top (apb0.apb_write . .sv module tb_top..psel ).apb_sel ..penable ). (rst)).pwrite ).apb_enable .clk .File: RAL/tb_top. (apb0.paddr[15:2] ). The translation must be accomplished in a user-defined extension of the vmm_rw_xactor::execute_single() task in a transactor extended from the vmm_rw_xactor base class—which is itself based on the vmm_xactor base class. File: RAL/apb_rw_xlate. Step 4: Physical Interface A RAL model is not aware of the physical interface used to access the registers and memories.. bit rst = 0. VMM Primer: Using the Register Abstraction Layer 18 . This task can use any transactor to execute the requested generic transactions. (apb0.

super. class apb_rw_xlate extends vmm_rw_xactor.. virtual task execute_single(vmm_rw_access tr).new("APB RAL Master". .. apb_master bfm.. File: RAL/apb_rw_xlate.. endfunction: new virtual function void start_xactor()..sv `include "apb_master. virtual task execute_single(vmm_rw_access tr).bfm. apb_master bfm). function new(string inst. endtask: execute_single endclass: apb_rw_xlate The first thing that is needed is a command-level transactor to execute the read and write transactions. endtask: execute_single endclass: apb_rw_xlate The generic transaction is then translated into an equivalent transaction suitable for the command-level transactor used.. To ensure that the command-level transactor is started when the translation transactor is started. . endfunction ..start_xactor(). Pass a reference to an instance of a suitable command-level transactor via the constructor argument.. this. its start_xactor() method must be called in the extension of the translation transactor’s start_xactor() own method.. int unsigned stream_id. stream_id).sv" ..bfm = bfm. Note that it may be necessary to adjust the address specified by the RAL VMM Primer: Using the Register Abstraction Layer 19 . this.start_xactor(). inst.. super.

inst.sv" `include "vmm_ral. endfunction virtual task execute_single(vmm_rw_access tr). this. function new(string inst.sv" class apb_rw_xlate extends vmm_rw_xactor.start_xactor(). apb_master bfm.start_xactor().sv `include "apb_master.kind = apb_rw::READ. apb_rw cyc = new. cyc. apb_master bfm). super. int unsigned stream_id. super.bfm.addr = {tr.kind == vmm_rw::WRITE) begin // Write cycle cyc.addr.bfm = bfm. end else begin // Read cycle cyc. because paddr[1:0] is not used by the DUT (because it uses BYTE granularity addressing with a DWORD data bus). VMM Primer: Using the Register Abstraction Layer 20 .data = tr. the status and the read-back data (if applicable) is annotated onto the generic transaction before the execute_single() method is allowed to return.new("APB RAL Master".data. File: RAL/apb_rw_xlate. endfunction: new virtual function void start_xactor(). In our case.model in the generic transaction to the physical address used by the physical protocol.kind = apb_rw::WRITE. this. // DUT uses BYTE granularity addresses // but with a DWORD datapath cyc. 2'b00}. Once the transaction has been executed according to the translator execution model. stream_id). if (tr. you must shift the specified address into paddr[31:2].

if (tr.status = vmm_rw::IS_OK. tr.bfm. single domain RAL physical access BFM. Command % vmmgen –l sv The relevant templates for writing translation transactors are: • • RAL physical access BFM. VMM Primer: Using the Register Abstraction Layer 21 .status = vmm_rw::ERROR. multiplexed domains Note that the menu number used to select the appropriate template may differ from the number in the above list.in_chan. assert(!($isunknown(tr. end endtask: execute_single endclass: apb_rw_xlate The creation of the translation transactor can be simplified by using the template provided by the vmmgen tool.end this. The style used to implement the translation transactor shown in this primer is provided by the “multiplexed domains” template.data))) tr. else begin ‘vmm_error(log.put(cyc).data.kind == vmm_rw::READ) begin tr.data = cyc. "Data Contains X Value").

.ral.ral_model).Step 5: Verification Environment A RAL model must be used with a verification environment class extended from the vmm_ral_env base class. The RAL model is instantiated in the environment constructor then registered with the base class using the ral. DUT configuration can be randomized in the gen_cfg() step.. File: RAL/tb_env.set_model() method.sv `ifndef TB_ENV__SV `define TB_ENV__SV `include `include `include `include .. RAL model randomization has been enabled.set_model(this. Therefore... ...sv" "vmm_ral. endfunction: new . . "vmm. endclass: tb_env `endif Via constraint definitions in the RALF description..sv" "apb.sv" "ral_slave. The RAL model is instantiated in the constructor so it can be used to generate a suitable configuration in the gen_cfg() step.sv" class tb_env extends vmm_ral_env. ral_model = new(vmm_ral::NO_COVERAGE).. function new().. VMM Primer: Using the Register Abstraction Layer 22 .. super. ral_block_slave ral_model.

ral_model. apb_master mst.sv" "apb. File: RAL/tb_env. ..sv" "ral_slave. The transactors will be automatically started by RAL but it does not hurt to start them again in the extension of the vmm_env::start() method (Rule 4-41). 4-35).sv" class tb_env extends vmm_ral_env. endfunction: gen_cfg . class tb_env extends vmm_ral_env.sv `ifndef TB_ENV__SV `define TB_ENV__SV `include `include `include `include `include "vmm.... VMM Primer: Using the Register Abstraction Layer 23 .sv" "vmm_ral. endclass: tb_env The translation transactor and its required command-layer transactor are constructed in the extension of the vmm_ral_env::build() method (Rule 4-34.sv" "apb_rw_xlate. The translation transactor must then be registered using the ral... if (!(this.randomize() )) `vmm_error(this. function new()... . .... function void gen_cfg(). ral_model = new(vmm_ral::NO_COVERAGE).add_xactor() method.log. .. ral_block_slave ral_model. apb_rw_xlate ral2apb. "ral_model could not be \n randomized").File: RAL/tb_env.sv .

super.. ral_model = new(vmm_ral::NO_COVERAGE). This task will be called by the default implementation of the vmm_ral_env::reset_dut() method.sv" "apb.apb0). endfunction: new virtual function void build(). endclass: tb_env `endif Instead of specifying how to reset the DUT in the extension of the vmm_ral_env::reset_dut() method. this.sv" "apb_rw_xlate.mst). endfunction: build .sv" class tb_env extends vmm_ral_env..sv `ifndef TB_ENV__SV `define TB_ENV__SV `include `include `include `include `include "vmm. this.build(). 0..ral2apb = new("APB".add_xactor(this. it is specified in the extension of the vmm_ral_env::hw_reset() task. 0.ral2apb).ral.set_model(this. .mst = new("APB".. File: RAL/tb_env.super.ral. apb_master mst. . this. tb_top. VMM Primer: Using the Register Abstraction Layer 24 .ral_model). apb_rw_xlate ral2apb.sv" "ral_slave. this. function new().sv" "vmm_ral.. thus satisfying Rule 4-30.. ral_block_slave ral_model.

.mst). . File: RAL/tb_env. 0.ral2apb = new("APB". virtual task cfg_dut(). vmm_ral::BACKDOOR).. Recall that the RAL model was randomized in the gen_cfg() step. endfunction: new virtual function void build(). endfunction: cfg_dut . class tb_env extends vmm_ral_env.ral. this.... endfunction: build virtual task hw_reset(). repeat (3) @ (negedge tb_top.clk). in the cfg_dut() step.ral_model).add_xactor(this. tb_top. this.apb0).rst <= 1'b1. update the DUT to reflect the randomized RAL model values.clk).. tb_top.mst = new("APB". super. . endclass: tb_env `endif Finally. endtask: hw_reset . this.ral2apb).. 0.set_model(this.build(). endclass: tb_env VMM Primer: Using the Register Abstraction Layer 25 .. tb_top. ral_model. this. repeat (3) @ (negedge tb_top.rst <= 1'b0.update(status..super.ral.sv ..

dut tb_top. VMM Primer: Using the Register Abstraction Layer 26 .. or the integration of the two will be identified by this simple test. Thus they can be modified to meet to particular needs of your design or they can be used as a source of inspiration for writing other RAL-based tests. This is done by the file ral_env. mem_access.svh `define RAL_TB_ENV tb_env `include "tb_env.svh in the current working directory and defining the `RAL_TB_ENV macro respectively./apb +define+SLAVE_TOP_PATH=tb_top.sv Other tests are provided with RAL. Notice the compiler directive SLAVE_TOP_PATH which will be needed for backdoor access codes used in mem_walk. Many of the problems with the DUT.sv \ $VCS_HOME/etc/vmm/sv/RAL/tests/hw_reset. File: RAL/ral_env. The RAL User Guide details the functionality of each test. the RAL model.Step 6: The Pre-Defined Tests Before you can run one of the pre-defined tests. They can all be found in the $VCS_HOME/etc/vmm/sv/RAL/tests directory. you must specify which files must be included in the simulation first and what is the name of the verification environment class.sv" You are now ready to execute any of the pre-defined tests! It is best to start with the simplest test: applying hardware reset then reading all of the registers to verify their reset values. etc. Command % vcs –R –sverilog –ntb_opts rvm+dtm \ +incdir+. Note that the pre-defined tests are available as unencrypted source code.

The coverage model can be enabled by passing the appropriate argument to the RAL model constructor. } . ..weight = 4. This will enable VCS to create and collect a coverage database. Command % ralgen –c b –b –l sv –t slave slave. wildcard bins bit_0_rd_as_1 = {2'b11}. class ral_cvr_reg_slave_STATUS.Step 7: Coverage Model A functional coverage model can be added to the RAL model by using an option in the ralgen script.ralf The generated coverage model can be large (4 bins per bit in every field).sv . option. wildcard bins bit_0_rd_as_0 = {2'b01}... VMM Primer: Using the Register Abstraction Layer 27 . A report can then be generated using utilities such as the Unified Report Generator.... wildcard bins bit_0_wr_as_1 = {2'b10}.. File: RAL/ral_slave. is_read} { wildcard bins bit_0_wr_as_0 = {2'b00}.. When functional coverage is added it should be pruned to improve memory usage and run-time performance once the register implementation has been verified and the coverage model filled to satisfaction. TXEN: coverpoint {data[1:1]. endclass .

This can be implemented by creating a user defined test to achieve the desired DUT configuration. ral_model = new(vmm_ral::REG_BITS). The RAL model provides easy access to write the appropriate values in the DUT registers through functions and tasks. endfunction: new .ral.ral_model. Step 8: Congratulations! You have now completed the integration of a RAL model with a design and were able to verify the correct implementation of all registers and memories! A specific DUT configuration may be desirable to complete functional coverage.. 1'h1).File: RAL/tb_env.peek(status. env.write(status.ral_model)... super.MODE. ..MASK_READY.. endclass . // writing registers with frontdoor access env. . // reading registers with backdoor access env.write(status.. function new(). .ral_model.. 3'h3).TXEN.. . . VMM Primer: Using the Register Abstraction Layer 28 .set_model(this. env...cfg_dut(). $psprintf(".ral_model. `vmm_note(log.sv class tb_env extends vmm_ral_env. ".. 1'h1).MODE..ral_model. . reg_value))... File: RAL/user_test. reg_value).sv program user_test. env.....write(status.

". VMM Primer: Using the Register Abstraction Layer 29 . env.ral_model...TXEN.peek(status. reg_value).. ". functional-layer transactors or verification environments.MASK_READY.env. . $psprintf(".. `vmm_note(log. reg_value)). endprogram There are many other VMM Primers that cover topics such as how to write VMM-compliant command-layer transactors. `vmm_note(log. $psprintf(".ral_model. reg_value).peek(status.. reg_value))..

VMM Primer: Using the Register Abstraction Layer 30 .