VMM PrimersVersion C-2009.06 June 2009 Comments? E-mail your comments about this manual to:
[email protected]. 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.VMM Primer: Composing Environments Author: Janick Bergeron Version 1. 2008 1 VMM Primer: Composing Environments 1 . 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. Despite being a fairly complex design. 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. verification environments and the Register Abstraction Layer package. This primer assumes that you are familiar with VMM.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. This primer is designed to learn how to create a block-level environment that will be reusable in a system-level verification environment. The design used to illustrate how to use the VMM Environment Composition application package is the OpenCore Ethernet Media Access Controller (MAC). If you need to ramp-up on VMM itself. you should read the other primers in this series. this primer focuses on a single aspect of its functionality: the verification of its transmission path. The system-level design will be a simple composition of two MAC blocks. Other primers cover other aspects of developing VMMcompliant verification assets. such as transactors. They have to be structured and architected to make then reusable. VMM Primer: Composing Environments 3 . Also. It is important to identify which portion(s) of the block-level environment that would be reusable in a system-level context.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. First. The DMA memory. not being mapped in the DUT address space. As such. The firmware emulation transactor configures and controls the registers inside the design through a RAL model. You can use the same sequence to create your own reusable sub-environments. The detailed operation of the firmware emulation layer to successfully transmit a frame is described in the Memory Allocation Manager Primer. is not included in the RAL model. low-level random stimulus at the block level may need to carry well-formed higherlevel information at the system-level. Figure 1 on page 4 shows the structure of the verification environment surrounding the Ethernet MAC block. 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. The firmware emulation transactor accesses the DMA memory directly through backdoor read()/write() methods provided by the RAM model itself. you should read it in a sequential fashion. The same information could have been extracted from the active MII receiver transactor. 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. It will be up to the system-level VMM Primer: Composing Environments 4 .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. This will enable the firmware layer to be fed from a high-level protocol stimulus source. A passive monitor is used to extract information from the MII interface. 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. should the need arise. 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 reusable sub-environment from Figure 1 is used twice with slightly different configurations.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. 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 system-level environment used in this primer is shown in Figure 2. is encapsulated in a class extended from the vmm_subenv base class.. oc_eth_env_cfg cfg.. endfunction: new . called a sub-environment.new("OcEth SubEnv"... inst. endclass: oc_eth_env_cfg class oc_eth_env extends vmm_subenv. The constructor arguments must include: • • • • An instance name. .. endclass: oc_eth_subenv VMM Primer: Composing Environments 6 .. eth_frame_channel tx_chan. function new(string inst. vmm_consensus end_test.).Step 2: Encapsulation The portion of the block level environment that will be reusable..passive mii_tx. virtual mii_if.. A vmm_consensus reference.. end_test). . File: SubEnv/eth_subenv. All virtual interfaces and channels that cross the sub-environment boundary. .sv class oc_eth_subenv_cfg. . super. A sub-environment configuration descriptor.. . the reference will be used as-is and assumed to be properly fed from some source external to the sub-environment.. .. These logical interfaces must also be provided as constructor arguments and include: VMM Primer: Composing Environments 7 . if (tx_chan == null) begin this. eth_frame_atomic_gen src. endclass: oc_eth_subenv The sub-environment has additional logical interfaces required to properly interact with other components of the overall environment.out_chan... function new(string inst.passive mii_tx.The reference to the tx_chan argument is optional. endclass: oc_eth_subenv_cfg class oc_eth_subenv extends vmm_subenv. virtual mii_if.. . inst. vmm_consensus end_test. File: SubEnv/eth_subenv...sv class oc_eth_subenv_cfg. super. inst).new("OcEth SubEnv". end_test)... If it is not null. end . . oc_eth_env_cfg cfg. . tx_chan = this. eth_frame_channel tx_chan. . it will be assumed to not cross the sub-environment boundary and the generator will be internally allocated and connected.. .). If set to null..src = new("OcEth SubEnv Src"...src.. endfunction: new .. . end .sv class oc_eth_subenv extends vmm_subenv. . super. But such is not the case here..out_chan. • • File: SubEnv/eth_subenv. it my be managed by a shared resource manager. eth_frame_channel tx_chan.src.. endfunction: new .new("OcEth SubEnv". ral_block_oc_ethernet ral. endclass: oc_eth_subenv VMM Primer: Composing Environments 8 . tx_chan = this. inst)... . end_test). function new(string inst. vmm_mam dma_mam). vmm_consensus end_test. oc_eth_env_cfg cfg.src = new("OcEth SubEnv Src".• A reference to a RAL model for the block. . virtual mii_if...passive mii_tx. inst.. it could be accessed through the RAL model for that block. A reference to the memory allocation manager for the system memory. If the system memory were mapped into the space of the block... wb_ram dma_ram.. eth_frame_atomic_gen src. . Since the system memory is a potentially shared resource. This will enable the firmware emulator to configure the block regardless of the physical interface or base address it will be located under.. A reference to system memory for backdoor read/write. if (tx_chan == null) begin this. wb_ram ram.. test_cfg cfg. this. .ral_model). this.ral_model. .set_model(this. rand oc_eth_subenv_cfg eth.. File: SubEnv/blk_env.ral....ram. virtual function build().build(). this.dma_mam). this. super.sv class test_cfg. this.. ral_block_oc_ethernet ral_model.ral_model = new().cfg.eth_sub this. this. endclass: test_cfg .new().eth.. null.passive.. endclass: blk_env VMM Primer: Composing Environments 9 . oc_eth_subenv eth. function new().. this. .cfg = new.The subenvironment is then instantiated in the block-level environment and constructed. this. class blk_env extends vmm_ral_env. endfunction: new ... super.. blk_top. wm_mam dma_mam.. endfunction: build .eth = new("Eth".end_vote. in the build() step.. like any other transactor.mii.. .. . sv class oc_eth_subenv_cfg. rand bit [ 7:0] n_tx_bd. For example. File: SubEnv/eth_subenv. rand bit [15:0] max_frame_len.. . endclass: oc_eth_subenv_cfg Structural configuration is implemented in the constructor.sv class oc_eth_subenv_cfg.Step 3: Configuration The sub-environment configuration descriptor must contain all information necessary to configure the elements in the subenvironment.. rand bit [47:0] dut_addr. . rand bit [ 7:0] n_tx_bd. File: SubEnv/eth_subenv. rand bit [15:0] max_frame_len. It can be determined by the value of the virtual interfaces or channels. endclass: oc_eth_subenv_cfg class oc_eth_subenv extends vmm_subenv. rand int unsigned run_for_n_frames. rand mii_cfg mii. Structural configuration also includes appropriately configuring the transactors instantiated during construction.. rand int unsigned run_for_n_frames. the optional reference to a channel controls whether or not a generator is instantiated inside the sub-environment. VMM Primer: Composing Environments 10 . This was implemented as part of Step 2.. rand bit [47:0] dut_addr. rand mii_cfg mii. randomized_obj.passive mii_tx.src = this.ral. super. this.randomized_obj. inst). vmm_mam dma_mam).cfg.. virtual mii_if.cfg. end_test).mon = new(inst.src. this. this. eth_frame_channel tx_chan. ral_block_oc_ethernet ral.src. endfunction: new . this.mii. tx_chan. this. this..frmwr = new(inst. tx_chan = this. this. eth_frame_atomic_gen src.stop_after_n_insts = this.src = new("OcEth SubEnv Src"... 0. function new(string inst. vmm_consensus end_test..rand_mode(0). end . if (tx_chan == null) begin this.. frmwr_emulator frmwr..out_chan. this.. this.run_for_n_frames. this. wb_ram dma_ram. .src. 0.mii_sigs). mii_monitor mon. this.new("OcEth SubEnv". endclass: oc_eth_subenv Functional configuration is implemented in the user-defined vmm_subenv::configure() method.dut_addr. inst.dma_mam)..cfg.dma_ram.src. this. This step configures the DUT to match the configuration of the sub-environment. VMM Primer: Composing Environments 11 . oc_eth_env_cfg cfg.src.sb.cfg. . endclass: oc_eth_subenv The call to super..configured().log. 1)..set(0). . The configure() method of the sub-environment is then invoked from at the cfg_dut() step. end super... vmm_rw::status_e status..set(1). . if (status !== vmm_rw::IS_OK) begin `vmm_error(ral.File: SubEnv/eth_subenv. endtask: cfg_dut . ..write(status. File: SubEnv/blk_env. endtask: configure . virtual task cfg_dut(). this.configured() at the end of the configure() implements an error detection mechanism to ensure that a subenvironment is completely configured before it is started...sv class oc_eth_subenv extends vmm_subenv.cfg_dut().HUGEN.configure(). ral.TXEN. task configure(). ral. endclass: blk_env VMM Primer: Composing Environments 12 .sv class blk_env extends vmm_ral_env.).. super.. ral.. .FULLD.eth. File: SubEnv/eth_subenv. .min_addr == 32'h0000_0000...max_addr == 32'hFFFF_FFFF.. mam.max_addr.sv class test_cfg.The configuration of the Memory Allocation Manager instance that is responsible for managing the system memory must match the configuration of that memory. .. constraint test_cfg_valid { ram.end_offset == ram. File: SubEnv/blk_env..start_offset == ram.. .. mam.sv class oc_eth_subenv extends vmm_subenv. rand wb_slave_cfg rand vmm_mam_cfg ram. ram.min_addr.n_bytes == 4. the vmm_subenv::start() method must be extended to start all transactors and ancillary threads in the sub-environment. First. VMM Primer: Composing Environments 13 . ram. endclass: test_cfg Step 4: Execution Sequence The sub-environment must then be integrated in the block-level environment’s execution sequence. } .. This can be implemented by constraining the configuration of one to match the configuration of the other. mam.port_size == wb_cfg::DWORD. mam. cfg. endclass: oc_eth_subenv The start() method of the sub-environment must then be called in the extension of the vmm_env::start() method.start().start().start().. super.frmwr.get(fr). this. if (this.run_for_n_frames > 0 && this.virtual task start().to_phy_chan. fork forever begin eth_frame fr. File: SubEnv/blk_env.. end this.. endtask: start .sb.start_xactor().....start_xactor(). end join_none ..eth. virtual task start(). this. .src.. endtask: start .. endclass: blk_env VMM Primer: Composing Environments 14 . .received_by_phy_side(fr). this.. super.sv class blk_env extends vmm_ral_env. .mon.src != null) begin this.. stop(). if (this.run_for_n_frames > 0) begin `vmm_error(this. endtask: stop virtual task cleanup().. this. endtask: stop virtual task cleanup(). super..cfg. super. File: SubEnv/eth_subenv. this.cleanup().src. $psprintf("%0d frames were not seen by the scoreboard".sv class blk_env extends vmm_ral_env.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. super. . .eth.. this. super.stop_xactor().src != null) this. virtual task stop().sv class oc_eth_subenv extends vmm_subenv. if (this.log. end endtask: cleanup endclass: oc_eth_subenv File: SubEnv/blk_env.cleanup().stop().cfg..run_for_n_frames)). endtask: cleanup endclass: blk_env VMM Primer: Composing Environments 15 . virtual task stop().cleanup().stop().eth. The sum of all contributions will be used to determine whether to end the test or not.run_for_n_frames > 0 && this.start().frmwr. 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. File: SubEnv/eth_subenv. the VMM Environment Composition package includes a new object that helps make the decision whether to end the test or not: vmm_consensus..cfg. Fortunately..sv class oc_eth_subenv extends vmm_subenv. 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). 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. regardless of how many contributors there are.start_xactor(). the test may end once the generator is done. fork VMM Primer: Composing Environments 16 . . all channels are empty and all transactors are idle.src != null) begin this. From the sub-environment’s perspective. virtual task start(). end this. if (this.The most important—and difficult to implement—is the wait_for_end step. super. The various contributions to the end-oftest consensus are registered in the extension of the vmm_subenv::start() method.start_xactor(). The other contributions to the end-oftest decision are registered in the extension of the vmm_env::start() method. end join_none if (this.src.end_test.forever begin eth_frame fr.end_vote. virtual task start(). this. 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..to_phy_chan). end this.mon.phy). this.end_test.mon.register_xactor(this.sb. this. VMM Primer: Composing Environments 17 .notify.end_test.. . this. endtask: start . .received_by_phy_side(fr). this.to_phy_chan. this. The consent of the sub-environment is automatically taken care of by virtue of using the same vmm_consensus instance.register_notification( this.eth.. File: SubEnv/blk_env.exec_chan).register_channel(this. this.register_channel(this. endclass: oc_eth_subenv From the block-level environment’s perspective.src != null) begin this.end_test.. this.register_channel(this.start().register_xactor(this.host.end_vote.register_xactor(this.tx_chan).frmwr)..mon). eth_frame_atomic_gen::DONE). super.get(fr)..sv class blk_env extends vmm_ral_env.end_test.start(). a trivial block-level test would limit the number of transmitted frames to perform initial debug. virtual task wait_for_end().run_for_n_frames = 3..sv class blk_env extends vmm_ral_env. 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.wait_for_consensus(). initial begin env.end_vote.register_xactor(this..host).. endtask: wait_for_end . File: SubEnv/blk_trivial_test.. super. this.gen_cfg(). env. endtask: start .wait_for_end().eth. 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.sv program test... blk_env env = new.cfg. File: SubEnv/blk_env. .run(). env. For example.this.end_vote. end VMM Primer: Composing Environments 18 . .. oc_eth_subenv eth[2].sv class sys_env extends vmm_ral_env. File: SubEnv/sys_env... A single RAL model. VMM Primer: Composing Environments 19 . endclass: test_cfg Two instances of the sub-environments are required....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. First. rand oc_eth_subenv_cfg eth[2]. virtual function build(). RAM and Memory Allocation Manager is shared across both sub-environment because there are shared resources. . File: SubEnv/sys_env.. . two configuration descriptors are required instead of one. each connected to their respective physical interfaces.. .sv class test_cfg. . fork this. sys_top. . this. this. This allows both sub-environments to independently contribute to the end-of-test decision.eth[1] = new("Eth0"..sv class sys_env extends vmm_ral_env..dma_mam). this. This configures the subenvironment to use the external source provided—in this case an externally instantiated atomic generator.cfg.cfg_dut(). this.end_vote.eth[1]. this.this. endclass: sys_env Notice how the end-of-test vmm_consensus object is passed to both sub-environments. this.. It is a good idea to fork the configuration steps to perform as much of it as possible concurrently. null. endfunction: build .mii_1. Both sub-environments must be configured. notice how the tx_chan value is not null for the second instance of the sub-environment. virtual task cfg_dut().passive.end_vote. super.configure(). this.src.configure(). this.eth[0] = new("Eth0".. Also.cfg.mii_0. File: SubEnv/sys_env.. this.. this. this. this.ral_model.ral_model. VMM Primer: Composing Environments 20 .eth[1].ram. sys_top. join endtask: cfg_dut .ram.out_chan.eth[0]..passive.eth[0]. this.dma_mam).. this. endclass: sys_env However..stop(). endtask: wait_for_end . . File: SubEnv/sys_env... endtask: stop .eth[0]. regardless of the number of contributors. virtual task stop().eth[1].. File: SubEnv/sys_env.endclass: sys_env The start(). this.. super. virtual task wait_for_end().wait_for_end(). endclass: sys_env VMM Primer: Composing Environments 21 .. . this.wait_for_consensus().stop().end_vote.. stop() and cleanup() methods must similarly invoke their respective counterpart in both sub-environment instances.sv class sys_env extends vmm_ral_env. super. the wait_for_end() task remains identical as the vmm_consensus object takes care of scaling the end-of-test decision.stop()..sv class sys_env extends vmm_ral_env. eth[1]. initial begin env.cfg.cfg. env.Step 7: System-Level Tests The system-level environment now completely integrates two subenvironments and is ready to perform system-level tests. You can now speed-up the development of system-level tests by leveraging the work done in block-level environments.run_for_n_frames = 3. VMM Primer: Composing Environments 22 .run().sv program test.run_for_n_frames = 3.gen_cfg(). 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.eth[0]. sys_env env = new. For example. File: SubEnv/sys_trivial_test. a trivial system-level test would limit the number of transmitted frames to perform initial debug. verification environments.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 23 . integrate a Register Abstraction Layer model or a Data Stream Scoreboard. VMM Primer: Composing Environments 24 . 1 / May 28. 2006 VMM Primer: Using the Memory Allocation Manager 1 .VMM Primer: Using the Memory Allocation Manager 1 Author: Janick Bergeron Version 1. you should read the other primers in this series. This primer will only show the portions of the environment that make use of the VMM Memory Allocation Manager class. such as DMA buffers. such as transactors. This primer assumes that you are familiar with VMM. The design used to illustrate how to use the VMM Memory Allocation Manager is the OpenCore Ethernet Media Access Controller (MAC). These memory buffers are usually configured as a buffer address value (pointer) through a descriptor or a linked list in the design. It is useful for managing memory buffers required by designs to store temporary. this primer focuses on a single aspect of its functionality: the transmit DMA buffers. VMM Primer: Using the Memory Allocation Manager 2 . and how to use it in a verification environment to properly configure a design that requires memory buffers. It is similar to C’s malloc() and free() routines. verification environments and the Register Abstraction Layer package.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. If you need to ramp-up on VMM itself. A complete verification environment that can verify this design requires many more elements than a memory allocation manager. This primer is designed to learn how to configure a memory allocation manager. Despite being a fairly complex design. Other primers cover other aspects of developing VMM-compliant verification assets. To transmit a frame. You can use the same sequence to use a memory allocation manager and use it to verify your design. you should read it in a sequential fashion. The DUT Figure 1 shows the (partial) structure of the verification environment surrounding the Ethernet MAC. The firmware emulation transactor accesses the DMA memory directly through backdoor read()/write() methods provided by the RAM model itself. The DMA memory. As such. is not included in the RAL model. 2. Figure 1 A Partial Verification Environment FRMWR RAL DUT Tx RAM The firmware must first initialize the DUT as follows: 1.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 must use the following procedure: VMM Primer: Using the Memory Allocation Manager 3 . All Transmit Buffer Descriptors are marked as “not ready”. Transmission is enabled. not being mapped in the DUT address space. The firmware emulation transactor configures and controls the registers inside the design through a RAL model. 5. Each TxBD is located in consecutive memory addresses and has the structure partially by the following RALF specification. .1. File: SubEnv/oc_ethernet. } } register TxPNT { bits 32. Store the frame in consecutive locations in the RAM.. as defined by the TX_BD_NUM register. The address of the frame in the RAM is specified in the Transmit Buffer Descriptor.. 4. . } VMM Primer: Using the Memory Allocation Manager 4 . register TX_BD_NUM { field TX_BD_NUM { bits 8. The DUT contains a programmable number of Transmit Buffer Descriptors (TxBD).ralf block oc_ethernet { bytes 4.. field LEN { bits 16. 2. . regfile TxBD[128] @. up to 128..... field PTR { bits 32. 3. +2 { register TxBD_CTRL { bits 32. The number of bytes in the frame is specified in the Transmit Buffer Descriptor.. A Transmit Buffer will now be available and automatically marked as “not ready” by the DUT. field RD.. } } . Wait for a “Buffer Transmitted” interrupt. The Transmit Buffer Descriptor is marked as “ready”.. field WR. buffers are allocated when the TxBD are initialized. With dynamic buffer allocation. 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.. } There are two ways DMA buffers can be allocated: statically or dynamically. As such. a new buffer is allocated for every frame to be transmitted. This approach minimizes the buffer allocation problem and restricts the memory usage to a well-defined area. VMM Primer: Using the Memory Allocation Manager 5 . They are large enough to accommodate the largest possible frames to be transmitted and are reused to transmit different frames. Each time. the TxBD is updated to refer to the newly allocated buffer. a dynamic buffer allocation strategy will be used. It would also require several different runs of randomly-allocated static buffers to cover different areas of the addressable space. Therefore. An actual device driver would probably use a static buffer allocation approach. Buffers are sized according to the need of each frame. it must be configured coherently with the configuration of the RAM itself. With static buffer allocation..} } . Step 1: Configuration A Memory Allocation Manager will be used to manage the allocation of DMA buffers in the RAM. QWORD} sizes_e. endclass: wb_slave_cfg File: SubEnv/blk_env.. mam. rand bit [63:0] max_addr. DWORD. It must also contain an instance of the MAM configuration descriptor. File: SubEnv/VIPs/wishbone/config.. Both are randomized and constrained to be coherent.sv class wb_cfg. } . WORD.... rand bit [63:0] min_addr..start_offset == ram.The environment configuration descriptor contains an instance of the RAM model configuration descriptor. endclass: test_cfg VMM Primer: Using the Memory Allocation Manager 6 ..sv class test_cfg.max_addr. rand sizes_e port_size.. endclass: wb_cfg class wb_slave_cfg extends wb_cfg. . mam. .. rand vmm_mam_cfg mam.. typedef enum {BYTE. constraint test_cfg_valid { .. rand wb_slave_cfg ram.min_addr.. .n_bytes == 1. mam.end_offset == ram. ... . ... ral. . 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. transmission can be enabled. Once the buffer descriptors have been initialized.sv: virtual function void gen_cfg(). This is accomplished in the extension of the vmm_env::cfg_dut() step. located in adjacent locations or in widely different addresses.cfg.. ok = this.gen_cfg(). .When the test configuration descriptor is randomized in the extension of the vmm_env::gen_cfg() method. File: SubEnv/blk_env. .set(cfg.randomize(). bit ok.. super. endfunction: gen_cfg Note that the memory allocation mode and locality remain unconstrained.n_tx_bd)..TX_BD_NUM.sv virtual task configure(). This will hopefully uncover bugs when DMA buffers are reused. Step 2: Buffer Initialization The TxBD must be initialized to the “not ready” state before transmission is enabled. VMM Primer: Using the Memory Allocation Manager 7 . File: SubEnv/eth_subenv. begin int bd_addr = 0. .sv class frmwr_emulator extends vmm_xactor. repeat (cfg.TxBD[bd_addr]. end . When a frame and a transmit buffer are available.. local task tx_driver().TXEN. Step 3: Frame Transmission The firmware emulation transactor is responsible for initiating the transmission of frames it receives from the frame generator. logic [7:0] bytes[]... ral. ral. . 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. . . ..... if (!drop) begin VMM Primer: Using the Memory Allocation Manager 8 ..n_tx_bd) begin ... It is waiting for the first TxBD to be specified as “ready”.. forever begin eth_frame fr. File: SubEnv/eth_subenv.RD. bd_addr++.. endtask: configure The DUT is now ready to transmit frames.. end .write(status...write(status. 0). 1).. ram.sv class frmwr_emulator extends vmm_xactor. for (int i = 0.. bytes[i+1].. .byte_size())..byte_pack(bytes).request_region(fr...byte_pack(bytes). i < len. end .. {bytes[i]. forever begin eth_frame fr.. if (!drop) begin vmm_mam_region bfr.byte_size(). bytes[i+3]})... bytes[i+2]. end end endtask: tx_driver .get_start_offset() + i.byte_size()). once the frame is laid in the DMA buffer. i < len.... File: SubEnv/eth_subenv. . fr. . all that remains is to update the buffer descriptor to point to the new DMA buffer and mark it as “ready”. fr.dma_mam. local task tx_driver(). bytes = new [len + (4 – len % 4)]. .request_region(fr. .write(bfr. bytes = new [len + (4 – len % 4)]. bfr = this. . bfr = this. logic [7:0] bytes[].dma_mam.vmm_mam_region bfr. i += 4) begin VMM Primer: Using the Memory Allocation Manager 9 .. len = fr.. i += 4) begin this. endclass: frmwr_emulator Of course... len = fr. for (int i = 0.byte_size(). write(bfr. . endclass: frmwr_emulator Step 4: Frame Completion To avoid memory leakage. and allow a greedy memory allocation mode to allocate previously-used memory...sv class frmwr_emulator extends vmm_xactor. all of the Transmit Buffer Descriptors that are newly marked as “not ready” are assumed to have been transmitted..LEN. local task service_irq().TxBD[bd_addr]. end . ral. When an interrupt signals that a frame has been transmitted..RD.ram. File: SubEnv/eth_subenv.this. ral..write(status. .TxBD[bd_addr].. bfr. it is necessary to free buffers once the frame they contained has been transmitted.... . bytes[i+3]}). This is implemented in the interrupt service routine.dma_bfrs[bd_addr] = bfr. ral.get_start_offset()).. bytes[i+1]. bytes[i+2]... VMM Primer: Using the Memory Allocation Manager 10 ...PTR.write(status. The memory allocated for this DMA buffer is then released. {bytes[i]. forever begin . . end end endtask: tx_driver . cfg.set(len).TxBD[bd_addr].get_start_offset() + i. 1). 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... . You may consider reading other publications in this series to learn how to write VMM-compliant command-layer transactors.) begin . ral. verification environments or integrate a Register Abstraction Layer model or a Data Stream Scoreboard VMM Primer: Using the Memory Allocation Manager 11 .dma_mam.dma_bfrs[bd_addr])..release_region( this... endclass: frmwr_emulator Step 5: Congratulations You have now completed the integration of a VMM Memory Allocation Manager in a verification environment...if (TXE || TXB) begin .. if (RD) break.read(status. end end .cfg.. while (.RD.. . RD). end endtask: service_irq ...TxBD[bd_addr]... VMM Primer: Using the Memory Allocation Manager 12 . VMM Primer: Writing Command-Layer Master Transactors Author: Janick Bergeron Version 1. 2006 1 VMM Primer: Writing Command-Layer Master Transactors 1 .3 / December 1. VMM compliance is a matter of degree. The protocol used in this primer was selected for its simplicity. It was designed to be a reference document that defines what is—and is not—compliant with the methodology described within it. such as slave transactors. It is sufficient to achieve the goal of demonstrating. generators.Introduction The Verification Methodology Manual for SystemVerilog book was never written as a training book. A word of caution however: it may be tempting to stop reading this primer half way through. assertions and verification environments. Other primers will eventually cover other aspects of developing VMM-compliant verification assets. it does not require the use of many elements of the VMM standard library. Because of its simplicity. step by step. This document is written in the same order you would implement a command-layer transactor. As such. functional-layer transactors. also known as bus-functional models (BFM). a transactor may be declared VMM compliant. You can use the same sequence to create your own specific transactor. as soon as a functional transactor is available. 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. Once a certain minimum level of functionality is met. But additional VMM functionality—such as VMM Primer: Writing Command-Layer Master Transactors 2 . monitors. how to create a simple VMMcompliant master transactor. When writing a reusable transactor.0) available from ARM (http://arm. Therefore. you have to think about all possible applications it may be used in. not attempt to justify them or present alternatives. the APB transactors should be written for the entire 32-bit of address and data information. Throughout the primer. references are made to rule numbers and table numbers: Those are rules and tables in the Verification Methodology Manual for SystemVerilog. This primer will show how to apply the various VMM guidelines. you should refer to the VMM book under the relevant quoted rules and recommendations. not just the device you are using it for the first time. The protocol specification can be found in the AMBA™ Specification (Rev 2.callbacks—will make it much easier to use in different verification environments. If you are interested in learning more about the justifications of various techniques and approaches used in this primer. even though the device in this primer only supports 8 address bits and 16 data bits.com). VMM Primer: Writing Command-Layer Master Transactors 3 . The Protocol The protocol used in this primer is the AMBA™ Peripheral Bus (APB) protocol. It is a simple single-master address-based parallel bus providing atomic individual read and write cycles. Therefore. you should read—and apply—this primer in its entirety. The Verification Components Figure 1 illustrates the various components that will be created throughout this primer. 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 command-layer master transactor interfaces directly to the DUT signals and initiates transactions upon requests on a transaction-level interface. The name of the interface is prefixed with "apb_" to identify that it belongs to the APB protocol (Rule 4-5). 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. A single exchange of information (a READ or a WRITE operation) is called a transaction. listed in the AMBA™ Specification in Section 2.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk).sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if.. endinterface: apb_if `endif The signals... wire [31:0] pwdata. File: apb/apb_if.4. wire [31:0] prdata.. 4-11). clocking blocks are used to define the direction and sampling of the signals (Rule 4-7. wire pwrite. . VMM Primer: Writing Command-Layer Master Transactors 5 . wire penable. whenever required. This is an old C trick that allows the file to be included multiple times.the interface is embedded in an `ifndef/`define/`endif construct. wire psel. wire [31:0] paddr. File: apb/apb_if. . endinterface: apb_if `endif Because this is a synchronous protocol. without causing multipledefinition errors. are declared as wires (Rule 4-6) inside the interface. 4-12). File: apb/apb_if.. wire [31:0] pwdata. The clock signal need not be specified as it is implicit in the clocking block.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). wire pwrite.. 4-11. wire psel. wire [31:0] prdata. psel. endclocking: mck .File: apb/apb_if. wire penable. clocking mck @(posedge pclk). wire penable. 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. output paddr. pwdata. VMM Primer: Writing Command-Layer Master Transactors 6 . penable. clocking mck @(posedge pclk). wire psel.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). pwrite. wire pwrite. wire [31:0] prdata. wire [31:0] paddr. input prdata. wire [31:0] pwdata. (apb0..apb_enable . pwrite. when these transactors will be written. (apb0. (apb0.psel ).prdata[15:0])....).pwdata[15:0]). Step 2: Connecting to the DUT The interface may now be connected to the DUT.apb_addr . pwdata..apb_rdata . It is instantiated in a top-level module.. To be fully compliant.apb_wdata .sv module tb_top.apb_sel . slave_ip dut_slv(. The connection to the DUT pins are specified using a hierarchical reference to the wires in the interface instance. apb_if apb0(. alongside of the DUT instantiation (Rule 413). These can be added later.apb_write .. endinterface: apb_if `endif The interface declaration is now sufficient for writing a master APB transactor. it should eventually include a modport for a slave and a passive monitor transactor (Rule 4-9). (apb0.. input prdata. endclocking: mck modport master(clocking mck). VMM Primer: Writing Command-Layer Master Transactors 7 .paddr[7:0] ). . psel..)..output paddr. . (apb0.pwrite ).. File: Command_Master_Xactor/tb_top.penable ). . penable. (apb0. VMM Primer: Writing Command-Layer Master Transactors 8 . File: apb/apb_master. Traditionally. one for the READ transaction and one for the WRITE transaction.pwrite).... .paddr[7:0]). endmodule: tb_top apb0..apb_wdata . bit clk = 0. (clk)). apb_if apb0(clk).endmodule: tb_top This top-level module also contains the clock generators (Rule 415).sv task read(input bit [31:0] addr. my_design dut(.clk always #10 clk = ~clk. using the bit type (Rule 4-17) and ensuring that no clock edges will occur at time zero (Rule 4-16).apb_write . .. . (apb0.psel). output logic [31:0] data).pwdata[15:0]).apb_enable . (apb0.. (apb0. (apb0. tasks would have been defined.apb_addr .. Step 3: The Transaction Descriptor The next step is to define the APB transaction descriptor.prdata[15:0]). File: Command_Master_Xactor/tb_top. (apb0.apb_sel .sv module tb_top.apb_rdata .penable). 4-62). rand bit [31:0] addr.. rand logic [31:0] data. If an argument is the same across multiple tasks. function new(). "class"). super.. It also needs a static vmm_log property instance used to issue messages from the transaction descriptor. endfunction: new . rand enum {READ.sv" class apb_rw extends vmm_data. but not at all for random tests. This descriptor is a class (Rule 4-53) extended from the vmm_data class (Rule 4-55). 4-62) and public rand properties for each task argument (Rule 4-59. A random test requires a transaction descriptor (Rule 4-54). `endif VMM Primer: Writing Command-Layer Master Transactors 9 .task write(input bit [31:0] addr. static vmm_log log = new("apb_rw". input bit [31:0] data).. containing a public rand property enumerating the directed tasks (Rule 4-60.new(this. This instance of the message service interface is passed to the vmm_data constructor (Rule 4-58). a single property can be used.. endclass: apb_rw . This works well for directed tests. File: apb/apb_rw.log). WRITE} kind.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. static vmm_log log = new("apb_rw".. A transactionlevel interface will be required to transfer transaction descriptors to a transactor to be executed. `endif VMM Primer: Writing Command-Layer Master Transactors 10 . File: apb/apb_rw. the random content is initially ignored and it will be replaced by the data value that was read. function new(). it has the minimum functionality to be used by a transactor.Note how the same property is used for data. endclass: apb_rw `vmm_channel(apb_rw) . WRITE} kind. 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. In a WRITE transaction...sv" class apb_rw extends vmm_data.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. In a READ transaction. "class").log).. rand enum {READ.new(this. It will be interpreted differently depending on the transaction kind (Rule 4-71). rand bit [31:0] addr. endfunction: new . rand logic [31:0] data. This is done using the `vmm_channel macro (Rule 4-56). Although the transaction descriptor is not yet VMM-compliant. it is interpreted as the data to be written. super. ... .. It is a class (Rule 4-91) derived from the vmm_xactor base class (4-92). class apb_master extends vmm_xactor.sv" . They are also declared protected to prevent them from being called from outside the class and create concurrent bus access problems.... File: apb/apb_master.Step 4: The Master Transactor The master transactor can now be started. class apb_master extends vmm_xactor.. . 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.. . input bit [31:0] data). virtual protected task read(input bit [31:0] addr. VMM Primer: Writing Command-Layer Master Transactors 11 .sv `ifndef APB_MASTER__SV `define APB_MASTER__SV .. `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if. endtask: read virtual protected task write(input bit [31:0] addr. apb_rw_channel in_chan. stream_id). class apb_master extends vmm_xactor. this.. function new(string name. 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. endfunction: new .in_chan = in_chan.master sigs.. super. apb_rw_channel in_chan = null). if (in_chan == null) in_chan = new("APB Master Input Channel". endtask: write . virtual protected task read(input bit [31:0] addr.sv `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if. File: apb/apb_master. 4-113) and both are saved in public properties (Rule 4-109.sv" `include "apb_rw. name. this.. output logic [31:0] data).new("APB Master". The former is done using a vmm_channel instance and the latter is done using a virtual modport. Both are passed to the transactor as constructor arguments (Rule 4-108. .master sigs....... 4-112). int unsigned stream_id.sigs = sigs. VMM Primer: Writing Command-Layer Master Transactors 12 . name).sv" . virtual apb_if.. master sigs.sv" `include "apb_rw. .sv `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if. if (in_chan == null) in_chan = new("APB Master Input Channel". function new(string name. endclass: apb_master `endif When the transactor is reset. endtask: write . virtual apb_if. This method may be called in the transactor constructor to initialize the output signals to their idle state. This is accomplished in the extension of the vmm_xactor::reset_xactor() method (Table A-8).. name... VMM Primer: Writing Command-Layer Master Transactors 13 .. this. apb_rw_channel in_chan = null). input bit [31:0] data). stream_id).. the input channel must be flushed and the critical output signals must be driven to their idle state.. apb_rw_channel in_chan. name).sigs = sigs. class apb_master extends vmm_xactor.master sigs... int unsigned stream_id. File: apb/apb_master. or explicit signal assignments may be used in the constructor. endtask: read virtual protected task write(input bit [31:0] addr.. virtual apb_if. super.new("APB Master".sv" . the vmm_xactor::wait_if_stopped_or_empty() method is used to suspend the execution of the transactor if it is stopped.mck.penable <= ’0.. this. endtask: read virtual protected task write(input bit [31:0] addr.. Because the protocol supports being suspended between transactions. endtask: write . endfunction: new virtual function void reset_xactor( reset_e rst_typ = SOFT_RST).psel <= ’0. .in_chan.psel <= ’0. endfunction: reset_xactor . input bit [31:0] data). File: apb/apb_master.sigs.penable <= ’0. 4-124 and 4-129). this. this. 4-122. 4-123.mck.. 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). output logic [31:0] data)..sigs.. this. virtual protected task read(input bit [31:0] addr.this. super.sigs. this.sigs.. .flush().in_chan = in chan. nonblocking and out-of-order execution models (Rules 4-121.reset(rst_typ).sv `ifndef APB_MASTER__SV VMM Primer: Writing Command-Layer Master Transactors 14 . The most flexible transaction execution mechanism uses the active slot as it supports block..mck..mck. .master sigs.new("APB Master".penable <= ’0.mck.write(tr..mck. this.sv" `include "apb_rw.sigs. tr. super. this. function new(string name. virtual apb_if.data). name.activate(tr).in_chan. this..sigs. forever begin apb_rw tr.start(). super. this.sigs.read(tr. @ (this.data)..sigs. tr.psel <= ’0.sigs. endfunction: reset_xactor virtual protected task main().in_chan). if (in_chan == null) in_chan = new("APB Master Input Channel". this.flush(). this. stream_id).psel <= ’0. int unsigned stream_id. class apb_master extends vmm_xactor..in_chan = in chan.master sigs. apb_rw_channel in_chan.penable <= ’0. endfunction: new virtual function void reset_xactor( reset_e rst_typ = SOFT_RST).sv" .mck).`define APB_MASTER__SV `include "apb_if. super. this.in_chan.addr. this.mck. this.in_chan. this. virtual apb_if.reset(rst_typ). VMM Primer: Writing Command-Layer Master Transactors 15 . apb_rw_channel in_chan = null).addr.kind) apb_rw::READ : this.wait_if_stopped_or_empty(this. name). apb_rw::WRITE: this. case (tr.mck.main().sigs = sigs. . virtual protected task read(input bit [31:0] addr.psel <= ’1. the active clock edge is defined by waiting on the clocking block itself.sigs. File: apb/apb_master..mck. not an edge of an input signal (Rule 4-7. this.mck. VMM Primer: Writing Command-Layer Master Transactors 16 .remove(). this. . endtask: write endclass: apb_master `endif The READ and WRITE tasks are coded exactly as they would be if good old Verilog was used.sigs.sigs.paddr <= addr..mck. 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. end endtask: main virtual protected task read(input bit [31:0] addr.pwrite <= ’0.complete().in_chan.sv ..in_chan... . 4-12). endtask: read virtual protected task write(input bit [31:0] addr. . Similarly.endcase this. input bit [31:0] data). 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).. output logic [31:0] data). this. @ (this.sigs.psel <= ’0.prdata.mck.mck. VMM Primer: Writing Command-Layer Master Transactors 17 . it needs to be started in that latter method.sigs. endtask: read virtual protected task write(input bit [31:0] addr. this.mck.mck.penable <= ’1.penable <= ’0.psel <= ’1. @ (this.mck). this.sigs.sigs.penable <= ’1. data = this. The transactor is constructed in the extension of the vmm_env::build() method (Rule 4-34.mck.mck. this. 4-35) and started in the extension of the vmm_env::start() method (Rule 4-41).mck.sigs. the transactor can now be used to exercise the DUT. this.@ (this.sigs.mck.sigs. input bit [31:0] data).sigs. this. 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. this. this.mck. extended from the vmm_env base class.penable <= ’0. It is instantiated in a verification environment class. this. endtask: write Step 5: The First Test Although not completely VMM-compliant.sigs. this.pwdata <= data.sigs.mck).paddr <= addr.mck).psel <= ’0.pwrite <= ’1.sigs.mck.mck.sigs. @ (this.sigs.sigs. Note that if the transactor is required to configure the DUT in the extension of the vmm_env::cfg_dut() method.mck). this.sigs. start_xactor().mst.start(). This way. File: Command_Master_Xactor/test_simple. apb_master mst. 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.sv `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm. endtask: start . tb_top. only constraining those properties that are needed for the directed test. super.. virtual function void build(). 0. It is a good idea to randomize these descriptors. any additional property will be randomized instead of defaulting to always the same value.apb0). endfunction: build virtual task start(). super.mst = new("0".sv" class tb_env extends vmm_env.File: Command_Master_Xactor/tb_env.sv `include "tb_env. The directed stimulus is created by instantiating transaction descriptors appropriately filled. The test is written in a program (Rule 4-27) that instantiates the verification environment (Rule 4-28).sv" `include "apb_master.build().. this.sv" VMM Primer: Writing Command-Layer Master Transactors 18 . this. data[15:0] !== wr.in_chan. wr = new. "Readback value != write value").program simple_test. "Simple"). addr == wr. tb_env env = new. if (!ok) begin `vmm_fatal(log. "Unable to randomize WRITE cycle"). ok = rd.put(wr). bit ok.randomize() with { kind == WRITE.start(). if (rd. if (!ok) begin `vmm_fatal(log. }. env. end endprogram VMM Primer: Writing Command-Layer Master Transactors 19 .put(rd).mst.randomize() with { kind == READ. wr. vmm_log log = new("Test".addr.report().in_chan. "Unable to randomize READ cycle"). end env. }. ok = wr. end env. rd = new.mst. $finish(). initial begin apb_rw rd. end log.data[15:0]) begin `vmm_error(log. each time with a different seed. 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. 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. It will not be possible to modify the behavior of this transactor—for example to introduce delays between transactions. 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 . is not very reusable. to synchronize the start of a transaction with some other external event. A callback method allows a user to extend the behavior of a transactor without having to modify the transactor itself.This test can be run many times. will provide basic functionality. to verify the transactor and the DUT using different addresses. Callback methods should be provided before and after a transaction executes (Recommendations 4-155 and 4-156). 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. Step 6: Extension Points The transactor. The “pre-transaction” callback method allows errors to be injected and delays to be inserted. as coded. The problem is that the transactor. this.master sigs. 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). ref bit drop).new("APB Master".sv `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if.sigs = sigs. this. stream_id). this. int unsigned stream_id. virtual apb_if. endtask: pre_cycle virtual task post_cycle(apb_master xactor.sigs.sigs.mck. apb_rw cycle. if (in_chan == null) in_chan = new("APB Master Input Channel".penable <= ’0. apb_rw cycle). apb_rw_channel in_chan. virtual task pre_cycle(apb_master xactor. function new(string name. this. name).psel <= ’0. endtask: post_cycle endclass: apb_master_cbs class apb_master extends vmm_xactor. virtual apb_if. name.sv" typedef class apb_master.in_chan = in chan. File: apb/apb_master.sv" `include "apb_rw. super.master sigs. class apb_master_cbs extends vmm_xactor_callbacks.mck.159). endfunction: new VMM Primer: Writing Command-Layer Master Transactors 21 . apb_rw_channel in_chan = null). .psel <= ’0.wait_if_stopped_or_empty(this.data). super.virtual function void reset_xactor( reset_e rst_typ = SOFT_RST)... .main().read(tr. . tr. end endtask: main virtual protected task read(input bit [31:0] addr. this.in_chan.penable <= ’0. output logic [31:0] data).write(tr. forever begin apb_rw tr.sigs.flush(). this. this. apb_rw::WRITE: this.mck. tr.data). this.in_chan.mck).kind) apb_rw::READ : this. .in_chan).activate(tr).remove().in_chan. this.complete().reset(rst_typ).sigs..addr. this. .addr... this. endtask: read virtual protected task write(input bit [31:0] addr. endfunction: reset_xactor virtual protected task main(). endtask: write endclass: apb_master `endif VMM Primer: Writing Command-Layer Master Transactors 22 .. input bit [31:0] data). super. @ (this.mck..start(). case (tr.in_chan.in_chan. endcase this.sigs. if (in_chan == null) in_chan = new("APB Master Input Channel". endfunction: new VMM Primer: Writing Command-Layer Master Transactors 23 . the appropriate callback method needs to be invoked at the appropriate point in the execution of the transaction. apb_rw cycle.sigs. function new(string name.penable <= ’0. this.sv" `include "apb_rw. int unsigned stream_id. apb_rw_channel in_chan = null).sigs = sigs. this.sigs. stream_id). apb_rw_channel in_chan. apb_rw cycle). this.master sigs. virtual apb_if.mck.new("APB Master".psel <= ’0.mck.Next. super.master sigs.sv `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if. class apb_master_cbs extends vmm_xactor_callbacks. this.sv" typedef class apb_master. endtask: post_cycle endclass: apb_master_cbs class apb_master extends vmm_xactor. virtual task pre_cycle(apb_master xactor. virtual apb_if. File: apb/apb_master. endtask: pre_cycle virtual task post_cycle(apb_master xactor. name).in_chan = in chan. ref bit drop). name. using the `vmm_callback() macro (Rule 4-163). data). end this.write(tr. post_cycle( this.main(). this.wait_if_stopped_or_empty(this. tr.in_chan. tr. `vmm_callback(apb_master_cbs. endcase this. tr. end endtask: main virtual protected task read(input bit [31:0] addr.complete(). drop = 0. drop)). this. VMM Primer: Writing Command-Layer Master Transactors 24 . tr)).addr.read(tr. if (drop) begin this.sigs. this.psel <= ’0. case (tr.penable <= ’0. this.in_chan. endfunction: reset_xactor virtual protected task main().mck.in_chan.mck. forever begin apb_rw tr.kind) apb_rw::READ : this. @ (this. output logic [31:0] data). `vmm_callback(apb_master_cbs.in_chan. apb_rw::WRITE: this.remove().in_chan).addr.virtual function void reset_xactor( reset_e rst_typ = SOFT_RST).in_chan. this.mck). bit drop.activate(tr).in_chan. super.start().remove(). pre_cycle( this.sigs. continue. super. this.sigs.flush().reset(rst_typ).data). endtask: write endclass: apb_master `endif Step 7: Debug Messages To be truly reusable.. Debug messages should be added at judicious points to indicate what the transactor is about to do.sv" `include "apb_rw. class apb_master_cbs extends vmm_xactor_callbacks. input bit [31:0] data).. VMM Primer: Writing Command-Layer Master Transactors 25 ..sv `ifndef APB_MASTER__SV `define APB_MASTER__SV `include "apb_if. endtask: read virtual protected task write(input bit [31:0] addr.. ref bit drop). apb_rw cycle. File: apb/apb_master. virtual task pre_cycle(apb_master xactor. `vmm_debug() or `vmm_verbose() macros (Recommendation 4-51). is doing or has done. . This capability may even be a basic requirement if you plan on shipping encrypted or compiled code.sv" typedef class apb_master.. These debug messages are inserted using the `vmm_trace(). it should be possible to understand what the transactor does and debug its operation without having to inspect the source code. activate(tr). this. super. this. super. this. function new(string name.penable <= ’0. @ (this.in_chan. apb_rw_channel in_chan. VMM Primer: Writing Command-Layer Master Transactors 26 . endtask: post_cycle endclass: apb_master_cbs class apb_master extends vmm_xactor.in_chan).mck). name). super.new("APB Master".wait_if_stopped_or_empty(this. virtual apb_if. this. forever begin apb_rw tr. if (in_chan == null) in_chan = new("APB Master Input Channel".endtask: pre_cycle virtual task post_cycle(apb_master xactor. int unsigned stream_id. bit drop.sigs. this.sigs.mck. this.penable <= ’0.psel <= ’0.main().mck.flush(). virtual apb_if.sigs.in_chan. endfunction: reset_xactor virtual protected task main().sigs = sigs.in_chan = in chan.mck.mck. this. stream_id).sigs.reset(rst_typ). apb_rw cycle).master sigs. endfunction: new virtual function void reset_xactor( reset_e rst_typ = SOFT_RST). apb_rw_channel in_chan = null). this.sigs. this.psel <= ’0. name.master sigs. \n".drop = 0..start().in_chan. this. endtask: write endclass: apb_master VMM Primer: Writing Command-Layer Master Transactors 27 ..in_chan. `vmm_callback(apb_master_cbs.. post_cycle( this. end endtask: main virtual protected task read(input bit [31:0] addr. drop))..data).addr.. output logic [31:0] data). {"Starting transaction. this. case (tr. this. tr. {"Completed transaction.psdisplay(" ")}).\n".data). if (drop) begin `vmm_debug(log.in_chan. tr. tr.addr.complete().read(tr.remove(). tr. tr. tr. tr)). .remove(). `vmm_trace(log.psdisplay(" ")})...in_chan. .. apb_rw::WRITE: this. endcase this.psdisplay(" ")}). input bit [31:0] data). endtask: read virtual protected task write(input bit [31:0] addr. `vmm_callback(apb_master_cbs. pre_cycle( this. {"Dropping transaction.\n".write(tr... continue.kind) apb_rw::READ : this. end `vmm_trace(log. new(this. defined in the vmm_data base class does not know about the content of the APB transaction descriptor. File: apb/apb_rw. File: Command_Master_Xactor/Makefile % vcs –sverilog –ntb_opts vmm +vmm_log_default=trace . rand enum {READ. For that method to display the information that is relevant for the APB transaction.. VMM Primer: Writing Command-Layer Master Transactors 28 . static vmm_log log = new("apb_rw". rand logic [31:0] data. rand bit [31:0] addr.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. That’s because the psdisplay() method. "class"). super. But the messages will be not very useful as they do not display the content of the executed transactions. WRITE} kind.`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. function new().log). it is necessary to overload this method in the transaction descriptor class (Rule 4-76)..sv" class apb_rw extends vmm_data. WRITE} kind. "class"). copying transaction descriptors (vmm_data::copy()).sv" class apb_rw extends vmm_data.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm... rand bit [31:0] addr... rand logic [31:0] data. prefix. static vmm_log log = new("apb_rw".data). endfunction: psdisplay . `endif Re-running the test with increased verbosity now yields useful and meaningful debug messages. VMM Primer: Writing Command-Layer Master Transactors 29 . this.name().addr. comparing transaction descriptors (vmm_data::compare()) and checking that the content of transaction descriptors is valid (vmm_data::is_valid()) (Rule 476).endfunction: new .. 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. $sformat(psdisplay. endclass: apb_rw `vmm_channel(apb_rw) . File: apb/apb_rw. "%sAPB %s @ 0x%h = 0x%h". virtual function string psdisplay(string prefix = ""). These common operations include creating transaction descriptors (vmm_data::allocate()).kind. this. rand enum {READ. new(this.kind.addr. tr. endfunction: new virtual function vmm_data allocate().kind = this. apb_rw tr. output string diff. endfunction: allocate virtual function vmm_data copy(vmm_data to = null). end super.addr. int kind = -1). this. endfunction: is_valid virtual function bit compare(input vmm_data to. return tr.addr = this. else if (!$cast(tr. $sformat(psdisplay. return 1. input int kind = -1). endfunction: copy virtual function string psdisplay(string prefix = ""). "Cannot copy into non-apb_rw \n instance").name(). tr. if (to == null) begin VMM Primer: Writing Command-Layer Master Transactors 30 . return null. if (to == null) tr = new. tr.data = this.kind. prefix. apb_rw tr.copy_data(tr).function new(). return tr. this. apb_rw tr = new.data). to)) begin `vmm_fatal(log. endfunction: psdisplay virtual function bit is_valid(bit silent = 1.data.log). super. "%sAPB %s @ 0x%h = 0x%h". this. addr !== tr. "Data 0x%h !== 0x%h". return 0. return 0. this.addr). end if (this. return 0. this. "Kind %s !== %s". tr. end if (this.kind.kind != tr..kind). "Cannot compare against \n non-apb_rw instance").kind) begin $sformat(diff. to)) begin `vmm_fatal(log. `endif VMM Primer: Writing Command-Layer Master Transactors 31 . return 0.data !== tr. "Addr 0x%h !== 0x%h".addr) begin $sformat(diff. end else if (!$cast(tr.`vmm_fatal(log. return 0. tr. endfunction: compare endclass: apb_rw `vmm_channel(apb_rw) ..data. tr.data) begin $sformat(diff. end return 1. end if (this.data). "Cannot compare to NULL \n reference"). this.addr. log). File: apb/apb_rw. static vmm_log log = new("apb_rw". return tr. 524) in the transaction descriptor file.sv" class apb_rw extends vmm_data. WRITE} kind. vmm_data::byte_pack() and vmm_data::byte_unpack() should also be overloaded for packet-oriented transactions. where the content of the transaction is transmitted over a physical interface (Recommendation 4-77). rand enum {READ. rand bit [31:0] addr. it is a good idea to pre-define random transaction generators whenever transaction descriptors are defined. apb_rw tr = new. It is a simple matter of using the `vmm_atomic_gen() and `vmm_scenario_gen() macros (Recommendation 5-23. Step 9: Transaction Generator To promote the use of random stimulus. function new().sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. vmm_data:byte_size().Three other standard methods. endfunction: allocate VMM Primer: Writing Command-Layer Master Transactors 32 .new(this. "class"). super. endfunction: new virtual function vmm_data allocate(). rand logic [31:0] data. 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 2007 1 VMM Primer: Writing Command-Layer Slave Transactors 1 .VMM Primer: Writing Command-Layer Slave Transactors Author: Janick Bergeron Version 0.4 / May17. it does not require the use of many elements of the VMM standard library. VMM compliance is a matter of degree. A word of caution however: it may be tempting to stop reading this primer half way through. But additional VMM functionality—such as VMM Primer: Writing Command-Layer Slave Transactors 2 . The protocol used in this primer was selected for its simplicity.Introduction The Verification Methodology Manual for SystemVerilog book was never written as a training book. step by step. This document is written in the same order you would implement a command-layer slave transactor. You can use the same sequence to create your own specific transactor. generators. a slave transactor may be declared VMM compliant. how to create a simple VMMcompliant slave transactor. monitors. It is sufficient to achieve the goal of demonstrating. as soon as a functional transactor is available. you should read it in a sequential fashion. Once a certain minimum level of functionality is met. such as master transactors. Because of its simplicity. It was designed to be a reference document that defines what is—and is not—compliant with the methodology described within it. As such. 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. functionallayer transactors. Other primers will eventually cover other aspects of developing VMM-compliant verification assets. assertions and verification environments. com). When writing a reusable slave transactor. not just the device you are using it for the first time.a request/response model to a higher-level transactor or 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. Therefore. VMM Primer: Writing Command-Layer Slave Transactors 3 . It is a simple single-master address-based parallel bus providing atomic individual read and write cycles. The Protocol The protocol used in this primer is the AMBA™ Peripheral Bus (APB) protocol.0) available from ARM (http://arm. not attempt to justify them or present alternatives. you should read—and apply—this primer in its entirety. The protocol specification can be found in the AMBA™ Specification (Rev 2. Therefore. even though the device you will be initially using with this transactor supports 8 address bits and 16 data bits. the APB slave transactor should be written for the entire 32-bit of address and data information. This primer will show how to apply the various VMM guidelines. If you are interested in learning more about the justifications of various techniques and approaches used in this primer. you should refer to the VMM book under the relevant quoted rules and recommendations. The name of the interface is prefixed with “apb_” to identify that it belongs to the APB protocol (Rule 4-5).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 . A single exchange of information (a READ or a WRITE operation) is called a transaction. Slaves are differentiated by responding to different address ranges. The signals are declared inside an interface (Rule 4-4). A command-layer slave transactor interfaces directly to the DUT signals and optionally requests transaction data fulfillment on a transaction-level interface. 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. There may be multiple slaves on an APB bus but there can only be one master. sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if.. wire [31:0] prdata. File: apb/apb_if. wire [31:0] pwdata. wire psel. without causing multiple-definition errors. endinterface: apb_if `endif Because this is a synchronous protocol.. listed in the AMBA™ Specification in Section 2. File: apb/apb_if. VMM Primer: Writing Command-Layer Slave Transactors 5 .sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). This is an old C trick that allows the file to be included multiple time.. are declared as wires (Rule 4-6) inside the interface. whenever required. wire [31:0] paddr.4. wire penable. wire pwrite. 4-11). endinterface: apb_if `endif The signals.. clocking blocks are used to define the direction and sampling of the signals (Rule 4-7. . .interface is embedded in an `ifndef/`define/`endif construct. endclocking: sck VMM Primer: Writing Command-Layer Slave Transactors 6 . wire psel. endinterface: apb_if `endif The clocking block defining the synchronous signals is specified in the modport for the APB slave transactor (Rule 4-9. wire [31:0] pwdata. clocking sck @(posedge pclk). wire [31:0] paddr. output prdata. wire pwrite. wire [31:0] prdata. 4-12). psel. wire [31:0] paddr. penable. psel. File: apb/apb_if. pwrite. wire [31:0] prdata. pwdata. wire [31:0] pwdata. input paddr. The clock signal need not be specified as it is implicit in the clocking block. clocking sck @(posedge pclk). pwdata. wire penable. pwrite.. wire pwrite. wire psel.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). input paddr. 4-11.File: apb/apb_if.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). penable. wire penable.. output prdata. endclocking: sck . endinterface: apb_if `endif The interface declaration is now sufficient for writing a APB slave transactor. The connection to the DUT pins are specified using a hierarchical reference to the wires in the interface instance.apb_rdata .prdata[15:0]). Step 2: Connecting to the DUT The interface may now be connected to the DUT.. (apb0. File: Command_Slave_Xactor/tb_top. .).).psel ).pwdata[15:0]).apb_write . To be fully compliant. apb_if apb0(.penable )... It is instantiated in a top-level module.apb_enable ..sv module tb_top.apb_addr .. . . (apb0.modport slave(clocking sck)... it should eventually include a modport for a master and a passive monitor transactor (Rule 4-9).pwrite ). These can be added later. (apb0. VMM Primer: Writing Command-Layer Slave Transactors 7 .. when these transactors will be written. (apb0.apb_sel . alongside of the DUT instantiation (Rule 4-13). endmodule: tb_top (apb0.. (apb0.paddr[7:0] ). master_ip dut_mst(..apb_wdata .. .paddr[7:0] ). my_design dut(. This instance of the message service interface is passed to the vmm_data constructor (Rule 4-58). .sv module tb_top. .apb_sel (apb0. It also needs a static vmm_log property instance used to issue messages from the transaction descriptor. . VMM Primer: Writing Command-Layer Slave Transactors 8 . 4-62)...apb_enable (apb0.. File: Command_Slave_Xactor/tb_top.pwdata[15:0]). using the bit type (Rule 4-17) and ensuring that no clock edges will occur at time zero (Rule 4-16).psel ).pwrite ). endmodule: tb_top Step 3: The Transaction Descriptor The next step is to define the APB transaction descriptor (Rule 4-54).. ..apb_rdata (apb0.clk (clk)). bit clk = 0. . always #10 clk = ~clk..prdata[15:0]). This descriptor is a class (Rule 4-53) extended from the vmm_data class (Rule 4-55).apb_write (apb0. .apb_addr (apb0. apb_if apb0(clk). .penable ).This top-level module also contains the clock generators (Rule 4-15). 4-62) and public properties for each parameter or value in the transaction (Rule 4-59.apb_wdata (apb0. .. containing a public property enumerating the various transactions that can be observed and reacted to by the slave transactor (Rule 4-60. super. endfunction: new .File: apb/apb_rw.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. enum {READ.new(this.. logic [31:0] data. static vmm_log log = new("apb_rw". it is interpreted as the data to be written.sv" class apb_rw extends vmm_data.log)... Although the transaction descriptor is not yet VMM-compliant. there can only be one data value valid at any given time. function new(). `endif A single property is used for “data”. despite the fact that the APB bus has separate read and write data buses. endclass: apb_rw . it is the data value that is to be driven as the read value. "class"). The data class property is interpreted differently depending on the transaction kind (Rule 4-71). The type for the data property is logic as it will allow the description of READ cycles to reflect unknown results. bit [31:0] addr. WRITE} kind. In a WRITE transaction. it is has the minimum functionality to be used by a slave transactor. VMM Primer: Writing Command-Layer Slave Transactors 9 . In a READ transaction. Because the APB does not support concurrent read/write transactions.. . this.. 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.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV ...slave sigs.sigs = sigs. . function new(string name. virtual apb_if.sv" .. . class apb_slave extends vmm_xactor.sv" `include "apb_rw. .).Step 4: The Slave Transactor The slave transactor can now be started. File: apb/apb_slave. super...sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if.slave sigs. virtual apb_if. It is a class (Rule 4-91) derived from the vmm_xactor base class (4-92).. int unsigned stream_id. endclass: apb_slave `endif The transactor needs a physical-level interface to observe and react to transactions.. . class apb_slave extends vmm_xactor. name. stream_id)....new("APB Slave". VMM Primer: Writing Command-Layer Slave Transactors 10 . stream_id).sigs = sigs. endfunction: new . forever begin VMM Primer: Writing Command-Layer Slave Transactors 11 ..... this. virtual protected task main(). The only difference is that the physical signals are accessed through the clocking block of the virtual modport instead of pins on a module. It is a simple matter of sampling input signals and driving an appropriate response at the right point in time. function new(string name.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if.). virtual apb_if.sv" .main().endfunction: new .. super. 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).... File: apb/apb_slave. The observation and reaction to READ and WRITE transactions is coded exactly as it would be if good old Verilog was used. . super. class apb_slave extends vmm_xactor. ..sv" `include "apb_rw. name..new("APB Slave". 4-12).slave sigs.. virtual apb_if. int unsigned stream_id. not an edge of an input signal (Rule 4-7.. . The active clock edge is defined by waiting on the clocking block itself.slave sigs. it must not be stopped until it completes the APB transaction.sck).log.sck). // Wait for a SETUP cycle do @ (this. the vmm_xactor::wait_if_stopped() method is called only before the beginning of transaction.sigs.data.addr = this. end . if the transactor is stopped in the middle of a transaction stream.sigs. this. Otherwise.kind = (this.prdata <= ’z.. .pwdata.sck. it will violate the protocol.sck).sigs.prdata <= tr.. .psel !== 1'b1 || this. tr. @ (this.paddr.sigs. while (this.sigs.sigs..pwrite) ? apb_rw::WRITE : apb_rw::READ. if (tr. tr. end else begin @ (this.data = this.sigs.sigs.sck.sck.kind == apb_rw::READ) begin ..sck. end if (this..sigs..sck.sck.sck.sck.sigs.penable !== 1'b0). tr.sigs. tr = new.penable !== 1'b1) begin `vmm_error(this.. this. end endtask: main endclass: apb_slave `endif Once a slave transactor recognizes the start of a transaction. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). VMM Primer: Writing Command-Layer Slave Transactors 12 .apb_rw tr. Therefore.. .addr = this.kind == apb_rw::READ) begin . class apb_slave extends vmm_xactor..sigs. forever begin apb_rw tr.sigs.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if... if (tr. virtual protected task main(). tr..sck. while (this.sigs.pwdata. virtual apb_if.slave sigs.sigs.data.sigs. this....kind = (this.slave sigs.sck..pwrite) ? apb_rw::WRITE : apb_rw::READ.sck. name. .sigs.sck.sigs.sck. .sck).sv" .sck..wait_if_stopped().main(). this.sck).. // Wait for a SETUP cycle do @ (this. endfunction: new . .sigs = sigs.. . end else begin @ (this. function new(string name. super. super.sck.sck). this.psel !== 1'b1 || this.. virtual apb_if. VMM Primer: Writing Command-Layer Slave Transactors 13 .). tr = new.sv" `include "apb_rw.sigs. .sigs. @ (this. stream_id).prdata <= ’z.data = this. this. tr.paddr.File: apb/apb_slave.sigs..penable !== 1'b0). tr.prdata <= tr.new("APB Slave"... int unsigned stream_id. File: apb/apb_slave. 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. 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.slave sigs..sigs = sigs.penable !== 1'b1) begin `vmm_error(this.new("APB Slave". .log. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). this. . ..sck.. Data returned in READ cycles is taken from the specified address. name. an associative array should be used.sv" `include "apb_rw..slave sigs. unknowns (’bx) are returned. Instead. int unsigned stream_id. end .end if (this.. class apb_slave extends vmm_xactor. virtual apb_if. endfunction: new VMM Primer: Writing Command-Layer Slave Transactors 14 . Data from WRITE cycles is written at the specified address. If an address is read that has never been written before.sv" ..sigs...). stream_id). super. function new(string name.. local bit [31:0] ram[*].. virtual apb_if.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. sck.sck.sigs.ram[tr. .data = this.addr = this. tr. end if (this.sck). end else begin @ (this.log.sigs.data = ’x. forever begin apb_rw tr.wait_if_stopped().sigs..sck.sigs.sck.sck.ram[tr.sigs. ..main(). this.addr] = tr.kind = (this.sck.prdata <= ’z. this.sigs.sigs.sigs.ram.penable !== 1'b1) begin `vmm_error(this. end .. if (tr. end endtask: main endclass: apb_slave `endif VMM Primer: Writing Command-Layer Slave Transactors 15 .addr)) tr.pwrite) ? apb_rw::WRITE : apb_rw::READ. . virtual protected task main(). @ (this.sck.prdata <= tr.paddr. tr = new.data = this. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle")..sck).sigs. while (this..psel !== 1'b1 || this.kind == apb_rw::READ) begin if (!this.sck.sigs.sck).addr]. this.pwdata. tr. tr.sigs..data...penable !== 1'b0).. else tr.. // Wait for a SETUP cycle do @ (this. super.exists(tr.. this.data. super. local bit [31:0] ram[*]... . this.... endfunction: new . typedef enum {RESPONSE} notifications_e. super. this.sv" `include "apb_rw. .. int unsigned stream_id.sv" . The observed transaction..sigs = sigs.main(). stream_id). File: apb/apb_slave. virtual protected task main().notify.slave sigs.slave sigs. virtual apb_if. virtual apb_if. . being derived from vmm_data. ... VMM Primer: Writing Command-Layer Slave Transactors 16 . name..).sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if.. is attached to a newly defined “RESPONSE” notification and can be recovered by all interested parties waiting on the notification. function new(string name..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.new("APB Slave".configure(RESPONSE). Observed transactions can be reported by indicating a notification in the vmm_xactor::notify property. Simply displaying the transactions is not very useful as it will require that the results be manually checked every time. class apb_slave extends vmm_xactor. psel !== 1'b1 || this.sck).. if (tr. end else begin @ (this.data.addr = this.sigs.data = this. @ (this. .sigs.penable !== 1'b1) begin `vmm_error(this.sck.pwrite) ? apb_rw::WRITE : apb_rw::READ.addr] = tr. this. tr.addr)) tr.data = this.notify.sck. .addr]. this. tr. end if (this.ram[tr.penable !== 1'b0). // Wait for a SETUP cycle do @ (this..sck.prdata <= tr.sigs. tr = new.sigs...kind == apb_rw::READ) begin if (!this.sck.ram[tr. tr).. while (this. end . this.sck).sigs. this...indicate(RESPONSE.data = ’x.sigs. this.wait_if_stopped().pwdata.sck.sck.prdata <= ’z.sck. tr.log.exists(tr.ram.sigs.paddr. else tr. end endtask: main endclass: apb_slave `endif VMM Primer: Writing Command-Layer Slave Transactors 17 . .sck).. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").sigs.sigs.kind = (this.sigs.forever begin apb_rw tr.data.sck.sigs. start_xactor(). this. apb_slave slv. .. 0..slv.. 4-35) and started in the extension of the vmm_env::start() method (Rule 4-41)..start(). extended from the vmm_env base class. endclass: tb_env `endif VMM Primer: Writing Command-Layer Slave Transactors 18 ..Step 6: The First Test Although not completely VMM-compliant. the slave transactor can now be used to reply to activity on an APB bus. endfunction: build virtual task start(). The slave transactor is constructed in the extension of the vmm_env::build() method (Rule 4-34. It is instantiated in a verification environment class.apb0).sv `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm. super. virtual function void build(). endtask: start .sv" `include "apb_slave.sv" class tb_env extends vmm_env. super. this. ..slv = new("0". 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. tb_top. File: Command_Slave_Xactor/tb_env.build(). slv.end_test). end join_none endtask: start virtual task wait_for_end(). 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.apb0).notify. virtual function void build(). super.As the simulation progress.stop_after--. this.slv. super. VMM Primer: Writing Command-Layer Slave Transactors 19 .wait_for(apb_slave::RESPONSE). this. fork forever begin apb_rw tr.start(). the slave transactor reports the completed transactions.sv" `include "apb_slave. int stop_after = 10. $cast(tr.sv" class tb_env extends vmm_env.slv = new("0".start_xactor(). this.end_test.stop_after <= 0) -> this. tb_top.notify. this.display("Responded: "). File: Command_Slave_Xactor/tb_env.status(apb_slave::RESPONSE)). if (this.build().sv `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm. this. @ (this.wait_for_end().slv. super. apb_slave slv. tr. 0. endfunction: build virtual task start(). File: Command_Slave_Xactor/test_simple `include "tb_env. env. The test is written in a program (Rule 4-27) that instantiates the verification environment (Rule 4-28). $finish().run(). 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").sv" program simple_test.stop_after = 5. For that method to display the information that is relevant for the APB transaction. initial begin env. VMM Primer: Writing Command-Layer Slave Transactors 20 . defined in the vmm_data base class does not know about the content of the APB transaction descriptor. tb_env env = new.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. vmm_log log = new("Test". it is necessary to overload this method in the transaction descriptor class (Rule 4-76). this.addr. prefix.File: apb/apb_rw.data). this. $sformat(psdisplay.sv `ifndef APB_RW__SV `define APB_RW__SV VMM Primer: Writing Command-Layer Slave Transactors 21 .. WRITE} kind. endfunction: psdisplay .sv" class apb_rw extends vmm_data. endclass: apb_rw . endfunction: new . "%sAPB %s @ 0x%h = 0x%h". "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.. `endif Re-running the test now yields useful and meaningful transaction response reporting.. bit [31:0] addr. comparing transaction descriptors (vmm_data::compare()) and checking that the content of transaction descriptors is valid (vmm_data::is_valid()) (Rule 4-76).log).. super.kind. copying transaction descriptors (vmm_data::copy()).name(). this. File: apb/apb_rw. function new(). static vmm_log log = new("apb_rw".new(this. virtual function string psdisplay(string prefix = ""). These common operations include creating transaction descriptors (vmm_data::allocate()).. logic [31:0] data.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm.. kind. bit [31:0] addr.data). function new(). endfunction: copy virtual function string psdisplay(string prefix = "").new(this. static vmm_log log = new("apb_rw".name().kind = this. tr.data = this. enum {READ. to)) begin `vmm_fatal(log.kind. apb_rw tr = new. "Cannot copy into non-apb_rw \n instance"). endfunction: allocate virtual function vmm_data copy(vmm_data to = null). return null. endfunction: is_valid VMM Primer: Writing Command-Layer Slave Transactors 22 .`include "vmm. WRITE} kind. else if (!$cast(tr. this. return tr.log).data.copy_data(tr).sv" class apb_rw extends vmm_data. int kind = -1). if (to == null) tr = new. return tr.addr. tr. endfunction: psdisplay virtual function bit is_valid(bit silent = 1. this. logic [31:0] data.addr. endfunction: new virtual function vmm_data allocate(). prefix. "class").addr = this. $sformat(psdisplay. return 1. tr. super. end super. apb_rw tr. this. "%sAPB %s @ 0x%h = 0x%h". this.kind). output string diff.kind.addr) begin $sformat(diff. "Addr 0x%h !== 0x%h". return 0.. end else if (!$cast(tr.data !== tr. if (to == null) begin `vmm_fatal(log. input int kind = -1).addr !== tr. "Cannot compare against \n non-apb_rw instance"). "Data 0x%h !== 0x%h". this. return 0. end if (this. return 0.data). tr. end if (this. end return 1. endfunction: compare endclass: apb_rw . tr. "Kind %s !== %s". `endif VMM Primer: Writing Command-Layer Slave Transactors 23 .data.addr).kind) begin $sformat(diff. to)) begin `vmm_fatal(log. this. return 0.data) begin $sformat(diff.. return 0.virtual function bit compare(input vmm_data to.addr. apb_rw tr. end if (this. tr. "Cannot compare to NULL reference").kind != tr. virtual apb_if. This capability may even be a basic requirement if you plan on shipping encrypted or compiled code. VMM Primer: Writing Command-Layer Slave Transactors 24 . function new(string name.. Step 8: Debug Messages To be truly reusable. Debug messages should be added at judicious points to indicate what the slave transactor is about to do.. These debug messages are inserted using the `vmm_trace()...sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if.. is doing or has done. int unsigned stream_id.. .slave sigs. vmm_data:byte_size().sv" . class apb_slave extends vmm_xactor. where the content of the transaction is transmitted over a physical interface (Recommendation 4-77). vmm_data::byte_pack() and vmm_data::byte_unpack() should also be overloaded for packet-oriented transactions.slave sigs. `vmm_debug() or `vmm_verbose() macros (Recommendation 451). local bit [31:0] ram[*]. it should be possible to understand what the slave transactor does and debug its operation without having to inspect the source code. typedef enum {RESPONSE} notifications_e. virtual apb_if. File: apb/apb_slave.Three other standard methods.sv" `include "apb_rw. . data. .psel !== 1'b1 || this.sigs.).ram[tr.sck)..sck.. "Waiting for start of transaction.notify.pwrite) ? apb_rw::WRITE : apb_rw::READ.sck. end if (this. virtual protected task main()..sigs.new("APB Slave". . @ (this.prdata <= ’z.penable !== 1'b0).sigs. . stream_id).sigs..addr = this. else tr.data.sck.exists(tr.sigs = sigs."). this.pwdata.sck. this. this.addr].sck.. tr. tr. end VMM Primer: Writing Command-Layer Slave Transactors 25 .sck).prdata <= tr..sck..data = ’x. this.sigs. end else begin @ (this. // Wait for a SETUP cycle do @ (this. this.sigs. while (this.sigs.. . super.psdisplay(" ")})..main().\n".. if (tr.addr] = tr.paddr.sigs.kind == apb_rw::READ) begin if (!this.ram[tr.kind = (this. tr = new. forever begin apb_rw tr.penable !== 1'b1) begin `vmm_error(this. `vmm_trace(log.sck..sck. this.sigs.....ram.addr)) tr. {"Responding to transaction. endfunction: new . super..data = this. tr. tr.sigs.log. `vmm_trace(log.data = this.configure(RESPONSE).. name.wait_if_stopped().sck).sigs. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). A constraint block is specified to guarantee that any random configuration is valid (Rule 4-80).. The slave is configured using a configuration descriptor (Rule 4-105). The slave configuration is specified as an optional argument to the constructor (Rule 4-106). 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.sv `ifndef APB_SLAVE__SV VMM Primer: Writing Command-Layer Slave Transactors 26 .notify. The slave must thus be configurable to limit its response to a specified address range (Rule 4-104). this. File: apb/apb_slave. Step 9: Slave Configuration The slave transactor. tr.\n". It would not be able to cooperate with other slaves on the same bus mapped to different address ranges.. `vmm_trace(log. tr). 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.. as currently coded... {"Responded to transaction. responds to any and all transactions on the APB bus..psdisplay(" ")}). File: Makefile % vcs –sverilog –ntb_opts vmm +vmm_log_default=trace .indicate(RESPONSE.. class apb_slave extends vmm_xactor.wait_if_stopped().main().slave sigs. } . constraint apb_slave_cfg_valid { end_addr >= start_addr.. this. typedef enum {RESPONSE} notifications_e.`define APB_SLAVE__SV `include "apb_if. if (cfg == null) cfg = new.slave sigs. this. endfunction: new ... virtual protected task main(). rand bit [31:0] end_addr = 32’hFFFF_FFFF.sck.. "Waiting for start of transaction. local apb_slave_cfg cfg..).sigs.notify.. `vmm_trace(log. VMM Primer: Writing Command-Layer Slave Transactors 27 .new("APB Slave".prdata <= ’z.sv" `include "apb_rw. stream_id).configure(RESPONSE). this.. int unsigned stream_id."). virtual apb_if. rand bit [31:0] start_addr = 32’h0000_0000... this. this. endclass: apb_slave_cfg . forever begin apb_rw tr. . name..sv" class apb_slave_cfg.. local bit [31:0] ram[*].. ..sigs = sigs.. super. .cfg = cfg. apb_slave_cfg cfg = null. function new(string name.. virtual apb_if.. super. . . tr.notify. this..addr = this.indicate(RESPONSE. `vmm_trace(log...sck.sck.sigs.sigs. {"Responded to transaction. ....sigs..paddr.pwrite) ? apb_rw::WRITE : apb_rw::READ.penable !== 1'b0 || this.data.addr] = tr.psdisplay(" ")}). end if (this.penable !== 1'b1) begin `vmm_error(this. end else begin @ (this.kind == apb_rw::READ) begin if (!this.psel !== 1'b1 || this. .sigs.sck).cfg. {"Responding to transaction.sck.ram..\n". // Wait for a SETUP cycle do @ (this.sigs. @ (this. tr.sck.sigs.sigs.sigs.end_addr). This occurrence of a direct VMM Primer: Writing Command-Layer Slave Transactors 28 .sck. else tr.sck.psdisplay(" ")}).sigs.sck. while (this. tr.cfg.addr].sigs. if (tr.start_addr || this.sck. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). this. `vmm_trace(log..data = this.sigs.ram[tr.\n"..data. tr).prdata <= tr.sigs.ram[tr.paddr < this.data = ’x.kind = (this.pwdata.sck).sck. end .data = this..sck).log.addr)) tr. tr = new.paddr > this. this. tr. 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..exists(tr. . rand bit [31:0] start_addr = 32’h0000_0000. rand bit [31:0] end_addr = 32’hFFFF_FFFF... constraint apb_slave_cfg_valid { end_addr >= start_addr. endclass: apb_slave_cfg ...slave sigs. VMM Primer: Writing Command-Layer Slave Transactors 29 . if (cfg == null) cfg = new..slave sigs. function new(string name. File: apb/apb_slave.cfg = cfg.sigs = sigs. local apb_slave_cfg cfg. virtual apb_if. apb_slave_cfg cfg = null. . int unsigned stream_id. but transactors implementing more complex protocols may very well need to be told that they are being reconfigured. } ... . virtual apb_if. class apb_slave extends vmm_xactor. stream_id). a reconfigure() method is provided (Rule 4-107)..new("APB Slave". local bit [31:0] ram[*]. name. this. ..sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if.. typedef enum {RESPONSE} notifications_e.modification would not be visible to the slave transactor..sv" class apb_slave_cfg. it would not have any ill effects. To that effect.sv" `include "apb_rw. Because of the simplicity of the slave functionality and its configuration.).. super. this. `vmm_trace(log.. `vmm_trace(log.data.paddr. if (tr. forever begin apb_rw tr.").sigs.sck..sigs. this. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg). // Wait for a SETUP cycle do @ (this.data = this.cfg = cfg. this..paddr > this.psdisplay(" ")})..addr)) tr.. end else begin @ (this.sigs.kind = (this..ram[tr..kind == apb_rw::READ) begin if (!this. .end_addr).sigs.sck).\n".sck.configure(RESPONSE).wait_if_stopped(). tr. .exists(tr.addr = this.sck.data = this.sigs. this. tr.cfg. else tr. while (this.log. this.sigs. super. tr = new.sigs.data = ’x. endfunction: reconfigure .cfg.ram.penable !== 1'b0 || this..prdata <= tr. "APB protocol violation:\n VMM Primer: Writing Command-Layer Slave Transactors 30 .start_addr || this.sigs.this.penable !== 1'b1) begin `vmm_error(this. "Waiting for start of transaction.sck).. tr.sck..sck.ram[tr.main().pwrite) ? apb_rw::WRITE : apb_rw::READ. end if (this.prdata <= ’z. @ (this.sigs. {"Responding to transaction.sck..sigs.paddr < this.sigs. virtual protected task main().pwdata.sigs..sck. this. .sck.addr] = tr.sck).psel !== 1'b1 || this.notify. tr.addr].data.sck.sck.sigs. prefix.. {"Responded to transaction.sv" class apb_slave_cfg.\n".. "%sAPB Slave Config: [‘h%h-‘h%h]".sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if.indicate(RESPONSE. tr). Nevertheless.start_addr. class apb_slave extends vmm_xactor.. `vmm_trace(log. with a signature identical to vmm::psdisplay() to make it easier to report the current transactor configuration during simulation. this. File: apb/apb_slave.. this. VMM Primer: Writing Command-Layer Slave Transactors 31 . endfunction: psdisplay endclass: apb_slave_cfg .psdisplay(" ")}). 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.SETUP cycle not followed by ENABLE cycle").end_addr).. end . the psdisplay() method should be provided. tr. this.notify. constraint apb_slave_cfg_valid { end_addr >= start_addr. } virtual function string psdisplay(string prefix = ""). rand bit [31:0] start_addr = 32’h0000_0000.. $sformat(psdisplay.sv" `include "apb_rw. rand bit [31:0] end_addr = 32’hFFFF_FFFF. local apb_slave_cfg cfg.penable !== 1'b0 || this..pwrite) ? apb_rw::WRITE : apb_rw::READ. . // Wait for a SETUP cycle do @ (this. .paddr > this.sck).addr = this.sigs. tr. this. tr = new. super.cfg. . stream_id).wait_if_stopped(). this. "Waiting for start of transaction.cfg.sigs. this.notify.sigs.). this.. endfunction: reconfigure .slave sigs.. int unsigned stream_id.virtual apb_if. super.sck.cfg = cfg.start_addr || this.sck..sigs = sigs.new("APB Slave".paddr < this.kind = (this.end_addr).slave sigs.sigs.sck. VMM Primer: Writing Command-Layer Slave Transactors 32 .configure(RESPONSE). . if (cfg == null) cfg = new.prdata <= ’z..sck..... local bit [31:0] ram[*].. `vmm_trace(log. function new(string name.main().sigs. . typedef enum {RESPONSE} notifications_e. tr.sck.cfg = cfg.sigs."). virtual apb_if..paddr.. this.sigs. forever begin apb_rw tr.sigs... virtual protected task main(). endfunction: new virtual function void reconfigure(apb_slave_cfg cfg).psel !== 1'b1 || this. name. this. apb_slave_cfg cfg = null. while (this.sck.sck. data.prdata <= tr. tr. this. it is useful to have a procedural interface to the content of the RAM. end else begin @ (this. {"Responding to transaction.. This interface allows querying the RAM at a specific address as well as setting values at specific addresses. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").sck).sigs.psdisplay(" ")}). this.data = this..notify..kind == apb_rw::READ) begin if (!this. `vmm_trace(log.sigs. .addr] = tr.. end endtask: main endclass: apb_slave `endif Step 10: Backdoor Interface To help predict or control the response of the slave.ram. this. tr)..data = this.\n".sck).`vmm_trace(log. a procedure could be provided to delete the data value at a specified address to reclaim its memory from the associated array.psdisplay(" ")}). Optionally.indicate(RESPONSE. end if (this. else tr. @ (this. if (tr...penable !== 1'b1) begin `vmm_error(this.sigs.data.addr]..ram[tr.addr)) tr. {"Responded to transaction.sigs. tr.sck. VMM Primer: Writing Command-Layer Slave Transactors 33 .pwdata. end .sck. .sck.log.\n". tr.exists(tr.sigs.data = ’x...ram[tr. slave sigs.start_addr.. int unsigned stream_id. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg).sigs = sigs. local bit [31:0] ram[*]. this..sv" `include "apb_rw. . this. rand bit [31:0] end_addr = 32’hFFFF_FFFF. prefix.. virtual apb_if.sv" class apb_slave_cfg. .sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if..cfg = cfg. . } virtual function string psdisplay(string prefix = "").).slave sigs. function new(string name.. constraint apb_slave_cfg_valid { end_addr >= start_addr. name. this. virtual apb_if. endfunction: psdisplay endclass: apb_slave_cfg . apb_slave_cfg cfg = null. if (cfg == null) cfg = new. rand bit [31:0] start_addr = 32’h0000_0000..configure(RESPONSE).new("APB Slave".. "%sAPB Slave Config: [‘h%h-‘h%h]".. stream_id). $sformat(psdisplay.end_addr).cfg = cfg.File: apb/apb_slave.notify... endfunction: reconfigure VMM Primer: Writing Command-Layer Slave Transactors 34 . this. this. class apb_slave extends vmm_xactor. local apb_slave_cfg cfg. this. super. typedef enum {RESPONSE} notifications_e. . sck. end return (this. tr."). {"Responding to transaction.sigs.penable !== 1'b0 || this.sigs. return ’x.sck.ram[addr] : ’x.. virtual protected task main().cfg.kind == apb_rw::READ) begin if (!this.addr = this.sck. endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr).sigs. if (addr < this.sck.addr)) tr.end_addr) begin `vmm_error(this.exists(tr.log.cfg. endfunction: peek . // Wait for a SETUP cycle do @ (this.sigs.sck.psel !== 1'b1 || this.end_addr) begin `vmm_error(this.end_addr). end this.sck). "Out-of-range peek").paddr..log.cfg.ram..prdata <= ’z.paddr > this....start_addr || addr > this.\n".exists(addr)) ? this.paddr < this.start_addr || this. tr. return. "Out-of-range poke"). "Waiting for start of transaction..cfg.ram.sigs.cfg.main(). tr.sigs. super. this. if (addr < this. .kind = (this. bit [31:0] data).data = ’x..virtual function void poke(bit [31:0] addr.sigs.sigs. if (tr. this.start_addr || addr > this.psdisplay(" ")}).sck. while (this. `vmm_trace(log. VMM Primer: Writing Command-Layer Slave Transactors 35 . forever begin apb_rw tr.wait_if_stopped().sck.cfg. `vmm_trace(log.ram[addr] = data. tr = new.pwrite) ? apb_rw::WRITE : apb_rw::READ. .sck)..data = this.log.pwdata. the only errors that could be injected are wrong addresses and wrong data.data = this.notify. end else begin @ (this. A callback method allows a user to extend the behavior of a slave transactor without having to modify the transactor itself. tr. `vmm_trace(log. Callback methods should be provided before the response to a transaction is sent back (Recommendation 4-156. {"Responded to transaction.data. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").sigs. @ (this..prdata <= tr.sck). 4-158) and after the transaction VMM Primer: Writing Command-Layer Slave Transactors 36 .sigs. end ..data.. this.indicate(RESPONSE.sck.addr] = tr. .sigs.psdisplay(" ")}).. In this simple protocol.addr]. tr.sigs. functional verification also entails the injection of errors to verify that the design reacts properly to those errors.else tr.sigs.sck. A more complex protocol would probably have parity bits that could be corrupted or responses that could be delayed.ram[tr.ram[tr. end endtask: main endclass: apb_slave `endif Step 11: Extension Points This slave transactor should behave correctly by default. end if (this. tr).penable !== 1'b1) begin `vmm_error(this. .sck. this.\n". Although the correct behavior is desired most of the time.. this.. The “preresponse” callback method allows errors to be injected and delays to be inserted.response has completed (Recommendation 4-155). using the `vmm_callback() macro (Rule 4-163). virtual function void pre_response(apb_slave xact. Next. constraint apb_slave_cfg_valid { end_addr >= start_addr.sv" `include "apb_rw. the appropriate callback method needs to be invoked at the appropriate point in the execution of the monitor. $sformat(psdisplay.end_addr). prefix. VMM Primer: Writing Command-Layer Slave Transactors 37 .sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. rand bit [31:0] end_addr = 32’hFFFF_FFFF. "%sAPB Slave Config: [‘h%h-‘h%h]". this. rand bit [31:0] start_addr = 32’h0000_0000. class apb_slave_cbs extends vmm_xactor_callbacks. 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. endfunction: psdisplay endclass: apb_slave_cfg typedef class apb_slave. 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). } virtual function string psdisplay(string prefix = ""). apb_rw cycle).start_addr. File: apb/apb_slave. this.sv" class apb_slave_cfg. ram[addr] = data. "Out-of-range poke").new("APB Slave".cfg. stream_id).cfg = cfg. super.cfg.end_addr) begin `vmm_error(this. this. endfunction: reconfigure virtual function void poke(bit [31:0] addr.. ... virtual apb_if.. . function new(string name.slave sigs. endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr).cfg = cfg.slave sigs.start_addr || addr > this.. typedef enum {RESPONSE} notifications_e. local bit [31:0] ram[*].sigs = sigs. if (addr < this. .endfunction: pre_response virtual function void post_response(apb_slave xactor. ..notify. virtual apb_if..). end this.start_addr || addr > this. if (cfg == null) cfg = new.cfg.end_addr) begin VMM Primer: Writing Command-Layer Slave Transactors 38 . apb_slave_cfg cfg = null. this.configure(RESPONSE). this. int unsigned stream_id.. return. if (addr < this. local apb_slave_cfg cfg. this. bit [31:0] data). endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor.cfg. apb_rw cycle). name.log. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg). .addr]. pre_response(this.addr = this.sigs. tr.end_addr).sigs. {"Responding to transaction.cfg.sck.sigs.exists(tr.sigs.`vmm_error(this. endfunction: peek .data.sigs. @ (this.ram.sck. "Waiting for start of transaction. forever begin apb_rw tr. pre_response(this. tr)).sck.\n"."). this.sigs.kind = (this..sigs. .exists(addr)) ? this. tr. end else begin @ (this.data = this.sck).kind == apb_rw::READ) begin if (!this. // Wait for a SETUP cycle do @ (this.. `vmm_callback(apb_slave_cbs.paddr > this. return ’x. VMM Primer: Writing Command-Layer Slave Transactors 39 . else tr.sck.sck. this.sck).sigs.wait_if_stopped(). tr. `vmm_trace(log. virtual protected task main().penable !== 1'b0 || this.ram. if (tr.start_addr || this.sck.sigs. `vmm_callback(apb_slave_cbs.sigs.sigs..data = ’x.. while (this.sck.sck.. super..pwdata.data = this.paddr < this. tr = new.pwrite) ? apb_rw::WRITE : apb_rw::READ.paddr.ram[tr.prdata <= tr.. tr. `vmm_trace(log.sigs.psdisplay(" ")}). this.log.ram[addr] : ’x.addr)) tr.cfg.sck).prdata <= ’z.main(). end return (this.psel !== 1'b1 || this.sck. "Out-of-range peek"). tr)). post_response(this.ram[tr.sv" class apb_rw extends vmm_data.sigs.addr] = tr. That is fine in most cases. {"Responded to transaction.indicate(RESPONSE.\n". 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). `vmm_trace(log...data.penable !== 1'b1) begin `vmm_error(this.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. tr. end `vmm_callback(apb_slave_cbs. this. tr)).sck.this. A transaction-level interface carrying APB transaction descriptor is defined by using the `vmm_channel macro. File: apb/apb_rw. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). tr). VMM Primer: Writing Command-Layer Slave Transactors 40 . but it makes the transactor unsuitable for providing a different response or to use it to write transaction-level models of slave devices. end if (this.notify. end endtask: main endclass: apb_slave `endif Step 12: Transaction Response The slave transactor is currently hard-coded to respond like a RAM.psdisplay(" ")}).log. data = this. function new(). "class").kind. endfunction: psdisplay virtual function bit is_valid(bit silent = 1. this. tr.kind = this. tr. return tr.addr = this. return 1.name(). if (to == null) tr = new. "Cannot copy into non-apb_rw \n instance"). return tr. $sformat(psdisplay. else if (!$cast(tr. output string diff.data. prefix.log). rand enum {READ. endfunction: allocate virtual function vmm_data copy(vmm_data to = null). input int kind = -1). "%sAPB %s @ 0x%h = 0x%h". WRITE} kind. rand logic [31:0] data.kind. VMM Primer: Writing Command-Layer Slave Transactors 41 .addr. to)) begin `vmm_fatal(log. this.data).static vmm_log log = new("apb_rw". this. endfunction: new virtual function vmm_data allocate(). return null. apb_rw tr = new. int kind = -1). endfunction: copy virtual function string psdisplay(string prefix = ""). apb_rw tr. endfunction: is_valid virtual function bit compare(input vmm_data to.addr. rand bit [31:0] addr. tr. super. end super.new(this.copy_data(tr). addr !== tr.data) begin $sformat(diff.addr. return 0. end if (this. end if (this.kind) begin $sformat(diff.data !== tr. this. to)) begin `vmm_fatal(log.kind.data. tr. "Kind %s !== %s".addr) begin $sformat(diff.addr). end else if (!$cast(tr. if (to == null) begin `vmm_fatal(log. "Cannot compare against \n non-apb_rw instance"). return 0. VMM Primer: Writing Command-Layer Slave Transactors 42 .kind != tr. end return 1. the response defaults to the RAM response. return 0. return 0. tr. tr.kind).apb_rw tr. "Cannot compare to NULL reference"). "Addr 0x%h !== 0x%h". this.data). "Data 0x%h !== 0x%h". If no channel is specified. return 0. this. end if (this. 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). . rand bit [31:0] start_addr = 32’h0000_0000. resp_chan = null. virtual apb_if. cfg = null.start_addr. endfunction: pre_response virtual function void post_response(apb_slave xactor. "%sAPB Slave Config: [‘h%h-‘h%h]"... apb_rw cycle). endfunction: psdisplay endclass: apb_slave_cfg typedef class apb_slave. this. function new(string int unsigned virtual apb_if. this. virtual function void pre_response(apb_slave xact. class apb_slave_cbs extends vmm_xactor_callbacks. } virtual function string psdisplay(string prefix = ""). $sformat(psdisplay.slave apb_slave_cfg apb_rw_channel .sv" class apb_slave_cfg.. local bit [31:0] ram[*]. rand bit [31:0] end_addr = 32’hFFFF_FFFF.slave sigs. prefix.File: apb/apb_slave.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. apb_rw_channel resp_chan.). stream_id.sv" `include "apb_rw. typedef enum {RESPONSE} notifications_e. local apb_slave_cfg cfg. .end_addr). endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor. name. sigs. apb_rw cycle). constraint apb_slave_cfg_valid { end_addr >= start_addr. VMM Primer: Writing Command-Layer Slave Transactors 43 . ram[addr] : ’x.cfg = cfg.cfg.sigs = sigs.notify.. return. this. this.ram[addr] = data.sigs."). stream_id)..exists(addr)) ? this. end return (this.new("APB Slave".super. . if (addr < this.cfg. "Out-of-range peek"). name.log.. this. return ’x..wait_if_stopped(). endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr).cfg. this.cfg = cfg.sck). . endfunction: reconfigure virtual function void poke(bit [31:0] addr. // Wait for a SETUP cycle do @ (this.resp_chan = resp_chan. bit [31:0] data).. this.cfg.. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg). if (cfg == null) cfg = new.ram. "Out-of-range poke"). end this. this.sigs.sck.end_addr) begin `vmm_error(this.psel !== 1'b1 || VMM Primer: Writing Command-Layer Slave Transactors 44 .sck.log. this.end_addr) begin `vmm_error(this. super. `vmm_trace(log. forever begin apb_rw tr. "Waiting for start of transaction. endfunction: peek ...start_addr || addr > this.start_addr || addr > this.sigs.main(). while (this.configure(RESPONSE). virtual protected task main().prdata <= ’z. if (addr < this. else tr.paddr > this. tr)).sigs.prdata <= tr.sck.sigs.data. tr.sck.. tr)).exists(tr. else this. tr. end `vmm_callback(apb_slave_cbs. `vmm_trace(log.cfg.this. pre_response(this.sigs.penable !== 1'b0 || this.data = this. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle")..resp_chan.resp_chan == null) this.. `vmm_callback(apb_slave_cbs. pre_response(this. tr = new.data = ’x.sigs.sck). end else begin . tr. if (tr. end else begin @ (this.psdisplay(" ")}).pwrite) ? apb_rw::WRITE : apb_rw::READ. end if (this.sck.sck.sneak(tr).sigs.data = this. post_response(this.sigs. .sigs.resp_chan == null) begin if (!this.penable !== 1'b1) begin `vmm_error(this.sck. tr.sck.log.ram[tr.addr)) tr.end_addr). tr)). VMM Primer: Writing Command-Layer Slave Transactors 45 .kind = (this.. @ (this.addr = this. {"Responding to transaction.ram.sigs.kind == apb_rw::READ) begin if (this. this. end `vmm_callback(apb_slave_cbs.paddr.paddr < this.sck).. if (this.pwdata.addr].addr] = tr.ram[tr.sck.sigs. this..data.\n".sigs.cfg.sck.put(tr).resp_chan.start_addr || this. the transaction descriptor contains the data to return to the DUT. 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. {"Responded to transaction. a blocking response model is expected from the response transactor.sv" `include "apb_rw.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if..indicate(RESPONSE.psdisplay(" ")}).\n".sv" class apb_slave_cfg. VMM Primer: Writing Command-Layer Slave Transactors 46 . the slave transactor uses the vmm_channel::sneak() method to put the transaction descriptor on the response request channel. tr). rand bit [31:0] end_addr = 32’hFFFF_FFFF. 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. File: apb/apb_slave.`vmm_trace(log. When the vmm_channel::put() method returns. rand bit [31:0] start_addr = 32’h0000_0000. Because there is no feedback on WRITE cycles in the APB protocol. the slave transactor simply notifies the response transactor of the WRITE transaction and does not need to wait for a response. this.notify. tr. When requesting the response to a READ transaction. end endtask: main endclass: apb_slave `endif When requesting the response to a WRITE transaction. constraint apb_slave_cfg_valid { end_addr >= start_addr.. this. virtual function void pre_response(apb_slave xact.} virtual function string psdisplay(string prefix = ""). apb_rw cycle)... apb_rw_channel resp_chan.configure(RESPONSE).new("APB Slave".cfg = cfg. if (cfg == null) cfg = new..). this.. this. this.end_addr).slave sigs. this. this. class apb_slave_cbs extends vmm_xactor_callbacks.cfg = cfg.. apb_rw cycle). stream_id). name. .sigs = sigs. virtual apb_if. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg). prefix. local bit [31:0] ram[*]. this. $sformat(psdisplay. . endfunction: pre_response virtual function void post_response(apb_slave xactor. int unsigned stream_id. function new(string name. endfunction: reconfigure VMM Primer: Writing Command-Layer Slave Transactors 47 . typedef enum {RESPONSE} notifications_e.notify. endfunction: psdisplay endclass: apb_slave_cfg typedef class apb_slave.slave sigs. super. . apb_slave_cfg cfg = null. local apb_slave_cfg cfg.. apb_rw_channel resp_chan = null. "%sAPB Slave Config: [‘h%h-‘h%h]". endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor. virtual apb_if.start_addr.resp_chan = resp_chan. "Waiting for start of transaction. {"Responding to transaction..sigs. bit [31:0] data). tr.. return ’x.\n".paddr > this.sck. endfunction: peek .kind = (this.cfg.").pwrite) ? apb_rw::WRITE : apb_rw::READ. this.. forever begin apb_rw tr. . // Wait for a SETUP cycle do @ (this.sck. if (tr.end_addr).sck. `vmm_trace(log.cfg.virtual function void poke(bit [31:0] addr. "Out-of-range peek"). this.ram[addr] : ’x. if (addr < this.kind == apb_rw::READ) begin if (this. while (this.start_addr || this.sck. "Out-of-range poke"). tr.psdisplay(" ")}). endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr)..resp_chan == null) begin VMM Primer: Writing Command-Layer Slave Transactors 48 . tr.sigs.end_addr) begin `vmm_error(this.start_addr || addr > this. super.sigs.cfg.prdata <= ’z. `vmm_trace(log.cfg.sigs.cfg.penable !== 1'b0 || this.ram..sck). tr = new.sigs. end return (this.sck.psel !== 1'b1 || this.addr = this. return.log.main().sigs.wait_if_stopped().sigs.sck.paddr < this.. if (addr < this.sigs..paddr. virtual protected task main().ram[addr] = data.cfg. end this.end_addr) begin `vmm_error(this.exists(addr)) ? this.start_addr || addr > this..sck.log. VMM Primer: Writing Command-Layer Slave Transactors 49 . end join if (abort) continue. else tr. fork begin fork begin @ (this. end this.sigs.sigs. else this.sneak(tr). end `vmm_callback(apb_slave_cbs.data. abort = 1.sck).penable !== 1'b1) begin `vmm_error(this. tr)).log.ram[tr.resp_chan.data = ’x. tr.log. `vmm_callback(apb_slave_cbs. pre_response(this. this.data.data = this. end else begin bit abort = 0.sck. @ (this.ram.resp_chan. tr)).addr] = tr.sck. if (this. tr)).addr)) tr.data = this.resp_chan == null) this.sck.sigs.sigs. end if (this. end else begin @ (this.addr]. end `vmm_callback(apb_slave_cbs.prdata <= tr. pre_response(this.if (!this.sck).sigs.sck). post_response(this.pwdata. join_any disable fork. "No response in time").put(tr).ram[tr. `vmm_error(this.sigs.exists(tr. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle"). the response channel must be flushed and the output signals must be driven to their idle state. tr.`vmm_trace(log. endfunction: pre_response virtual function void post_response(apb_slave xactor. apb_rw cycle). prefix.notify.indicate(RESPONSE. constraint apb_slave_cfg_valid { end_addr >= start_addr. end endtask: main endclass: apb_slave `endif When the transactor is reset.end_addr). $sformat(psdisplay. This behavior is accomplished in the extension of the vmm_xactor::reset_xactor() method (Table A-8). endfunction: psdisplay endclass: apb_slave_cfg typedef class apb_slave.\n".psdisplay(" ")}). } virtual function string psdisplay(string prefix = ""). "%sAPB Slave Config: [‘h%h-‘h%h]".sv" `include "apb_rw.. apb_rw cycle). File: apb/apb_slave. this. {"Responded to transaction.sv" class apb_slave_cfg. rand bit [31:0] start_addr = 32’h0000_0000. this.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. this. rand bit [31:0] end_addr = 32’hFFFF_FFFF. VMM Primer: Writing Command-Layer Slave Transactors 50 .. virtual function void pre_response(apb_slave xact.start_addr. class apb_slave_cbs extends vmm_xactor_callbacks. tr). log. apb_slave_cfg cfg = null.cfg. bit [31:0] data). super.cfg. stream_id). virtual apb_if.. endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr).resp_chan = resp_chan. if (addr < this. ..slave sigs. this.cfg = cfg. if (addr < this. apb_rw_channel resp_chan.). name. typedef enum {RESPONSE} notifications_e.start_addr || addr > this.sigs = sigs. int unsigned stream_id. this..cfg = cfg.end_addr) begin `vmm_error(this.start_addr || addr > this.end_addr) begin `vmm_error(this.slave sigs. .ram[addr] = data.. local apb_slave_cfg cfg.cfg.notify. end this. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg). if (cfg == null) cfg = new. this.configure(RESPONSE). endfunction: reconfigure virtual function void poke(bit [31:0] addr.new("APB Slave".. . VMM Primer: Writing Command-Layer Slave Transactors 51 . return ’x. "Out-of-range poke").endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor. return. "Out-of-range peek").cfg. virtual apb_if. this. apb_rw_channel resp_chan = null.log. local bit [31:0] ram[*].. function new(string name. this. main(). super.end return (this. if (tr.paddr > this..pwrite) ? apb_rw::WRITE : apb_rw::READ.sck.sigs.data = ’x.resp_chan == null) begin if (!this.kind == apb_rw::READ) begin if (this.psel !== 1'b1 || this.paddr.sck.sigs. forever begin apb_rw tr.. tr..sigs.\n".start_addr || this. this.paddr < this.addr = this.sck. tr.sigs.wait_if_stopped(). {"Responding to transaction. while (this.sck). else tr.sck.cfg. tr = new.psdisplay(" ")}). this.exists(addr)) ? this.sigs.flush().kind = (this.. tr.data = this.cfg.exists(tr. endfunction: reset_xactor virtual protected task main().ram[addr] : ’x.sigs. "Waiting for start of transaction.end_addr).ram.ram.sck.sigs. endfunction: peek virtual function void reset_xactor(reset_e rst_typ = SOFT_RST).sigs.penable !== 1'b0 || this.sigs.addr].sck. this.ram[tr. this.sck.addr)) tr.resp_chan. super. fork begin fork begin VMM Primer: Writing Command-Layer Slave Transactors 52 . end else begin bit abort = 0. `vmm_trace(log."). `vmm_trace(log.reset_xactor(rst_typ).sck.prdata <= ’z.prdata <= ’z. // Wait for a SETUP cycle do @ (this. end join if (abort) continue. end this.@ (this. pre_response(this tr)). end else begin @ (this.resp_chan.. if (this.sck.data = this.notify. "No response in time").penable !== 1'b1) begin `vmm_error(this. `vmm_callback(apb_slave_cbs.addr] = tr. tr)).put(tr).log..log. tr). join_any disable fork.pwdata. this. tr)). end if (this.\n".sigs. end `vmm_callback(apb_slave_cbs.data.sck. `vmm_error(this.sck).sigs. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").psdisplay(" ")}).sigs. else this.sck).resp_chan.sck.sigs. tr. @ (this.resp_chan == null) this.ram[tr. post_response(this.sigs.sneak(tr). end `vmm_callback(apb_slave_cbs. pre_response(this.data.sigs.indicate(RESPONSE.sck). tr. this. end endtask: main endclass: apb_slave `endif VMM Primer: Writing Command-Layer Slave Transactors 53 .prdata <= tr. abort = 1. `vmm_trace(log. {"Responded to transaction. "%sAPB Slave Config: [‘h%h-‘h%h]". this. 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). rand bit [31:0] start_addr = 32’h0000_0000. prefix.sv" `include "apb_rw. virtual apb_if. this. apb_rw cycle). endfunction: psdisplay endclass: apb_slave_cfg typedef class apb_slave. apb_rw cycle).sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. } virtual function string psdisplay(string prefix = "").end_addr). class apb_slave_cbs extends vmm_xactor_callbacks. endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor. endfunction: pre_response virtual function void post_response(apb_slave xactor. $sformat(psdisplay. virtual function void pre_response(apb_slave xact. constraint apb_slave_cfg_valid { end_addr >= start_addr. File: apb/apb_slave. apb_rw_channel resp_chan.start_addr. local apb_slave_cfg cfg. VMM Primer: Writing Command-Layer Slave Transactors 54 .sv" class apb_slave_cfg.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.slave sigs. rand bit [31:0] end_addr = 32’hFFFF_FFFF. this. .. super. this. name. apb_slave_cfg cfg = null. end this. . if (addr < this..ram[addr] = data.. if (addr < this. function new(string name.log.. local bit [31:0] ram[*]. return. return ’x. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg).).cfg. "Out-of-range peek"). bit [31:0] data).cfg = cfg. if (cfg == null) cfg = new.end_addr) begin `vmm_error(this.cfg. this. this. stream_id). .exists(addr)) ? this. super.new("APB Slave".cfg.reset_xactor(rst_typ). endfunction: reconfigure virtual function void poke(bit [31:0] addr.cfg. this.sigs = sigs. end return (this. virtual apb_if.start_addr || addr > this.slave sigs..typedef enum {RESPONSE} notifications_e..cfg = cfg. VMM Primer: Writing Command-Layer Slave Transactors 55 .ram[addr] : ’x.start_addr || addr > this. endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr).notify. apb_rw_channel resp_chan = null.resp_chan = resp_chan. endfunction: peek virtual function void reset_xactor(reset_e rst_typ = SOFT_RST).configure(RESPONSE). "Out-of-range poke").ram. int unsigned stream_id.end_addr) begin `vmm_error(this.log. cfg. end else begin bit abort = 0. tr. // Wait for a SETUP cycle do @ (this.sigs. tr.sck. fork begin fork begin @ (this.ram[tr.sck. join_any VMM Primer: Writing Command-Layer Slave Transactors 56 . else tr. "No response in time").resp_chan.psdisplay(" ")}).paddr > this... tr.notify. this.. tr = new. if (tr. `vmm_error(this.start_addr || this.resp_chan == null) begin if (!this. this.log..sigs. tr.main().sigs. endfunction: reset_xactor virtual protected task main().resp_chan.this. super.paddr < this. forever begin apb_rw tr.flush().sigs.addr = this.data = ’x. "Waiting for start of transaction.sck).addr].paddr.put(tr).prdata <= ’z. while (this.sck.prdata <= ’z.kind = (this. {"Responding to transaction.").end_addr).ram.sck. this.sck. end this. abort = 1.sigs.sck).indicate(vmm_data::STARTED).\n".addr)) tr.sck. `vmm_trace(log.sck.cfg.sigs.kind == apb_rw::READ) begin if (this.penable !== 1'b0 || this.sigs.exists(tr.psel !== 1'b1 || this.sigs.sigs. `vmm_trace(log.wait_if_stopped().sigs.data = this.sck.pwrite) ? apb_rw::WRITE : apb_rw::READ. {"Responded to transaction.indicate(vmm_data::ENDED).sigs. `vmm_callback(apb_slave_cbs. tr)). end endtask: main endclass: apb_slave `endif VMM Primer: Writing Command-Layer Slave Transactors 57 . post_response(this.. end tr.sck.penable !== 1'b1) begin `vmm_error(this.resp_chan == null) this.log.sigs. pre_response(this.sigs. this. pre_response(this.notify. end `vmm_callback(apb_slave_cbs. if (this.data. `vmm_trace(log. `vmm_callback(apb_slave_cbs.notify. tr.sck.pwdata.ram[tr.sck).disable fork.psdisplay(" ")}).\n".sneak(tr).sck. tr)).data. end join if (abort) continue. else this. @ (this.data = this.sigs.addr] = tr. tr).indicate(RESPONSE. tr. end if (this.sck).. this. "APB protocol violation:\n SETUP cycle not followed by ENABLE cycle").resp_chan.sigs. tr)). end else begin @ (this.prdata <= tr. File: apb/apb_rw. return tr.sv" class apb_rw extends vmm_data. all public properties in a transaction descriptor should be declared as rand (Rules 4-59. super.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. "class"). "Cannot copy into non-apb_rw \n instance"). else if (!$cast(tr.new(this. apb_rw tr. VMM Primer: Writing Command-Layer Slave Transactors 58 . static vmm_log log = new("apb_rw".log).Step 13: Random Responses To promote the use of random stimulus and make the same transaction descriptor usable in transaction generators. endfunction: new virtual function vmm_data allocate(). endfunction: allocate virtual function vmm_data copy(vmm_data to = null). It is a simple matter of using the `vmm_atomic_gen() and `vmm_scenario_gen() macros (Recommendation 5-23. It is also a good idea to pre-define random transaction generators whenever transaction descriptors are defined. 4-62). function new(). rand logic [31:0] data. to)) begin `vmm_fatal(log. if (to == null) tr = new. 5-24) in the transaction descriptor file. WRITE} kind. apb_rw tr = new. rand bit [31:0] addr. rand enum {READ. 4-60. "Kind %s !== %s". "Addr 0x%h !== 0x%h". return 0.kind.name(). input int kind = -1). apb_rw tr. "Cannot compare against non-apb_rw \n instance"). this.return null.kind != tr. return tr. int kind = -1). return 0. $sformat(psdisplay.addr).addr !== tr. end super. endfunction: copy virtual function string psdisplay(string prefix = ""). tr.addr. end if (this.copy_data(tr). to)) begin `vmm_fatal(log. tr.data. "Cannot compare to NULL reference"). "%sAPB %s @ 0x%h = 0x%h".kind) begin $sformat(diff. return 0. this.addr = this. return 1. end if (this. output string diff. VMM Primer: Writing Command-Layer Slave Transactors 59 .kind.kind. this. this. endfunction: psdisplay virtual function bit is_valid(bit silent = 1.data).kind). this.data = this.addr) begin $sformat(diff. return 0. endfunction: is_valid virtual function bit compare(input vmm_data to. end else if (!$cast(tr.addr. if (to == null) begin `vmm_fatal(log. tr.addr. prefix. tr. tr.kind = this. this. virtual function void build().resp_chan = new("APB Response". tr. end return 1.sv" class tb_env extends vmm_env.build().slv = new("0". int stop_after = 10. "Data 0x%h !== 0x%h". endfunction: compare endclass: apb_rw `vmm_channel(apb_rw) `vmm_atomic_gen(apb_rw. return 0. null.data) begin $sformat(diff. tb_top. this. 0.data).sv `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm. "APB Bus Cycle") `endif Random stimulus can also be used in generating the response to READ cycles. 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. "APB Bus Cycle") `vmm_scenario_gen(apb_rw. endfunction: build VMM Primer: Writing Command-Layer Slave Transactors 60 .data !== tr.data. File: Command_Slave_Xactor/tb_env.resp_chan). "0"). this. super.apb0.sv" `include "apb_slave.end if (this. apb_slave slv. this. apb_rw_channel resp_chan. if (!tr.start(). "Unable to randomize APB response").slv.resp_chan. @ (this.wait_for(apb_slave::RESPONSE). super. if (this. this.slv.slv.rand_mode(0). end end this. end join_none endtask: start virtual task wait_for_end(). fork forever begin apb_rw tr.start_xactor(). tr.kind == apb_rw::READ) begin tr. endtask: wait_for_end endclass: tb_env `endif VMM Primer: Writing Command-Layer Slave Transactors 61 .rand_mode(0).stop_after <= 0) -> this.peek(tr).resp_chan. tr. $cast(tr.addr. if (tr.kind.wait_for_end(). this. this. this.notify.randomize()) begin `vmm_error(log.get(tr).stop_after--.notify.virtual task start().status(apb_slave::RESPONSE)).end_test. super. this.display("Responded: "). end forever begin apb_rw tr.end_test). Using a factory pattern. File: apb/apb_slave. rand bit [31:0] start_addr = 32’h0000_0000. the apw_rw transaction descriptor cannot be written to meet the unpredictable needs of users for annotating it with arbitrary information.end_addr).sv" `include "apb_rw. endfunction: psdisplay VMM Primer: Writing Command-Layer Slave Transactors 62 . Unfortunately. $sformat(psdisplay. this. constraint apb_slave_cfg_valid { end_addr >= start_addr.sv `ifndef APB_SLAVE__SV `define APB_SLAVE__SV `include "apb_if. rand bit [31:0] end_addr = 32’hFFFF_FFFF. this. However. } virtual function string psdisplay(string prefix = ""). a user may wish to annotate the transaction descriptor with additional information inside an extension of the “pre_response” callback method. "%sAPB Slave Config: [‘h%h-‘h%h]".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.sv" class apb_slave_cfg.start_addr. prefix. 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. This allocates a new instance of an object of the same type as the “tr” variable. apb_rw_channel resp_chan = null. apb_rw tr_factory.new("APB Slave".notify. local apb_slave_cfg cfg.cfg = cfg. if (cfg == null) cfg = new. this. if (tr_factory == null) tr_factory = new. endfunction: pre_response virtual function void post_response(apb_slave xactor. this. endfunction: new virtual function void reconfigure(apb_slave_cfg cfg). apb_slave_cfg cfg = null.slave sigs. class apb_slave_cbs extends vmm_xactor_callbacks. apb_rw cycle).sigs = sigs.tr_factory = tr_factory. virtual apb_if. name. super. apb_rw cycle). local bit [31:0] ram[*]. typedef enum {RESPONSE} notifications_e. this. this.resp_chan = resp_chan. apb_rw tr_factory = null). endfunction: post_response endclass: apb_slave_cbs class apb_slave extends vmm_xactor. stream_id).configure(RESPONSE). function new(string name. apb_rw_channel resp_chan. virtual function void pre_response(apb_slave xact.slave sigs. virtual apb_if. VMM Primer: Writing Command-Layer Slave Transactors 63 . endfunction: reconfigure virtual function void poke(bit [31:0] addr. this. this.endclass: apb_slave_cfg typedef class apb_slave. int unsigned stream_id.cfg = cfg. psel !== 1'b1 || this. forever begin apb_rw tr.sck.cfg. "Out-of-range peek").ram[addr] = data. this.sigs.notify.resp_chan. VMM Primer: Writing Command-Layer Slave Transactors 64 . this.cfg.sigs... super.bit [31:0] data). tr.log.prdata <= ’z.cfg. end return (this.start_addr || addr > this.cfg.log. return ’x. endfunction: reset_xactor virtual protected task main().tr_factory.sck.sigs. this. this.paddr < this. // Wait for a SETUP cycle do @ (this.flush().sigs.allocate()).paddr > this.start_addr || this.sigs. super.sck.pwrite) ? apb_rw::WRITE : apb_rw::READ.sigs. if (addr < this. "Out-of-range poke").sigs.end_addr) begin `vmm_error(this.end_addr). return.sck. $cast(tr. endfunction: poke virtual function bit [31:0] peek(bit [31:0] addr).prdata <= ’z.reset_xactor(rst_typ).sck.kind = (this.sck.cfg.main().end_addr) begin `vmm_error(this.indicate(vmm_data::STARTED). end this.cfg.penable !== 1'b0 || this.sck).sck.ram. endfunction: peek virtual function void reset_xactor(reset_e rst_typ = SOFT_RST). tr. while (this.exists(addr)) ? this.ram[addr] : ’x.sigs. if (addr < this.wait_if_stopped(). `vmm_trace(log. "Waiting for start of transaction. this.start_addr || addr > this."). sck.sigs.sck). `vmm_callback(apb_slave_cbs. end if (this.data = ’x. tr. abort = 1. end join if (abort) continue.tr.penable !== 1'b1) begin `vmm_error(this.exists(tr.sck. fork begin fork begin @ (this.paddr.sck. end else begin bit abort = 0.log.ram. this.pwdata.resp_chan == null) this.ram[tr.sck)..log. else this.kind == apb_rw::READ) begin if (this.sigs.sigs.data = this. if (tr. "No response in time"). `vmm_error(this.addr].sneak(tr). end `vmm_callback(apb_slave_cbs.put(tr). `vmm_trace(log. tr)).sck.sck). if (this.prdata <= tr.resp_chan.. else tr. @ (this.sigs.resp_chan == null) begin if (!this.sigs.addr] = tr. join_any disable fork.psdisplay(" ")}). tr)).sigs. tr.addr)) tr. {"Responding to transaction.resp_chan.data.ram[tr.\n".data = this.addr = this. end else begin @ (this. "APB protocol violation:\n VMM Primer: Writing Command-Layer Slave Transactors 65 .sigs.data. end this. pre_response(this. pre_response(this. this. 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. annotated_apb_rw tr = new.psdisplay(" ")}).notify. annotated_apb_rw tr. `vmm_callback(apb_slave_cbs. tr)).. {"Responded to transaction. to)) begin `vmm_fatal(log. class annotated_apb_rw extends apb_rw. File: Command_Slave_Xactor/test_annotate. VMM Primer: Writing Command-Layer Slave Transactors 66 .SETUP cycle not followed by ENABLE cycle").sv `include "tb_env. "Cannot copy to a \n non-annotated_apb_rw instance"). end tr. a test may now add user-defined information to a transaction descriptor that may be used to determine the transaction response. `vmm_trace(log. else if (!$cast(tr.. tr.sv" program annotate_test. return tr. post_response(this. virtual function vmm_data allocate().notify. string note.indicate(vmm_data::ENDED).\n". endfunction virtual function vmm_data copy(vmm_data to = null).indicate(RESPONSE. tr). if (to == null) tr = new. local string format = "[-%0d-]".note. "Transaction descriptor \n is not a annotated_apb_rw"). tr. this. tb_env env = new.note.format = format. if (format != "") this.format. psdisplay = {super. " (". cycle)) begin `vmm_error(xactor. endfunction: pre_response endclass vmm_log log = new("Test". this.note = this. local int seq = 0.return null.log. end super. function new(string format = ""). if (!$cast(tr. return. env. apb_rw cycle). annotated_apb_rw tr. end $sformat(tr. return tr.note. initial begin env.build().stop_after = 5. endfunction virtual function void pre_response(apb_slave xactor. ")"}.copy(tr). endfunction virtual function string psdisplay(string prefix = ""). VMM Primer: Writing Command-Layer Slave Transactors 67 . this.psdisplay(prefix). begin annotated_apb_rw tr = new.seq++). "Annotate"). endfunction endclass class annotate_tr extends apb_slave_cbs. end env.sv" "apb_rw. VMM Primer: Writing Command-Layer Slave Transactors 68 . interface and transaction descriptor. env. we implemented only a slave transactor.slv. env. $finish().tr_factory = tr.sv" In this example. All of these transactors would be included in the top-level file. File: apb/apb. 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.run().sv" "apb_if.append_callback(cb).slv.sv" "apb_slave. 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. but a complete VIP for a protocol would also include a master transactor and a passive monitor.sv `ifndef APB__SV `define APB__SV `include `include `include `include `endif "vmm.annotate_tr cb = new. command-layer monitors. VMM Primer: Writing Command-Layer Slave Transactors 69 . functional-layer transactors or verification environments. Based on a few simple question and answers. you probably realized that there is much code that is similar across different monitors. You may consider reading other publications in this series to learn how to write VMM-compliant command-layer master transactors.Step 16: Congratulations! You have now completed the creation of a VMM-compliant command-layer slave transactor! Upon reading this primer. Command % vmmgen –l sv The relevant templates for writing command-layer slave transactors are: • • • Physical interface declaration Transaction Descriptor Reactive Driver. it will create a template for various components of a VMM-compliant monitor. 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. Physical-level. Half-duplex Note that the menu number used to select the appropriate template may differ from the number in the above list.06-6. VMM Primer: Writing Command-Layer Slave Transactors 70 . 2006 1 VMM Primer: Writing Command-Layer Monitors 1 .VMM Primer: Writing Command-Layer Monitors Author: Janick Bergeron Version 0.3 / Dec 01. VMM Primer: Writing Command-Layer Monitors 2 . Other primers will eventually cover other aspects of developing VMM-compliant verification assets. as soon as a functional monitor is available. step-by-step. functional-layer transactors. generators. The protocol used in this primer was selected for its simplicity. But additional VMM functionality—such as callbacks— will make it much easier to use in different verification environments. it does not require the use of many elements of the VMM standard library. This document is written in the same order you would implement a command-layer monitor. You can use the same sequence to create your own specific transactor. A word of caution however: it may be tempting to stop reading this primer half way through. you should read it in a sequential fashion. assertions and verification environments. Once a certain minimum level of functionality is met. It is sufficient to achieve the goal of demonstrating. Therefore. such as master and slave transactors. Because of its simplicity. As such. VMM compliance is a matter of degree. how to create a simple VMMcompliant master transactor. It was designed to be a reference document that defines what is—and is not—compliant with the methodology described within it.Introduction The Verification Methodology Manual for SystemVerilog book was never written as a training book. a monitor may be declared VMM compliant. you should read—and apply—this primer in its entirety. 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. The protocol specification can be found in the AMBA™ Specification (Rev 2. The Protocol The protocol used in this primer is the AMBA™ Peripheral Bus (APB) protocol. the APB monitor should be written for the entire 32-bit of address and data information. It is a simple single-master address-based parallel bus providing atomic individual read and write cycles. not just the device you are using it for the first time. The Verification Components Figure 1 illustrates the various components that will be created throughout this primer.This primer will show how to apply the various VMM guidelines. VMM Primer: Writing Command-Layer Monitors 3 . Therefore. If you are interested in learning more about the justifications of various techniques and approaches used in this primer. When writing a reusable monitor. A command-layer monitor interfaces directly to the DUT signals and reports all observed transactions on a transaction-level interface. not attempt to justify them or present alternatives. even though the device in this primer only supports 8 address bits and 16 data bits.com).0) available from ARM (http://arm. you should refer to the VMM book under the relevant quoted rules and recommendations. you have to think about all possible applications it may be used in. The signals are declared inside an interface (Rule 4-4). This is an old C trick that allows the file to be included multiple times. The entire content of the file declaring the interface is embedded in an `ifndef/`define/`endif construct. The name of the interface is prefixed with “apb_” to identify that it belongs to the APB protocol (Rule 4-5). Slaves are differentiated by responding to different address ranges. VMM Primer: Writing Command-Layer Monitors 4 . A single exchange of information (a READ or a WRITE operation) is called a transaction.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. There may be multiple slaves on an APB bus but there can only be one master. whenever required. without causing multipledefinition errors. wire [31:0] pwdata. listed in the AMBA™ Specification in Section 2. 4-11). wire [31:0] paddr. wire pwrite. wire psel.File: apb/apb_if. are declared as wires (Rule 4-6) inside the interface. wire penable... wire [31:0] prdata. wire [31:0] paddr.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if. wire psel. endinterface: apb_if `endif Because this is a synchronous protocol.4. VMM Primer: Writing Command-Layer Monitors 5 . . ..sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). File: apb/apb_if. clocking blocks are used to define the direction and sampling of the signals (Rule 4-7. wire penable.sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). File: apb/apb_if. endinterface: apb_if `endif The signals.. File: apb/apb_if. clocking pck @(posedge pclk).sv `ifndef APB_IF__SV `define APB_IF__SV interface apb_if(input bit pclk). wire penable. wire [31:0] pwdata. penable. 4-12). pwdata. 4-11. endclocking: pck modport passive(clocking pck). penable. psel. endinterface: apb_if `endif VMM Primer: Writing Command-Layer Monitors 6 . pwrite. endinterface: apb_if `endif The clocking block defining the synchronous signals is specified in the modport for the APB monitor (Rule 4-9. wire psel. input paddr. prdata. psel. wire [31:0] paddr. wire pwrite. input paddr.. prdata. wire [31:0] prdata. clocking pck @(posedge pclk). pwdata. wire [31:0] pwdata. endclocking: pck . pwrite. wire [31:0] prdata.wire pwrite. The clock signal need not be specified as it is implicit in the clocking block.. .paddr[7:0] )... .pwdata[15:0]). .apb_addr (apb0.apb_addr (apb0.apb_write (apb0.apb_enable (apb0..The interface declaration is now sufficient for writing a passive APB monitor.).penable ).prdata[15:0]).apb_sel (apb0. ..apb_rdata (apb0. when these transactors will be written. alongside of the DUT instantiation (Rule 4-13). These can be added later.apb_sel (apb0. endmodule: tb_top VMM Primer: Writing Command-Layer Monitors 7 . ... File: Command_Monitor_Xactor/tb_top....apb_wdata (apb0. .pwdata[15:0]).).. master_ip dut_mst(. . .apb_wdata (apb0. .apb_rdata (apb0.psel ).. ..psel ). It is instantiated in a top-level module. slave_ip dut_slv(. . .apb_enable (apb0.prdata[15:0]).pwrite ).paddr[7:0] ). .. apb_if apb0(. . Step 2: Connecting to the DUT The interface may now be connected to the DUT. it should eventually include a modport for a master and a slave monitor transactor (Rule 4-9). .. To be fully compliant. .pwrite ).sv module tb_top.penable )..apb_write (apb0.).. The connection to the DUT pins are specified using a hierarchical reference to the wires in the interface instance. This instance of the message service interface is passed to the vmm_data constructor (Rule 4-58). .This top-level module also contains the clock generators (Rule 4-15).pwrite ). It also needs a static vmm_log property instance used to issue messages from the transaction descriptor.apb_enable (apb0. ..apb_sel (apb0. bit clk = 0. . ..apb_rdata (apb0.pwdata[15:0]). VMM Primer: Writing Command-Layer Monitors 8 . always #10 clk = ~clk.clk (clk)).. containing a public property enumerating the various transactions that can be observed by the monitor (Rule 4-60.penable ).apb_addr (apb0..sv module tb_top. my_design dut(. .apb_wdata (apb0. 4-62).paddr[7:0] ). apb_if apb0(clk).apb_write (apb0.psel ). 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).prdata[15:0]). . File: Command_Monitor_Xactor/tb_top. .. endmodule: tb_top Step 3: The Transaction Descriptor The next step is to define the APB transaction descriptor (Rule 4-54). .. 4-62) and public properties for each parameter or value in the transaction (Rule 4-59.. . VMM Primer: Writing Command-Layer Monitors 9 . bit [31:0] addr. it is the data value that was read. function new(). endclass: apb_rw . super.File: apb/apb_rw. In a READ transaction. endfunction: new . WRITE} kind. `endif A single property is used for data. A transactionlevel interface will be required to transfer transaction descriptors to a transactor to be executed.. static vmm_log log = new("apb_rw". In a WRITE transaction. there can only be one data value valid at any given time. it is has the minimum functionality to be used by a monitor. Although the transaction descriptor is not yet VMM-compliant.sv" class apb_rw extends vmm_data. "class").new(this. it is interpreted as the data to be written. enum {READ...sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. The type for the data property is logic as it will allow the description of READ cycles to reflect unknown results. Because the APB does not support concurrent read/write transactions.log). despite the fact that the APB bus has separate read and write data buses. The data class property is interpreted differently depending on the transaction kind (Rule 4-71). This is done using the `vmm_channel macro (Recommendation 4-56).. logic [31:0] data. sv" class apb_rw extends vmm_data..new(this. `endif Step 4: The Monitor The monitor transactor can now be started. super. endclass: apb_rw `vmm_channel(apb_rw) . bit [31:0] addr..sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV . function new().. class apb_monitor extends vmm_xactor. endclass: apb_monitor `endif VMM Primer: Writing Command-Layer Monitors 10 . WRITE} kind.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm.. ..File: apb/apb_rw. static vmm_log log = new("apb_rw".... File: apb/apb_monitor. logic [31:0] data. endfunction: new . enum {READ.log). "class"). It is a class (Rule 4-91) derived from the vmm_xactor base class (4-92). VMM Primer: Writing Command-Layer Monitors 11 ...passive sigs. . File: apb/apb_monitor. int unsigned stream_id.. The observation of READ and WRITE transactions is coded exactly as it would be if good old Verilog was used.. ... 4-12).The transactor needs a physical-level interface to observe transactions. endclass: apb_monitor `endif The transaction descriptors are filled in from observations on the physical interface in the main() task (Rule 4-93). super.. virtual apb_if. . virtual apb_if.. stream_id).sv" .sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if. this. The only difference is that the physical signals are accessed through the clocking block of the virtual modport instead of pins on a module.sigs = sigs. class apb_monitor extends vmm_xactor. function new(string name.. endfunction: new . It is a simple matter of sampling input signals at the right point in time. The active clock edge is defined by waiting on the clocking block itself.passive sigs..). not an edge of an input signal (Rule 4-7. 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).new("APB Monitor".sv" `include "apb_rw. name. sigs.pck.kind == apb_rw::READ) ? this.pwdata.sigs. ..sv" `include "apb_rw. virtual protected task main().sigs. . . super.pck.sigs. this.data = (tr..sv" .. endfunction: new .log. class apb_monitor extends vmm_xactor..sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if.pck.. end tr.pck.sigs.pck.. stream_id).kind = (this... super. @ (this. while (this..penable !== 1'b1) begin `vmm_error(this.passive sigs.passive sigs. tr.prdata : this. name. // Wait for a SETUP cycle do @ (this..sigs = sigs.addr = this.).pck.new("APB Monitor"..pck).psel !== 1'b1 || this.File: apb/apb_monitor.sigs. . int unsigned stream_id...sigs.penable !== 1'b0). tr. virtual apb_if..sigs.pwrite) ? apb_rw::WRITE : apb_rw::READ.pck). tr = new. forever begin apb_rw tr.main().sigs.paddr. ... "APB protocol violation: SETUP cycle not followed by ENABLE cycle"). virtual apb_if. if (this. function new(string name. end endtask: main endclass: apb_monitor VMM Primer: Writing Command-Layer Monitors 12 . .pck. if the monitor is stopped in the middle of a transaction stream. Therefore..pck.sigs. this. name. Otherwise. tr = new.. it must not be stopped until the end of the transaction is observed and the entire transaction is reported.`endif Once a monitor recognizes the start of a transaction. super.).main()..master sigs...pck). while (this. . class apb_monitor extends vmm_xactor. function new(string name.. int unsigned stream_id.. .sigs.wait_if_stopped(). it will report a transaction error at best or transactions composed of information from two different transactions.pck. super...penable !== 1'b0). File: apb/apb_monitor. forever begin apb_rw tr. . VMM Primer: Writing Command-Layer Monitors 13 .sv" . .passive sigs.sigs = sigs. the vmm_xactor::wait_if_stopped() method is called only before the beginning of transaction. virtual apb_if. this.sv" `include "apb_rw.psel !== 1'b1 || this.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if.sigs. virtual apb_if. stream_id).... // Wait for a SETUP cycle do @ (this.new("APB Master". virtual protected task main(). endfunction: new . prdata : this. is attached to a newly defined OBSERVED notification and can be recovered by all interested parties waiting on the notification.sv" .sigs.sigs. "APB protocol violation: SETUP cycle not followed by ENABLE cycle").pck. The observed transaction..log.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if.sigs.pck. if (this. end tr.pwdata.pck.kind == apb_rw::READ) ? this.data = (tr.penable !== 1'b1) begin `vmm_error(this. VMM Primer: Writing Command-Layer Monitors 14 . File: apb/apb_monitor.addr = this.sigs. being derived from vmm_data.. Simply displaying the observed transactions is not very useful as it will require that the results be manually checked every time.pck).sigs. tr. Observed transactions can be reported by indicating a notification in the vmm_xactor::notify property.paddr..pwrite) ? apb_rw::WRITE : apb_rw::READ.kind = (this.sv" `include "apb_rw. @ (this.sigs. 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.pck.pck.. .tr. while (this. this. this. forever begin apb_rw tr.class apb_monitor extends vmm_xactor. this... name. .sigs. function new(string name.).pck.sigs.prdata : this. "APB protocol violation: SETUP cycle not followed by ENABLE cycle"). virtual apb_if.indicate(OBSERVED. if (this.addr = this. tr.pwdata...penable !== 1'b0). ... virtual apb_if.new("APB Master".sigs.pwrite) ? apb_rw::WRITE : apb_rw::READ. tr).. // Wait for a SETUP cycle do @ (this. . end tr.main(). this.kind == apb_rw::READ) ? this.sigs = sigs. typedef enum {OBSERVED} notifications_e. .sigs. tr. .notify.notify. .pck. stream_id).pck. super..kind = (this...sigs. virtual protected task main().pck.log.pck.psel !== 1'b1 || this...sigs.pck)..configure(OBSERVED).. .sigs.data = (tr.penable !== 1'b1) begin `vmm_error(this. tr = new. super.pck. endfunction: new .passive sigs..sigs.paddr.master sigs.pck.wait_if_stopped(). int unsigned stream_id. @ (this..sigs.pck). end endtask: main endclass: apb_monitor VMM Primer: Writing Command-Layer Monitors 15 . endfunction: build virtual task start(). . 0.mon. 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.. the monitor can now be used to monitor the activity on an APB bus.. The monitor is constructed in the extension of the vmm_env::build() method (Rule 4-34.start(). . super.mon = new("0". this..sv `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm..apb0). tb_top. 4-35) and started in the extension of the vmm_env::start() method (Rule 4-41). . endtask: start . virtual function void build().. . It is instantiated in a verification environment class..sv" class tb_env extends vmm_env.. VMM Primer: Writing Command-Layer Monitors 16 .. apb_monitor mon.start_xactor().sv" `include "apb_monitor.build(). File: Command_Monitor_Xactor/tb_env. this.`endif Step 6: The First Test Although not completely VMM-compliant. extended from the vmm_env base class. super... super. 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. the monitor can report the observed transactions.start(). this. .sv" class tb_env extends vmm_env...status(apb_monitor::OBSERVED)). 0. . int stop_after = 10. apb_monitor mon. `ifndef TB_ENV__SV `define TB_ENV__SV `include "vmm.. if (this...build(). fork .sv" `include "apb_monitor.mon.start_xactor().stop_after <= 0) -> this. forever begin apb_rw tr. . this.. tb_top.wait_for(apb_monitor::OBSERVED). this.notify. tr. virtual function void build().display("Notified: "). this...apb0).end_test. super.mon.mon.stop_after--. end join_none VMM Primer: Writing Command-Layer Monitors 17 .endclass: tb_env `endif As the simulation progress. this. $cast(tr.notify. endfunction: build virtual task start()..mon = new("0".. . 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 where the content of the transaction is transmitted over a physical interface (Recommendation 4-77). `endif Three other standard methods. vmm_data::byte_pack() and vmm_data::byte_unpack() should also be overloaded for packet-oriented transactions. is saved in a public property (Rule 4-112). it is possible that transactions will be missed. unless it is sunk using the vmm_channel::sink() method. Transactions reported via a vmm_notify can be consumed by an arbitrary number of consumers but must be consumed in zero time. where the transaction descriptors are kept until they are explicitly consumed. The output channel. 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. but that consumer can only consume transactions at its own pace.. However.. vmm_data:byte_size(). The vmm_channel transaction-level interface is used to provide a buffering reporting mechanism. It is sunk by default to avoid accidental memory leakage. passed to the transactor as constructor arguments (Recommendation 4-113). Transactions reported via a vmm_channel can only be consumed by a single consumer. a channel with no consumer will accumulate transaction descriptors. Should a higher-level thread or transactor have the potential to block.`vmm_channel(apb_rw) . a vmm_notify mechanism will not accumulate transaction descriptors. Transaction VMM Primer: Writing Command-Layer Monitors 22 . Should there be no consumers. if (out_chan == null) begin out_chan = new("APB Monitor Output Channel". virtual apb_if.sink(). name). this. super. this..sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if.out_chan = out_chan.sv" `include "apb_rw..pck. apb_rw_channel out_chan = null .sigs.. .pck. virtual protected task main(). super. virtual apb_if... . apb_rw_channel out_chan.passive sigs.sigs = sigs. VMM Primer: Writing Command-Layer Monitors 23 .. endfunction: new .psel !== 1'b1 || this.. typedef enum {OBSERVED} notifications_e..configure(OBSERVED).penable !== 1'b0).pck). function new(string name. while (this.new("APB Master".sv" .. int unsigned stream_id.sigs. this.sigs. name. end this.wait_if_stopped()..). out_chan. stream_id).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.main(). // Wait for a SETUP cycle do @ (this. forever begin apb_rw tr. class apb_monitor extends vmm_xactor.notify. File: apb/apb_monitor. end endtask: main endclass: apb_monitor `endif When the transactor is reset.pck).pwrite) ? apb_rw::WRITE : apb_rw::READ. typedef enum {OBSERVED} notifications_e..sneak(tr). tr.sigs.indicate(OBSERVED. .sv" `include "apb_rw. File: apb/apb_monitor.. . this. function new(string name.master sigs.. this. This is accomplished in the extension of the vmm_xactor::reset_xactor() method (Table A-8). virtual apb_if..pck.sigs.sigs. @ (this.log.prdata : this.sv" .pck. "APB protocol violation: SETUP cycle not followed by ENABLE cycle"). tr. tr).notify. the output channel must be flushed. class apb_monitor extends vmm_xactor.pwdata.kind == apb_rw::READ) ? this. VMM Primer: Writing Command-Layer Monitors 24 .sigs.paddr. end tr.. ..sigs.kind = (this.tr = new.addr = this.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if. if (this. int unsigned stream_id.pck.sigs.pck.out_chan. apb_rw_channel out_chan.data = (tr.. virtual apb_if.penable !== 1'b1) begin `vmm_error(this.passive sigs..pck. prdata : this.sigs.wait_if_stopped().pck.sigs. VMM Primer: Writing Command-Layer Monitors 25 ..indicate(OBSERVED.sink().pck). end tr.pck.main().penable !== 1'b0).apb_rw_channel out_chan = null .sigs. . this. this.pck...sigs. super. name. @ (this. if (this.reset_xactor(rst_typ)..pwdata. .addr = this.flush().paddr.new("APB Master".. this. stream_id).pck.pck.out_chan = out_chan. this. end this. tr). out_chan. name). while (this.sigs.sigs. // Wait for a SETUP cycle do @ (this.pck.data = (tr.kind = (this. tr..sigs = sigs. tr = new.sigs. super.psel !== 1'b1 || this. if (out_chan == null) begin out_chan = new("APB Monitor Output Channel".pwrite) ? apb_rw::WRITE : apb_rw::READ. .out_chan.pck.configure(OBSERVED).penable !== 1'b1) begin `vmm_error(this. forever begin apb_rw tr.kind == apb_rw::READ) ? this.sigs... super. endfunction: new virtual function void reset_xactor(reset_e rst_typ = SOFT_RST).notify.notify. this. endfunction: reset_xactor virtual protected task main().log.).sigs.pck). tr. "APB protocol violation: SETUP cycle not followed by ENABLE cycle"). int unsigned stream_id. . apb_rw_channel out_chan = null . .configure(OBSERVED).. super.master sigs. class apb_monitor extends vmm_xactor.. this. out_chan.sv" .this...sneak(tr). if (out_chan == null) begin out_chan = new("APB Monitor Output Channel".sink().notify.new("APB Master".).sigs = sigs. These transaction endpoints are recorded in the transaction descriptor itself by indicating the vmm_data::STARTED and vmm_data::ENDED notifications (Rule 4-142).passive sigs.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if..sv" `include "apb_rw. endfunction: new VMM Primer: Writing Command-Layer Monitors 26 . apb_rw_channel out_chan.out_chan. virtual apb_if. end this. name). stream_id). virtual apb_if.. 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.out_chan = out_chan. typedef enum {OBSERVED} notifications_e. function new(string name. File: apb/apb_monitor.. this.. name. pwrite) ? apb_rw::WRITE : apb_rw::READ.main(). if (this.pwdata.pck.pck).pck. super.pck.penable !== 1'b1) begin `vmm_error(this.sigs.out_chan. tr). @ (this. this.sigs.notify. . tr = new.psel !== 1'b1 || this.indicate(vmm_data::STARTED). tr.out_chan.sigs. tr.sigs.pck.indicate(vmm_data::ENDED).log.wait_if_stopped().sigs.pck.kind == apb_rw::READ) ? this. super.notify.sigs.addr = this.sigs. this..penable !== 1'b0).notify.indicate(OBSERVED. end tr.kind = (this. endfunction: reset_xactor virtual protected task main(). this.pck)..pck.flush(). tr. end endtask: main endclass: apb_monitor `endif VMM Primer: Writing Command-Layer Monitors 27 .sigs. // Wait for a SETUP cycle do @ (this. this. tr. forever begin apb_rw tr. while (this.prdata : this.sneak(tr).sigs.paddr.pck.data = (tr. "APB protocol violation: SETUP cycle not followed by ENABLE cycle").virtual function void reset_xactor(reset_e rst_typ = SOFT_RST).reset_xactor(rst_typ). Step 9: Debug Messages To be truly reusable..sv" .). `vmm_debug() or `vmm_verbose() macros (Recommendation 4-51). File: apb/apb_monitor.. is doing or has done.sigs = sigs.new("APB Master". .. function new(string name. This capability may even be a basic requirement if you plan on shipping encrypted or compiled code. stream_id). int unsigned stream_id. name). VMM Primer: Writing Command-Layer Monitors 28 .. .notify. class apb_monitor extends vmm_xactor.. virtual apb_if. this.out_chan = out_chan. These debug messages are inserted using the `vmm_trace().configure(OBSERVED). end this. apb_rw_channel out_chan = null .sink(). Debug messages should be added at judicious points to indicate what the monitor is about to do.. super. name. typedef enum {OBSERVED} notifications_e. apb_rw_channel out_chan..sv" `include "apb_rw.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if. this. it should be possible to understand what the monitor does and debug its operation without having to inspect the source code.master sigs. if (out_chan == null) begin out_chan = new("APB Monitor Output Channel".. out_chan. virtual apb_if.passive sigs. tr.wait_if_stopped().sigs. this.pck).out_chan.out_chan.sigs.main().log.notify.pck. endfunction: reset_xactor virtual protected task main()..sigs..\n".pck).sigs.indicate(vmm_data::STARTED).notify.sigs.sigs.pck.addr = this. end tr. {"Observed transaction. forever begin apb_rw tr.").penable !== 1'b1) begin `vmm_error(this.. this.indicate(OBSERVED.pck.pwrite) ? apb_rw::WRITE : apb_rw::READ. end endtask: main endclass: apb_monitor `endif VMM Primer: Writing Command-Layer Monitors 29 . tr.indicate(vmm_data::ENDED).. "Waiting for start of transaction.penable !== 1'b0).pwdata. `vmm_trace(log.pck. this.reset_xactor(rst_typ).psdisplay(" ")}).sigs. .flush().pck. super.notify.endfunction: new virtual function void reset_xactor(reset_e rst_typ = SOFT_RST). // Wait for a SETUP cycle do @ (this.paddr.sigs.psel !== 1'b1 || this.. this.sigs. super.pck.. tr). tr = new. "APB protocol violation: SETUP cycle not followed by ENABLE cycle"). if (this. @ (this.kind == apb_rw::READ) ? this. `vmm_trace(log. tr.kind = (this.prdata : this.pck.data = (tr. tr. tr. while (this.sneak(tr). class apb_monitor_cbs extends vmm_xactor_callbacks.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if. Next. File: Command_Monitor_Xactor/Makefile % vcs –sverilog –ntb_opts rmm +vmm_log_default=trace . VMM Primer: Writing Command-Layer Monitors 30 . the callback method needs to be invoked at the appropriate point in the execution of the monitor..sv" `include "apb_rw.. using the `vmm_callback() macro (Rule 4-163).sv" typedef class apb_monitor. A callback method should be provided after a transaction has been observed (Recommendation 4-155). This callback method allows the observed transaction to be recorded in a functional coverage model or checked against an expected response.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. File: apb/apb_monitor. Step 10: Extension Points A callback method is a third mechanism for reporting observed transactions. 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). pck.notify.psel !== 1'b1 || this.. .. super.sigs. name). if (out_chan == null) begin out_chan = new("APB Monitor Output Channel".flush().. typedef enum {OBSERVED} notifications_e. virtual apb_if.."). "Waiting for start of transaction. VMM Primer: Writing Command-Layer Monitors 31 .).sigs = sigs. this. out_chan.. `vmm_trace(log.sigs. super.new("APB Master". this.penable !== 1'b0). this. apb_rw cycle).sink(). .virtual function void post_cycle(apb_monitor xactor.configure(OBSERVED). super. function new(string name. end this.. apb_rw_channel out_chan.pck. int unsigned stream_id. while (this.out_chan = out_chan. . stream_id). forever begin apb_rw tr. endfunction: post_cycle endclass: apb_monitor_cbs class apb_monitor extends vmm_xactor. this. virtual apb_if. // Wait for a SETUP cycle do @ (this.passive sigs.out_chan. endfunction: reset_xactor virtual protected task main(). name.pck)... apb_rw_channel out_chan = null.main().sigs.wait_if_stopped().master sigs.reset_xactor(rst_typ). endfunction: new virtual function void reset_xactor(reset_e rst_typ = SOFT_RST). addr = this.tr = new..sneak(tr).pck.pwdata. This allocates a new instance of an object of the same type as the tr variable. VMM Primer: Writing Command-Layer Monitors 32 . `vmm_callback(apb_monitor_cbs. However. tr)). tr.pck.sigs.indicate(vmm_data::STARTED). "APB protocol violation: SETUP cycle not followed by ENABLE cycle").psdisplay(" ")}).pwrite) ? apb_rw::WRITE : apb_rw::READ. 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.sigs.notify. tr. end tr. @ (this.kind = (this.sigs.prdata : this. this.notify.sigs.kind == apb_rw::READ) ? this.indicate(OBSERVED.pck). {"Observed transaction.sigs. `vmm_trace(log. Unfortunately.paddr.log.pck.\n". a user may wish to annotate the transaction descriptor with additional information inside an extension of the “post_cycle” callback method.data = (tr.pck.pck. post_cycle(this. the apw_rw transaction descriptor cannot be written to meet the unpredictable needs of users for annotating it with arbitrary information.out_chan.indicate(vmm_data::ENDED). this.sigs. tr.. tr.penable !== 1'b1) begin `vmm_error(this.notify. tr). if (this. function new(string name.sv `ifndef APB_MONITOR__SV `define APB_MONITOR__SV `include "apb_if.sv" `include "apb_rw. endfunction: post_cycle endclass: apb_monitor_cbs class apb_monitor extends vmm_xactor.configure(OBSERVED).master sigs.Using a factory pattern. apb_rw_channel out_chan. File: apb/apb_monitor.passive sigs. name).out_chan = out_chan. virtual function void post_cycle(apb_monitor xactor. 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. typedef enum {OBSERVED} notifications_e. this.sigs = sigs. name. this. apb_rw cycle). apb_rw tr_factory. this. if (tr_factory == null) tr_factory = new. VMM Primer: Writing Command-Layer Monitors 33 . stream_id).tr_factory = tr_factory. apb_rw_channel out_chan = null. if (out_chan == null) begin out_chan = new("APB Monitor Output Channel".new("APB Master". class apb_monitor_cbs extends vmm_xactor_callbacks. out_chan. super. virtual apb_if.sink(). virtual apb_if.sv" typedef class apb_monitor. int unsigned stream_id. end this.notify. apb_rw tr_factory = null). this.reset_xactor(rst_typ).pck.log. tr.pck).sigs. tr.. endfunction: reset_xactor virtual protected task main(). `vmm_trace(log. this.tr_factory.kind = (this.sigs. {"Observed transaction.out_chan.sigs.data = (tr.notify.indicate(vmm_data::STARTED).flush(). // Wait for a SETUP cycle do @ (this.notify.sigs.allocate())..penable !== 1'b0).psel !== 1'b1 || this.addr = this.penable !== 1'b1) begin `vmm_error(this.. this. "Waiting for start of transaction.pck.pck.sneak(tr).out_chan. tr.main(). `vmm_trace(log.pwdata.kind == apb_rw::READ) ? this.pck. this. if (this.endfunction: new virtual function void reset_xactor(reset_e rst_typ = SOFT_RST).notify.psdisplay(" ")}).pck.sigs.indicate(OBSERVED. tr). $cast(tr. while (this.\n"..pck.pck). end tr.pck. VMM Primer: Writing Command-Layer Monitors 34 . "APB protocol violation: SETUP cycle not followed by ENABLE cycle"). tr. super.sigs. this. tr)).prdata : this.indicate(vmm_data::ENDED). @ (this. forever begin apb_rw tr.sigs.pwrite) ? apb_rw::WRITE : apb_rw::READ.sigs.wait_if_stopped(). super.sigs. tr. `vmm_callback(apb_monitor_cbs.paddr."). post_cycle(this. annotated_apb_rw tr.\n annotated_apb_rw instance"). return tr. to)) begin `vmm_fatal(log.psdisplay(prefix). psdisplay = {super. a test may now add user-defined information to a transaction descriptor. endfunction virtual function string psdisplay(string prefix = ""). return null. 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. ")"}. "Cannot copy to a non. " (".sv" program annotate_test. annotated_apb_rw tr = new. VMM Primer: Writing Command-Layer Monitors 35 .copy(tr). if (to == null) tr = new. end super.note = this. else if (!$cast(tr. File: Command_Monitor_Xactor/test_annotate. return tr. endfunction virtual function vmm_data copy(vmm_data to = null). class annotated_apb_rw extends apb_rw. virtual function vmm_data allocate().note.note. string note. this.sv `include "tb_env. $finish(). this.append_callback(cb). env. annotated_apb_rw tr. apb_rw cycle). local int seq = 0. function new(string format = "").log. end $sformat(tr. initial begin env.seq++).tr_factory = tr.format = format. this. env. env.run(). tb_env env = new. end env. return.build(). end endprogram VMM Primer: Writing Command-Layer Monitors 36 . annotate_tr cb = new.stop_after = 5.mon. "Transaction descriptor \n is not a annotated_apb_rw").format.endfunction endclass class annotate_tr extends apb_monitor_cbs.mon. begin annotated_apb_rw tr = new. endfunction virtual function void post_cycle(apb_monitor xactor. "Annotate"). if (format != "") this.note. local string format = "[-%0d-]". if (!$cast(tr. cycle)) begin `vmm_error(xactor. endfunction: post_cycle endclass vmm_log log = new("Test". super.sv `ifndef APB_RW__SV `define APB_RW__SV `include "vmm. apb_rw tr = new. It is a simple matter of using the `vmm_atomic_gen() and `vmm_scenario_gen() macros (Recommendation 5-23. rand logic [31:0] data. all public properties in a transaction descriptor should be declared as rand (Rules 4-59.new(this. if (to == null) tr = new. else if (!$cast(tr. apb_rw tr. endfunction: new virtual function vmm_data allocate(). rand bit [31:0] addr. "class"). "Cannot copy into non-apb_rw \n instance"). File: apb/apb_rw. 4-60.Step 11: Random Transactions To promote the use of random stimulus and make the same transaction descriptor usable in transaction generators. It is also a good idea to pre-define random transaction generators whenever transaction descriptors are defined. to)) begin `vmm_fatal(log. static vmm_log log = new("apb_rw". return tr. endfunction: allocate virtual function vmm_data copy(vmm_data to = null). function new(). 5-24) in the transaction descriptor file. rand enum {READ.sv" class apb_rw extends vmm_data. 4-62).log). WRITE} kind. VMM Primer: Writing Command-Layer Monitors 37 . kind. tr.kind. tr.addr = this. $sformat(psdisplay.kind != tr.addr. tr. end VMM Primer: Writing Command-Layer Monitors 38 . endfunction: psdisplay virtual function bit is_valid(bit silent = 1. input int kind = -1). tr. end if (this. end else if (!$cast(tr.data = this. this.addr. endfunction: is_valid virtual function bit compare(input vmm_data to.data). this.copy_data(tr).data.addr.addr) begin $sformat(diff. return tr. if (to == null) begin `vmm_fatal(log. return 0. return 0. apb_rw tr. return 0. "Addr 0x%h !== 0x%h".kind). "Cannot compare to NULL reference"). return 0. to)) begin `vmm_fatal(log. end if (this.kind) begin $sformat(diff.\n apb_rw instance").name(). "%sAPB %s @ 0x%h = 0x%h".kind.addr !== tr. tr. endfunction: copy virtual function string psdisplay(string prefix = ""). prefix. "Kind %s !== %s". end super. this. output string diff.return null.kind = this. this.addr). "Cannot compare against non. int kind = -1). return 1. this. tr. end return 1.data.if (this. endfunction: compare endclass: apb_rw `vmm_channel(apb_rw) `vmm_atomic_gen(apb_rw. "APB Bus Cycle") `vmm_scenario_gen(apb_rw. 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. "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. this. interface and transaction descriptor. File: apb/apb.sv" "apb_if.sv" "apb_rw.sv" VMM Primer: Writing Command-Layer Monitors 39 .sv" "apb_monitor. return 0. "Data 0x%h !== 0x%h".data).data !== tr.data) begin $sformat(diff. you probably realized that there is much code that is similar across different monitors. it will create a template for various components of a VMM-compliant monitor. VMM Primer: Writing Command-Layer Monitors 40 . Half-duplex Note that the menu number used to select the appropriate template may differ from the number in the above list.In this example. we implemented only a monitor. Command % vmmgen –l sv The relevant templates for writing command-layer monitors are: • • • Physical interface declaration Transaction Descriptor Monitor. Step 13: Congratulations! You have now completed the creation of a VMM-compliant command-layer monitor transactor! Upon reading this primer. Physical-level. 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. but a complete VIP for a protocol would also include a master transactor and a slave transactor. All of these transactors would be included in the top-level file. Based on a few simple question and answers. command-layer slave transactors. functional-layer transactors or verification environments.You may consider reading other publications in this series to learn how to write VMM-compliant command-layer master transactors. 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 1.s2.slv[1] = new("Slave". "2").. tb_top. virtual function build(). 0.get(tr). foreach (this.s0. apb_slave slv[3]. .slv[2] = new("Slave". this.slv[0] = new("Slave"...peek(tr)... // Poor random // strategy! .. . this.. this. end VMM Primer: Using the Data Stream Scoreboard 6 .data = $random(). this.kind == apb_rw::READ) begin tr...generator. tb_top. this...resp_chan[0]). end this. class tb_env extends vmm_env. "0"). "1").resp_chan[0] = new("Response".resp_chan[2] = new("Response". this.resp_chan[j].resp_chan[1]).resp_chan[2])... . if (tr.. It can be coded directly in the environment as a forked-off thread.resp_chan[j].resp_chan[i]) begin int j = i. File: DateStream_DB/tb_env. . tb_top. fork forever begin apb_rw tr. a simple non-reusable and unconstrainable response generator will be used. this. .sv .s1. 2. . . this. this. endfunction: build virtual function start(). .resp_chan[1] = new("Response". this. apb_rw_channel resp_chan[3]. Step 2: Master-to-Slaves The master-to-slaves scoreboard requires one queue of input transaction and three queues of expected transaction. In this case.. File: DataStream_SB/tb_env. function new(). 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). one per slave. `include "vmm_sb..join_none end endfunction: start . This is implemented using the Data Stream Scoreboard foundation class by defining one input stream and three expected streams. VMM Primer: Using the Data Stream Scoreboard 7 . endclass: tb_env With random responses.sv" class m2s_sb extends vmm_sb_ds. it is no longer possible to predict the expected response strictly from the master’s interface.new("Master->Slave")..sv .. super. the other in the slaves-to-master direction. A unique stream identifier must be assigned to each stream. The stream identifier is chosen to facilitate the association of a slave with its corresponding stream during integration. This requires two scoreboards: one in the master-to-slaves direction. the stream identifier corresponds to the value of ‘j’ in the response generator thread. output vmm_data out_pkts[]). as observed by the slaves. Any bits set by the master will be masked. "Slave 0".. this. out_pkts = new [1]. EXPECT). $cast(tr.define_stream(1. function new(). in_pkt..this. this. will be different from the one injected by the master. EXPECT). VMM Primer: Using the Data Stream Scoreboard 8 ..define_stream(0. "Master".define_stream(0. INPUT).new("Master->Slave"). virtual function bit transform(input vmm_data in_pkt. EXPECT). endclass: m2s_sb . bits [31:8] of the address will always be zero. endfunction: new "Master". "Slave 0". this. out_pkts[0] = tr. EXPECT).define_stream(0.define_stream(1.define_stream(0.paddr[31:8] = ‘0. this..sv" class m2s_sb extends vmm_sb_ds. EXPECT). endfunction: new ..define_stream(2. EXPECT). tr. this. INPUT). Because each slave only has 256 addressable locations.. The expected transaction. `include "vmm_sb.sv . File: DataStream_SB/tb_env. super. It is thus necessary to perform the same transformation in the scoreboard. "Slave 1". "Slave 2". "Slave 2". this. this. apb_rw tr. "Slave 1".define_stream(2.copy()). By default. EXPECT). `include "vmm_sb. This is done by overloading the vmm_sb_ds::compare() method. endclass: m2s_sb .sv .define_stream(2.copy()). "Slave 2".. when verifying the response of the master-to-slave path. "Slave 1".sv" class m2s_sb extends vmm_sb_ds. endfunction: transform VMM Primer: Using the Data Stream Scoreboard 9 . in_pkt. tr.. virtual function bit transform(input vmm_data in_pkt. the comparison function use by the Data Stream Scoreboard foundation class is the vmm_data::compare() method defined for the transaction descriptor.define_stream(0. "Slave 0".. this. this.define_stream(0. function new(). $cast(tr. EXPECT).new("Master->Slave"). File: DataStream_SB/tb_env.endfunction: transform . However.. out_pkts[0] = tr. endfunction: new "Master". apb_rw tr. this. It is thus necessary to specify a custom comparisons function in the master-to-slave scoreboard.define_stream(1. EXPECT). output vmm_data out_pkts[]). INPUT). super. this. out_pkts = new [1]. the data value can only be compared against an expected value for WRITE cycles..addr[31:8] = ‘0.. kind == exp.kind) && (act. diff).compare(exp. VMM Primer: Using the Data Stream Scoreboard 10 . Step 3: Integration Once the functionality of the scoreboard is defined. $cast(exp. exp. endfunction: compare endclass: m2s_sb .addr == exp. expected).. $cast(act. The pre_cycle() method is thus the proper integration point.kind == apb_rw::WRITE) begin string diff. return act.addr). The stimulus transaction executed by the master can be extracted using either the pre_cycle() or post_cycle() callback method. if (act. vmm_data expected). 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. it must then be integrated with the rest of the verification environment. actual).. apb_rw act.virtual function bit compare(vmm_data actual. end return (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. . virtual function build().m. . end .m2s. vmm_sb_ds::INPUT).. ref bit drop)...mst. this.m2s.. m2s_sb m2s. apb_rw cycle. . this.. tb_top. It is only necessary to invoke the proper checking function. endtask: pre_cycle .. endclass: apb_master_to_sb class tb_env extends vmm_env. this.. endclass: tb_env Integrating the checking part is easier since the response generator is implemented directly in the environment... function new(m2s_sb m2s.out_chan). .. m2s_sb m2s = new. class apb_master_to_sb extends apb_master_cbs. . VMM Primer: Using the Data Stream Scoreboard 11 .)..m2s = m2s... identifying the stream this response has been observed on.File: DataStream_SB/tb_env..). endfunction: build . 0.gen.mst = new("Master". endfunction: new virtual task pre_cycle(apb_master xactor. ..append_callback(cbs).. begin apb_master_to_sb cbs = new(this.insert(cycle. this..sv .. this. .. class tb_env extends vmm_env.resp_chan[i]) begin int j = i..m2s. end join_none end endfunction: start .File: DataStream_SB/tb_env. fork forever begin apb_rw tr. . In this case. foreach (this. if (tr..sv .get(tr). one per slave.. j). ..expect_in_order(tr.resp_chan[j]. This is implemented using the Data Stream Scoreboard foundation class by defining three input streams..peek(tr).data = $random(). virtual function start(). this. endclass: tb_env Step 4: Slaves-to-Master The slaves-to-master scoreboard also requires three queues of input transactions.. the stream identifier corresponds to the value of ‘j’ in the response generator thread. The stream identifier is chosen to facilitate the association of a slave with its corresponding stream during integration. VMM Primer: Using the Data Stream Scoreboard 12 .kind == apb_rw::READ) begin tr. this.. // Poor random // strategy! ..resp_chan[j]. end this. A unique stream identifier must be assigned to each stream... "Slave 1".new("Slave->Master"). "Slave 2". "Slave 0". EXPECT).define_stream(0. All of the remaining information is unmodified. INPUT)...sv" class s2m_sb extends vmm_sb_ds. INPUT).define_stream(1. super. File: DataStream_SB/tb_env. this.. this. EXPECT).define_stream(0.. The completed transactions. The simplest approach is to leave the response transaction as-is and only compare the data value of READ cycles.define_stream(2. EXPECT). endclass: s2m_sb . `include "vmm_sb. this. this. function new(). super. endfunction: new .sv .define_stream(1.. this. The only information that is transferred from a slave to the master is the read back data.define_stream(0.sv . "Master"..define_stream(2. "Slave 2".. function new(). `include "vmm_sb. INPUT).File: DataStream_SB/tb_env.define_stream(0. this. will be different from the one replied by the slaves. "Master". this. endfunction: new "Slave 0". VMM Primer: Using the Data Stream Scoreboard 13 . "Slave 1". as reported by the master. EXPECT).new("Master->Slave"). this.. EXPECT).sv" class s2m_sb extends vmm_sb_ds. m2s. $cast(exp.kind == apb_rw::WRITE) return 1. apb_rw act.. Step 5: Integration Once the functionality of the scoreboard is defined.data). $cast(act. ref bit drop). actual). apb_rw cycle. exp. vmm_sb_ds::INPUT).sv .m2s = m2s.insert(cycle. vmm_data expected).data == exp. class apb_master_to_sb extends apb_master_cbs. File: DataStream_SB/tb_env. VMM Primer: Using the Data Stream Scoreboard 14 . if (act.s2m = s2m. this. s2m_sb s2m. this. function new(m2s_sb m2s. expected). it must then be integrated with the rest of the verification environment.. endfunction: new virtual task pre_cycle(apb_master xactor. this. return (act.. m2s_sb m2s. 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.virtual function bit compare(vmm_data actual.. endfunction: compare endclass: s2m_sb . s2m_sb s2m). . this.s2m. 0. if (cycle..addr[9:8] == 2'b11) return. virtual function start(). this. identifying the stream this response has been observed on. end .mst = new("Master". virtual function build(). It is only necessary to invoke the insertion function. apb_rw cycle). VMM Primer: Using the Data Stream Scoreboard 15 . m2s_sb m2s = new. . .. this.out_chan).expect_in_order(cycle. .m2s..kind == apb_rw::WRITE) return.append_callback(cbs)..mst. s2m_sb s2m = new....gen. begin apb_master_to_sb cbs = new(this. endtask: post_cycle endclass: apb_master_to_sb class tb_env extends vmm_env..addr[9:8])).endtask: pre_cycle virtual task post_cycle(apb_master xactor.. if (cycle. . File: DataStream_SB/tb_env. endfunction: build . tb_top. endclass: tb_env Integrating the checking part is again easier since the response generator is implemented directly in the environment. this.. class tb_env extends vmm_env. .sv . this.inp_stream_id(cycle....s2m).m. data = $random().kind == apb_rw::READ) begin tr. this. this.resp_chan[i]) begin int j = i.. end this.expect_in_order(tr.insert(tr.get(tr).. 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. VMM Primer: Using the Data Stream Scoreboard 16 . end join_none end endfunction: start .foreach (this.s2m.resp_chan[j]. j). You can verify the correct operation of the design by simulating the now self-checking verification environment.inp_stream_id(j)). vmm_sb_ds::INPUT.peek(tr). . verification environments or integrate a Register Abstraction Layer model.resp_chan[j]. if (tr. // Poor random // strategy! this. fork forever begin apb_rw tr. You may consider reading other publications in this series to learn how to write VMM-compliant command-layer transactors.m2s. 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 If the address is specified using a BYTE granularity.Address Granularity It is important to understand the address granularity of the DUT. STATUS[31:8] MASK[15:0]. The address granularity refers to the minimum number of bytes that can be uniquely addressed. MASK[31:16] 24’h000000. STATUS[31:24] MASK[31:0] 8’h00. MASK[31:8] 16’h0000. VMM Primer: Using the Register Abstraction Layer 5 . as defined in Table 1. the address space of the DUT would look like Table 6. There are two possibilities for interpreting these two addresses. the address space of the DUT would look like Table 7. Table 6 BYTE Address Granularity Data (32 bits) STATUS[31:0] MASK[7:0]. assuming it is LITTLE_ENDIAN. STATUS[31:16] MASK[23:0]. Consider the address of the STATUS and MASK registers. MASK[31:24] Address 0x0010 0x0011 0x0012 0x0013 0x0014 0x0015 0x0016 0x0017 If the address is specified using DWORD (32 bits) granularity. This effectively shifts the address value left by two bits and creates 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. Its address space is thus illustrated by Table 8 and effectively implements a DWORD granularity. as illustrated in Table 8.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. VMM Primer: Using the Register Abstraction Layer 6 . a design with BYTE granularity will often not implement the least significant two bits of the address bus. The COUNTERS registers. a DWORD address granularity will be assumed. such as an Excel spreadsheet. Since the data path of the DUT is 32-bits. RAL assumes that the address granularity is equal to the width of the data path. being identical and located at consecutive addresses can be specified using a register array. } The registers are declared in the block using a register description. This will allow the RALF description of the block to be included in a RALF description of the system that instantiates it.. Do not name your block “DUT”—that is simply begging to collide with the name of another block or system similarly badly named. The bytes attribute defines the width of the physical data path when accessing registers and memories in the block. . The smallest unit that can be used to represent a design in a RALF description is the block. File: RAL/slave..ralf block slave { bytes 4. It can be captured by hand from the specification above or it could be automatically generated from a suitably formatted specification document.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. The name of the block should be relevant and somewhat unique. VMM Primer: Using the Register Abstraction Layer 7 . The address offset of each register within the blocks is also specified at the same time. .File: RAL/slave.. } { CHIP_ID @’h0000 { STATUS @’h0010 { MASK @’h0014 { COUNTERS[256] @’h1000 { If no address offset is specified for a register. 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. { CHIP_ID @’h0000 { STATUS @’h0004 { MASK @’h0005 { VMM Primer: Using the Register Abstraction Layer 8 .. the shifting may have to be undone in the translation transactor (see Step 4)..ralf block slave bytes 4.. register .. the shifted values) should be specified. } register . the address offset increment refers to the shifted address value. To avoid this problem. } register .. Because the block is assumed to have a DWORD granularity.... not the documented BYTE granularity address value. In some cases. } register . } register . the DWORD granularity address values (i.. File: RAL/slave.. register ..e. } .. } register .ralf block slave bytes 4... it is assumed to be incremented by one from the previous register address offset. VMM Primer: Using the Register Abstraction Layer 9 . access ro. It is not necessary to specify unused or reserved bits if they are read-only and read as zeroes. and justified in the least significant bits.ralf block slave { bytes 4. } } register STATUS @’h0004 { field BUSY (BUSY) { bits 1. } field PRODUCT_ID { bits 10. 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. access ro. Fields are assumed to be contiguous. reset ’h03. } field CHIP_ID { bits 8. The last remaining element to be specified is the DMA RAM. reset ’h5A. File: RAL/slave..} register COUNTERS[256] @’h0400 { . register CHIP_ID @’h0000 { field REVISION_ID { bits 8. access ro. } The fields inside each register are then specified using a field description.... A field is the smallest of information and describes a set of consecutive bits with identical behavior. } . access ro. reset ’h176. } field MODE (MODE) { bits 3. The backdoor access code allows the model to directly access registers and memories without VMM Primer: Using the Register Abstraction Layer 10 . } field READY (RDY) @16 { bits 1. access rw. access w1c. reset ’h0. } field TXEN (TXEN) { bits 1.reset ’h0. } } register MASK @’h0005 { field READY (RDY_MSK) @16 { bits 1. } } When (HDL_PATH) is specified for registers and memories. reset ’h0. reset 3’h0. backdoor access code for those registers and memories can be automatically added to the RAL model. access rw. access rw. } } memory DMA_RAM (DMA) @’h0800 { size 1k. bits 32. access rw. reset ’h0. access ru. reset ’h0. } } register COUNTERS[256] @’h0400 { field value { bits 32. ...ralf block slave { bytes 4. } field TXEN (TXEN) { . } register STATUS @’h0004 { field BUSY (BUSY) { .. the registers and the memories can be read or written without penalty of simulation cycles. } field READY (RDY) @16 { . Therefore.going through the physical interface bus functional model. memory DMA_RAM (DMA) @’h0800 { .. } } register MASK @’h0005 { field READY (RDY_MSK) @16 { ... } } VMM Primer: Using the Register Abstraction Layer 11 . } field MODE (MODE) { ...... File: RAL/slave.. } } .... register CHIP_ID @’h0000 { . .ralf The generated code is not designed to be read or subsequently manually modified.sv: Command: % ralgen –b –l sv –t slave slave. rand vmm_ral_field READY... . RAL/ral_slave.sv class ral_reg_slave_CHIP_ID extends vmm_ral_reg. . All other lines not shown are not explicitly documented and should not be relied upon. class ral_reg_slave_STATUS extends vmm_ral_reg.. rand vmm_ral_field BUSY. However. The following generated RAL model corresponds to the documented output. the ralgen script is used to generate the corresponding RAL model. rand vmm_ral_field REVISION_ID. rand vmm_ral_field CHIP_ID.. class ral_reg_slave_MASK extends vmm_ral_reg. endclass : ral_reg_slave_STATUS .. rand vmm_ral_field PRODUCT_ID.... The following command will generate a SystemVerilog RAL model of the slave block in the file ral_slave. rand vmm_ral_field TXEN. the structure of the generated RAL model will be outlined to demonstrate how it mirrors the RALF description. VMM Primer: Using the Register Abstraction Layer 12 . rand vmm_ral_field READY. rand vmm_ral_field MODE. . endclass : ral_reg_slave_CHIP_ID .Step 2: Model Generation Once the registers and memories have been specified in a RALF file.. The register array is modeled using an array of abstraction classes. rand ral_mem_slave_DMA_RAM DMA_RAM.. RAL/ral_slave.. vmm_ral_field BUSY.... vmm_ral_field MODE. The block abstraction class contains a property for each register in the block that refers to an abstraction class for that register.CHIP_ID_PRODUCT_ID. VMM Primer: Using the Register Abstraction Layer 13 . rand vmm_ral_field value. .CHIP_ID_REVISION_ID. class ral_block_slave extends vmm_ral_block.STATUS_BUSY.. endfunction: new endclass: ral_block_slave The first thing to notice about the RAL model is the abstraction class that corresponds to the block. rand ral_reg_slave_MASK MASK. rand vmm_ral_field CHIP_ID_CHIP_ID. . rand vmm_ral_field REVISION_ID.COUNTERS_value[256]. rand ral_reg_slave_COUNTERS COUNTERS[256]. rand vmm_ral_field PRODUCT_ID. function new(int cover_on = vmm_ral::NO_COVERAGE.. vmm_ral_field TXEN.STATUS_MODE.sv .STATUS_TXEN. rand vmm_ral_field MASK_READY.. . vmm_ral_field STATUS_READY. rand rand rand rand rand ral_reg_slave_STATUS STATUS. ). rand vmm_ral_field value[256]. rand ral_reg_slave_CHIP_ID CHIP_ID.endclass : ral_reg_slave_MASK class ral_reg_slave_COUNTERS extends vmm_ral_reg. rand ral_reg_slave_CHIP_ID CHIP_ID. endclass: ral_reg_slave_COUNTERS class ral_block_slave extends vmm_ral_block. sv class ral_reg_slave_CHIP_ID extends vmm_ral_reg. endclass: ral_block_slave VMM Primer: Using the Register Abstraction Layer 14 .CHIP_ID_REVISION_ID.. rand vmm_ral_field REVISION_ID. There is also a property for each field in a register in the block abstraction class. .. This allows fields to be referenced without regards to their location in a specific register. this requires that the field name be unique within the block. rand vmm_ral_field REVISION_ID. rand vmm_ral_field CHIP_ID_CHIP_ID. .. For example. RAL/ral_slave. class ral_block_slave extends vmm_ral_block. rand vmm_ral_field CHIP_ID. . because the field named CHIP_ID in the register named CHIP_ID conflicts with the register of the same name.. . there are no class properties of that name in the block abstraction class. rand ral_reg_slave_STATUS STATUS.. rand ral_reg_slave_MASK MASK.. thus allowing them to be relocated without having to modify the code that use them. rand ral_mem_slave_DMA_RAM DMA_RAM. rand ral_reg_slave_COUNTERS COUNTERS[256]. .. endclass : ral_reg_slave_CHIP_ID . there is no class property named CHIP_ID for the field in the block abstraction class...... . Similarly. the register abstraction class for a register contains a property for each field it contains.. rand vmm_ral_field PRODUCT_ID...CHIP_ID_PRODUCT_ID. endclass: ral_block_slave Similarly. because there are two fields named READY in different registers.. rand vmm_ral_field PRODUCT_ID. However. rand ral_reg_slave_CHIP_ID CHIP_ID.. ... access rw.Every class property in the abstraction classes has the rand attribute. constraint valid { value < 3’h6... File: RAL/slave. field TXEN (TXEN) { bits 1.. access rw. Each class contains virtual task read() and virtual task write() for backdoor read and write access.value != 1’b1.. However. constraint status_reg_valid { (MODE. this attribute is turned off by default in all fields unless a constraint—even an empty one—has been specified for that field. register STATUS @’h0004 { . } For every register and memory with (HDL_PATH) specified. } } . reset ’h0. } } .value == 3’h5) -> TXEN. VMM Primer: Using the Register Abstraction Layer 15 . reset 3’h0.. constraint valid {} } field MODE (MODE) { bits 1.ralf block slave { . a backdoor access class is automatically generated. This allows the content of a RAL model to be randomized. Both tasks utilize the compiler directive ‘SLAVE_TOP_PATH to define the hierarchical path to registers in the DUT. RDY.RDY = data[16:16]. the constructor for the block abstraction class has a default argument vmm_ral::NO_COVERAGE. data[0:0] = ‘SLAVE_TOP_PATH. ‘SLAVE_TOP_PATH. class ral_reg_slave_STATUS_bkdr extends vmm_ral_reg_backdoor..sv .. data[1:1] = ‘SLAVE_TOP_PATH.. By default. function new(int cover_on = vmm_ral::NO_COVERAGE. endtask endclass .. end status = vmm_rw::IS_OK.. .TXEN.. end status = vmm_rw::IS_OK.. endfunction: new endclass: ral_block_slave VMM Primer: Using the Register Abstraction Layer 16 .sv ..). no functional coverage model is included.. data[16:16] = ‘SLAVE_TOP_PATH. data[4:2] = ‘SLAVE_TOP_PATH..)...BUSY. Lastly. ‘SLAVE_TOP_PATH.TXEN = data[1:1]. endtask vitual task write(output vmm_rw::status_e status.. begin ‘SLAVE_TOP_PATH. File: RAL/ral_slave. ..MODE = data[4:2]. vitual task read(output vmm_rw::status_e status.MODE. ..). begin data = ‘VMM_RAL_DATA_WIDTH’h0. .. . class ral_block_slave extends vmm_ral_block.File: RAL/ral_slave. psel ).sv module tb_top.apb_addr ... (apb0.. VMM Primer: Using the Register Abstraction Layer 17 . apb_if apb0(. using the bit type (Rule 4-17)..penable ). File: RAL/tb_top. endmodule: tb_top (apb0. (apb0.. That is because they are not documented and not intended to be used directly. The clock generator ensures that no clock edges will occur at time zero (Rule 4-16). 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.apb_enable ..paddr[15:2] ).pwdata[31:0]).prdata[31:0]). (apb0.apb_wdata . . The connection to the DUT pins are specified using a hierarchical reference to the wires in the interface instance. .apb_sel . The DUT and the relevant interfaces are instantiated in a top-level module (Rule 4-13). This top-level module also contains the clock generators (Rule 4-15) and reset signal. 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. Only block and system abstraction classes are intended as end-user RAL models.Note that the constructors for the other abstraction classes are not shown.. (apb0. (apb0.apb_write .apb_rdata .pwrite ).).).. slave_ip dut_slv(. (clk).paddr[15:2] ).apb_addr . class apb_rw_xlate extends vmm_rw_xactor.sv . (rst)). bit rst = 0. This task can use any transactor to execute the requested generic transactions.psel ).apb_sel .File: RAL/tb_top. VMM Primer: Using the Register Abstraction Layer 18 .rst always #10 clk = ~clk. 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. File: RAL/apb_rw_xlate. (apb0. slave_ip dut_slv(. . bit clk = 0.apb_enable .clk . apb_if apb0(clk). (apb0.prdata[31:0])..pwdata[31:0]). (apb0..pwrite ). 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.sv module tb_top. endmodule: tb_top (apb0.penable ).. Step 4: Physical Interface A RAL model is not aware of the physical interface used to access the registers and memories. (apb0.apb_wdata .apb_rdata .. (apb0.apb_write . int unsigned stream_id.start_xactor()... super. . this. virtual task execute_single(vmm_rw_access tr). this.bfm... Pass a reference to an instance of a suitable command-level transactor via the constructor argument. File: RAL/apb_rw_xlate... To ensure that the command-level transactor is started when the translation transactor is started. endtask: execute_single endclass: apb_rw_xlate The generic transaction is then translated into an equivalent transaction suitable for the command-level transactor used. super.sv" . 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. endfunction .new("APB RAL Master"..start_xactor(). apb_master bfm.... virtual task execute_single(vmm_rw_access tr). endfunction: new virtual function void start_xactor(). inst. class apb_rw_xlate extends vmm_rw_xactor.bfm = bfm.sv `include "apb_master. Note that it may be necessary to adjust the address specified by the RAL VMM Primer: Using the Register Abstraction Layer 19 .. . function new(string inst. stream_id). apb_master bfm). its start_xactor() method must be called in the extension of the translation transactor’s start_xactor() own method. kind = apb_rw::READ. apb_rw cyc = new. because paddr[1:0] is not used by the DUT (because it uses BYTE granularity addressing with a DWORD data bus). // DUT uses BYTE granularity addresses // but with a DWORD datapath cyc.addr = {tr.start_xactor(). super.new("APB RAL Master". cyc. end else begin // Read cycle cyc. inst. In our case.kind = apb_rw::WRITE. File: RAL/apb_rw_xlate. endfunction: new virtual function void start_xactor(). 2'b00}.data.bfm. Once the transaction has been executed according to the translator execution model. apb_master bfm. apb_master bfm).start_xactor().sv `include "apb_master. int unsigned stream_id. VMM Primer: Using the Register Abstraction Layer 20 .data = tr. endfunction virtual task execute_single(vmm_rw_access 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.kind == vmm_rw::WRITE) begin // Write cycle cyc. function new(string inst.model in the generic transaction to the physical address used by the physical protocol. super.addr.bfm = bfm. this. if (tr. you must shift the specified address into paddr[31:2].sv" class apb_rw_xlate extends vmm_rw_xactor. stream_id).sv" `include "vmm_ral. this. VMM Primer: Using the Register Abstraction Layer 21 . if (tr. multiplexed domains Note that the menu number used to select the appropriate template may differ from the number in the above list. Command % vmmgen –l sv The relevant templates for writing translation transactors are: • • RAL physical access BFM.end this. single domain RAL physical access BFM. else begin ‘vmm_error(log. The style used to implement the translation transactor shown in this primer is provided by the “multiplexed domains” template.kind == vmm_rw::READ) begin tr.in_chan. tr.status = vmm_rw::IS_OK. assert(!($isunknown(tr.data.data = cyc.status = vmm_rw::ERROR. "Data Contains X Value").data))) tr.bfm. 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.put(cyc). ral_model = new(vmm_ral::NO_COVERAGE).sv" class tb_env extends vmm_ral_env..set_model() method. function new().. . Therefore. RAL model randomization has been enabled. DUT configuration can be randomized in the gen_cfg() step. super.ral.set_model(this.sv `ifndef TB_ENV__SV `define TB_ENV__SV `include `include `include `include .ral_model). endclass: tb_env `endif Via constraint definitions in the RALF description.. The RAL model is instantiated in the constructor so it can be used to generate a suitable configuration in the gen_cfg() step. ... VMM Primer: Using the Register Abstraction Layer 22 . "vmm.. The RAL model is instantiated in the environment constructor then registered with the base class using the ral.sv" "ral_slave...sv" "apb. ral_block_slave 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. File: RAL/tb_env. endfunction: new ..sv" "vmm_ral. . File: RAL/tb_env. if (!(this.sv" "apb. apb_master mst.. The translation transactor must then be registered using the ral. apb_rw_xlate ral2apb. 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)...sv .. ral_block_slave ral_model..log..... endfunction: gen_cfg .add_xactor() method. 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... 4-35).sv" "vmm_ral.sv" class tb_env extends vmm_ral_env.randomize() )) `vmm_error(this. . function void gen_cfg()..File: RAL/tb_env. . .sv" "apb_rw_xlate. .sv `ifndef TB_ENV__SV `define TB_ENV__SV `include `include `include `include `include "vmm. ral_model = new(vmm_ral::NO_COVERAGE).ral_model. function new(). class tb_env extends vmm_ral_env.sv" "ral_slave. "ral_model could not be \n randomized"). VMM Primer: Using the Register Abstraction Layer 23 . ral_model = new(vmm_ral::NO_COVERAGE).super. endclass: tb_env `endif Instead of specifying how to reset the DUT in the extension of the vmm_ral_env::reset_dut() method. this... function new(). 0.sv" "apb_rw_xlate.build().ral_model).sv" class tb_env extends vmm_ral_env. this. .ral.ral2apb).ral2apb = new("APB". endfunction: build . apb_master mst..ral. . this.add_xactor(this. super.mst).sv" "apb.sv `ifndef TB_ENV__SV `define TB_ENV__SV `include `include `include `include `include "vmm. 0. VMM Primer: Using the Register Abstraction Layer 24 .sv" "ral_slave.. endfunction: new virtual function void build(). File: RAL/tb_env. apb_rw_xlate ral2apb.mst = new("APB".apb0). This task will be called by the default implementation of the vmm_ral_env::reset_dut() method.sv" "vmm_ral. it is specified in the extension of the vmm_ral_env::hw_reset() task.set_model(this.. ral_block_slave ral_model. thus satisfying Rule 4-30. tb_top.. this. mst = new("APB". this. virtual task cfg_dut().. update the DUT to reflect the randomized RAL model values. . vmm_ral::BACKDOOR). tb_top. repeat (3) @ (negedge tb_top.ral.rst <= 1'b0.update(status. tb_top.ral_model). ral_model.. File: RAL/tb_env.add_xactor(this. Recall that the RAL model was randomized in the gen_cfg() step.clk).ral2apb = new("APB"...ral. this. endfunction: build virtual task hw_reset().mst).. endfunction: cfg_dut .ral2apb).apb0).set_model(this.. tb_top.rst <= 1'b1. .sv . 0.build(). super. endtask: hw_reset .. class tb_env extends vmm_ral_env. endclass: tb_env `endif Finally. in the cfg_dut() step.super.. endfunction: new virtual function void build()..clk). this. endclass: tb_env VMM Primer: Using the Register Abstraction Layer 25 . repeat (3) @ (negedge tb_top.. 0. this. Step 6: The Pre-Defined Tests Before you can run one of the pre-defined tests.svh `define RAL_TB_ENV tb_env `include "tb_env. Notice the compiler directive SLAVE_TOP_PATH which will be needed for backdoor access codes used in mem_walk. you must specify which files must be included in the simulation first and what is the name of the verification environment class. or the integration of the two will be identified by this simple test.svh in the current working directory and defining the `RAL_TB_ENV macro respectively.dut tb_top. Command % vcs –R –sverilog –ntb_opts rvm+dtm \ +incdir+. VMM Primer: Using the Register Abstraction Layer 26 . Many of the problems with the DUT. They can all be found in the $VCS_HOME/etc/vmm/sv/RAL/tests directory.sv \ $VCS_HOME/etc/vmm/sv/RAL/tests/hw_reset. File: RAL/ral_env./apb +define+SLAVE_TOP_PATH=tb_top.sv Other tests are provided with RAL. etc.. The RAL User Guide details the functionality of each test. mem_access. Note that the pre-defined tests are available as unencrypted source code. the RAL model. This is done by the file ral_env. 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.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. wildcard bins bit_0_wr_as_1 = {2'b10}.....weight = 4. class ral_cvr_reg_slave_STATUS. VMM Primer: Using the Register Abstraction Layer 27 . endclass .ralf The generated coverage model can be large (4 bins per bit in every field). This will enable VCS to create and collect a coverage database.sv . Command % ralgen –c b –b –l sv –t slave slave. A report can then be generated using utilities such as the Unified Report Generator. 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... The coverage model can be enabled by passing the appropriate argument to the RAL model constructor.. wildcard bins bit_0_rd_as_1 = {2'b11}.Step 7: Coverage Model A functional coverage model can be added to the RAL model by using an option in the ralgen script. wildcard bins bit_0_rd_as_0 = {2'b01}. . File: RAL/ral_slave. is_read} { wildcard bins bit_0_wr_as_0 = {2'b00}. TXEN: coverpoint {data[1:1].. } . option. reg_value))... endclass .ral_model). This can be implemented by creating a user defined test to achieve the desired DUT configuration.MASK_READY. env...peek(status. ..ral_model.MODE. ... . 1'h1). function new().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.write(status.write(status. File: RAL/user_test. $psprintf(". super.write(status. 3'h3).. endfunction: new ..File: RAL/tb_env. env. .MODE... The RAL model provides easy access to write the appropriate values in the DUT registers through functions and tasks.ral_model..ral. env.. ral_model = new(vmm_ral::REG_BITS)..ral_model. `vmm_note(log.set_model(this...cfg_dut(). .sv program user_test.TXEN.sv class tb_env extends vmm_ral_env. // reading registers with backdoor access env. VMM Primer: Using the Register Abstraction Layer 28 . reg_value). . // writing registers with frontdoor access env. "... 1'h1). ". reg_value).ral_model.. endprogram There are many other VMM Primers that cover topics such as how to write VMM-compliant command-layer transactors. reg_value)).. . `vmm_note(log. reg_value)). env. $psprintf("..peek(status. functional-layer transactors or verification environments.peek(status. $psprintf("... VMM Primer: Using the Register Abstraction Layer 29 . `vmm_note(log. ".env.. reg_value).TXEN.MASK_READY.ral_model. VMM Primer: Using the Register Abstraction Layer 30 .