• No results found

4 UVM Library Basics

4 int unsigned num_votes;

4 int unsigned num_votes;

5 // Configurable fields must use the field declaration macros. Use the 6 // UVM_READONLY flag if the property should not be configurable.

7 ‘uvm_component_utils_begin(state)

8 ‘uvm_field_string(state_name, UVM_DEFAULT)

9 ‘uvm_field_int(num_votes, UVM_DEFAULT | UVM_DEC) 10 ‘uvm_component_utils_end

11 function new(string name, uvm_component parent);

12 super.new(name, parent);

13 endfunction : new

14 function void end_of_elaboration();

15 this.print();

16 endfunction : end_of_elaboration 17 endclass : state

18 state my_state1, my_state2;

19 initial begin

20 // configure BEFORE create

21 set_config_string("my_state1", "state_name", "GEORGIA");

22 set_config_string("my_state2", "state_name", "CONNECTICUT");

23 set_config_int("*", "num_votes", 7);

24 // create

25 my_state1 = state::type_id::create("my_state1", null);

26 my_state2 = state::type_id::create("my_state2", null);

27 fork

28 run_test();

29 #100 global_stop_request();

30 join 31 end

32 endmodule : test

Line 23: We used the “*” notation to configure the num_votes field. All states will use that configuration value.

The log information below shows the component configurations in the end_of_elaboration phase().

The example below demonstrates a use for the get_config_* API. You can use this API if you do not use the automation macros, or you want to query the configuration settings before or after the build() phase. In the example we use the get_config_int to decide if the coverage group needs to be constructed.

1 module test;

2 typedef enum bit [2:0] {FLORIDA, NEW_YORK, GEORGIA, CALIFORNIA, MICHIGAN, 3 TEXAS, MARYLAND, CONNECTICUT} state_name_enum;

4 class state extends uvm_component;

5 bit coverage_enable = 1;

5 bit coverage_enable = 1;

6 state_name_enum = FLORIDA;

7 ‘uvm_component_utils_begin(state)

8 ‘uvm_field_string(state_name_enum, state_name, UVM_DEFAULT) 9 ‘uvm_field_int(coverage_enable, UVM_DEFAULT|UVM_READONLY) 10 ‘uvm_component_utils_end

11 covergroup state_cg;

12 coverpoint state_name;

13 endgroup : state_cg

14 function new(string name="state", uvm_component parent);

15 super.new(name, parent);

16 void'(get_config_int("coverage_enable", coverage_enable));

17 if (coverage_enable)

18 state_cg = new(); // covergroup must be new'ed in constructor 19 endfunction : new

20 function void end_of_elaboration();

21 if(coverage_enable) state_cg.sample();

22 this.print();

23 endfunction : end_of_elaboration 24 endclass : state

25 state my_state;

26 initial begin

27 set_config_int("my_state", "state_name", NEW_YORK);

28 set_config_int("my_state", "coverage_enable", 0);

29 my_state = state::type_id::create("my_state", null);

30 fork

Line 16: Since a covergroup can only be created in the constructor, we must call the get_config_int() method to retrieve the coverage_enable field. The other fields will be retrieved in the build() method.

Line 28: Sets coverage_enable to 0 for my_state. In this example, the coverage group will not be created.

4.6 Transaction-Level Modeling in UVM

Over two decades ago, designers shifted from gate-level to RTL design. This shift was driven by the development of standard Verilog and VHDL RTL coding styles, as well as the availability of RTL synthesis and implementation tools. A major benefit of moving to RTL was that it enabled designers to focus more on the intended cycle level behavior designs and design correctness at this level, and much less on gate-level considerations.

Transaction-level modeling (TLM) is a similar shift in abstraction levels that has been occurring for both design and verification engineers.

With transaction-level models, the focus is on modeling distinct transactions flowing through a system, and less on clock cycle level behavior.

TLM has been used within testbenches for many years. In general, any testbench is at the transaction-level if it has components that create stimulus or that do coverage or checks using transactions rather than using clock cycle level behavior. To verify RTL DUTs, such testbenches use transactors (sometimes called “bus functional models” or BFMs) to convert between the transaction level and the RTL. In UVM, such transactors are also called drivers and collectors.

transactors are also called drivers and collectors.

In TLM, transactions in the system are modeled using method calls and class objects. There are several significant benefits that come from modeling at the transaction level rather than at the signal level:

TLM models are more concise and simulate faster than RTL models.

TLM models are at a higher level of abstraction, and more closely match the level of abstraction at which the verification engineer or design engineer thinks about the intended functionality. This makes it easier to write the models and easier for other engineers to understand them.

TLM models tend to be more reusable since unnecessary details which hinder reuse are moved outside of the models. Also, TLM enables use of object-oriented techniques such as inheritance and separation of interfaces from implementation.

The adoption of TLM is dependent on the availability of standard TLM modeling approaches, just as the adoption of RTL synthesis flows was dependent on the availability of standard RTL coding styles. Fortunately, in recent years, several important standardized TLM APIs have been defined. Two particularly prominent standards in the EDA and ESL area are the Open SystemC Initiative (OSCI) TLM 1.0 and TLM 2.0 standards.

The OSCI TLM 1.0 standard is a simple, general-purpose TLM API that is designed to model message passing. In message passing, objects (in this case transactions) are passed between components in a manner similar to packets being passed on a network.

In message passing as in packet transmission, there is no shared state between sender and receiver—rather, the only communication is contained within the messages that are passed.

The OSCI TLM 2.0 standard is designed to enable the development of high speed virtual platform models in SystemC. The TLM 2.0 standard is specifically targeted at modeling on-chip memory mapped buses, and contains many features to enable integration and reuse of components which connect to on-chip buses.

OSCI TLM 1.0 and TLM 2.0 are separate and distinct standards, each serving different purposes. Some people may assume that TLM 2.0 supersedes TLM 1.0 due to their naming, but this is not the case.

The UVM provides TLM APIs and classes which are based on the TLM 1.0 standard. This is because the general purpose message passing semantics of TLM 1.0 are well suited to the needs of transaction-level modeling for many verification components. TLM 1.0 is also well suited to modeling communication between different languages, for example between models in SystemVerilog, SystemC, and e. The TLM 1.0 interfaces in UVM can even be used to communicate with TLM 2.0 models in SystemC.

This section presents some of the key concepts for using TLM within UVM so that you can understand how to use TLM to construct reusable verification components. More detailed information on the various TLM classes in UVM is also available within the UVM Reference Manual.

4.6.1 Key TLM Concepts in UVM

4.6.1.1 Modeling Transactions

In UVM, a transaction is any class that extends from uvm_sequence_item. A transaction is defined by the user to contain whatever fields and methods are required to model the information that needs to be communicated between different components within the verification environment. For example, a simple packet might be modeled as follows:

1 class simple_packet extends uvm_sequence_item;

2 rand int src_addr;

3 rand int dst_addr;

3 rand int dst_addr;

4 rand byte unsigned data[];

5 constraint addr_constraint { src_addr != dst_addr; } 6 ...

7 endclass

A transaction typically contains sufficient data fields to enable a driver or transactor to create the actual signal-level activity that is required to represent the transaction data. A transaction may also contain additional data fields to control how randomization occurs, or for any other purposes within the verification environment. You can extend from transactions to include additional data members, functions, and constraints.

Later sections show how you can use the concept of extending from transactions to accomplish specific verification tasks with minimum effort.

4.6.1.2 TLM Ports and Exports

The TLM API in UVM specifies a set of methods that are called by models to communicate transactions between components. In UVM, a port object specifies the set of methods that can be called, while an export object provides the implementation of the methods. Ports and exports are connected together via connect() calls when the verification environment is constructed, and subsequent calls to the TLM methods on the port will execute the TLM methods implemented within the export.

Example 4–7 Transaction from a Producer to a Consumer Using put

In UVM TLM, the put interface can be used to send a transaction from a producer to a consumer. A simple example of such a producer in UVM follows:

As mentioned before, the put port is connected via a connect() call to a put export. The implementation of the put method that is called above is provided in the consumer component:

The result of connecting the port to the export and then calling the put method in the producer above is that the put implementation in the consumer will execute with the simple_packet object passed from producer to consumer.

TLM also include standard graphical notation to illustrate different type of communication. The put communication flow is captured in the block diagram below:

Figure 4-2 Simple Producer/Consumer put Communication

The TLM interfaces in UVM specify some simple rules that the producer and consumer both must follow. In this case, for the put interface, the rules are:

The implementation of the put method may block execution until the put completes. Therefore, the caller of the put method must work properly if the implementation of the put method blocks execution.

The producer is responsible for creating the packet transaction, and the consumer must not modify it (if it needs to it must copy it first).

By following these rules, it becomes possible to easily swap either the producer or consumer with alternative models that also adhere to the same simple TLM interface. The TLM API serves as a simple interface contract that promotes interoperability, much as standards such as USB and Ethernet do in the hardware world. By allowing easy swapping of models, UVM TLM plays a key role in enabling you to reuse models and meet verification goals, as we see in later chapters.

In the above example, there is a single process that runs in the producer, and when the put method is called, the flow of control passes to the put task within the consumer. The put method passes the transaction in the same direction as the control flow of the put method call itself.

In some modeling situations, it is necessary to have the transaction data flow happen in the opposite direction from the control flow of the TLM method call, because the consumer contains the process that requires the transaction data. A producer/consumer example in this case would instead use the get interface, and would be written as:

1 class producer_2 extends uvm_component;