• No results found

8 Stimulus Generation Topics

8 interrupt_seq.start(this);

8.7 Protocol Layering

Some verification environments require the layering of data items of different protocols. Examples include TCP over IP, and ATM over Sonet.

Other applications such as register and memory packages provide for traffic to be sent by way of a lower-layer bus component. Sequence layering and virtual sequences are two ways in which sequencers can be composed to create a layered protocol implementation.

Figure 8-1 Layering Environments

The example environment in Figure 8-1 connects the lower env sequencer to the upper env driver. In most cases, the driver is not necessary and the lower sequencer will request data items directly from the upper sequencer. The following sections discuss options for layering stimulus.

8.7.1 Layering of Protocols

The classic example of protocol layering can be described by generic upper- and lower-levels (or layers) of a protocol. An array of bytes may be meaningless to the lower-level protocol, while in the upper-level protocol context, the array provides control and data messages to be processed appropriately.

For example, assume that there are two environments/sequencers that can be layered on top of each other as illustrated in Figure 8-1. The requirements for such layering include:

Layering protocol environments without up-front planning of the lower and upper layers. This refers to the need to decouple layers without dependencies or knowledge of what needs to be connected.

Layer randomization control. While the traffic from the upper layer is used by the lower layer, you still need to control randomization of all layers simultaneously. For example, you need to allow randomization, allow for directed and mixed traffic, and you need to be able to

all layers simultaneously. For example, you need to allow randomization, allow for directed and mixed traffic, and you need to be able to leverage a sequence library at each sequencer layer.

Multi-sequencer operations and control. For example, multiple high layers driving a single low-layer sequencer.

Performance optimization. In some cases, layers are mainly mediators between other layers and do not require allocation or randomization of data.

Upper layers have access to lower layers. Upper layers may need to grab control or reset activity of lower layers.

8.7.2 Layering and Sequences

Layering is best implemented with sequences. There are two ways to do layering using sequences:

Layering inside a single sequencer, which applies to simple cases. See “Layering Inside One Sequencer” for details.

Layering multiple sequencers, which applies to all layering cases. See “Layering of Several Sequencers” for details.

Consider a low-layer sequencer driving lower_env_items, which are defined as:

class lower_env_item extends uvm_sequence_item;

rand bit[‘DATA_SIZE-1:0] payload[];

rand int pl_size;

constraint c_pload { pl_size < ‘MAX_PL;

payload.size() == pl_size; }

... // Constructor and UVM automation macros go here.

endclass : lower_env_item

The lower environment will have its own sequence library capable of generating low-level random sequences of lower_env items. This set of random sequences will be used to verify the lower-layer protocol and connection to the DUT. A low-level sequence base class is defined as:

class lower_env_base_seq extends uvm_sequence #(lower_env_item);

Now consider a simple upper-layer data item which contains one or more lower_env items.

class upper_env_item extends uvm_sequence_item;

rand int unsigned max_item_size; // maximum pl_size of lower item rand int unsigned num_items; // maximum number of lower items rand lower_env_item item[]; // array of lower items

constraint c_item { num_items == item.size();

num_items inside {[1:10]};}

constraint c_item_size { max_item_size inside {[10:20]}; } ... // Constructor and UVM automation macros go here.

function void post_randomize();

foreach(item[i]) begin

item[i] = lower_env_item::type_id::create($psprintf("item[%0d]", i));

assert(item[i].randomize with {pl_size <= max_item_size; })) else ‘uvm_error("RNDFAIL", "item randomization failed")

end

endclass : upper_env_item

Let’s consider the options for layering the upper_env_item in a verification environment.

8.7.2.1 Layering Inside One Sequencer

For simple cases, you can layer inside one sequencer by generating a data item of the upper layer within a lower-layer sequence. Do this by creating a new sequence for the lower-layer sequencer. Figure 8-2 below depicts the architecture for using a single sequencer to layer the stimulus.

Figure 8-2 Single-Layer Sequencer Architecture

For example:

1 class upper_env_item_seq extends uvm_sequence #(lower_env_item);

2 ... // Constructor and UVM automation macros

3 upper_env_item u_item; // Contains an array of lower_env_items 4 task body();

5 // Create a upper-level item.

6 ‘uvm_create(u_item)

7 ... // Randomize it here with appropriate constraints.

8 for(int i = 0 ; i< u_item.num_items; i++) 9 ‘uvm_send(u_item.item[i])

10 endtask : body

11 endclass: upper_env_item_seq

The upper_env_item_seq sequence generates a single upper_env_item and sends it in chunks of one or more lower_env_items. In this simple example, the upper item is comprised of lower items, so the lower items are automatically created when the upper item is randomized. In many cases, the lower item will have additional fields that will need to be generated, randomized and sent. For this, you will need to create the lower item and populate it with data from the upper item before randomizing and sending.

8.7.2.2 Layering of Several Sequencers

This approach to layering several sequencers uses multiple sequencers as shown in Figure 8-3, below.

Figure 8-3 Multi-Layer Sequence Architecture

Taking the upper_env_item and lower_env_item example, there is a lower-layer sequence and a upper-layer sequence (complete with their sequencers). The lower-layer sequence pulls data from the upper-layer sequencer (or from the upper-layer driver). Each sequencer is encapsulated in a UVC so that layering can be done by connecting together the UVCs. “Using Layered Sequencers”, goes into more details on this topic.

8.7.3 Styles of Layering

This section discusses various styles you can use in layering.

8.7.3.1 Basic Layering

The most general case of layering consists of the driver accepting layer1 items, in which:

The layer1 items are constructed from layer2 items in some way. The layer2 items are, in turn, constructed from layer3 items, and so on.

For every layerN and layerN+1, there is a mechanism that takes layerN+1 items and converts them into layerN items.

You can also have multiple kinds of layer1 and layer2 items. In different configurations, you might want to layer any kind of layer2 item over any kind of layer1 item.

The remainder of this section describes possible variations, and complications, depending on the particular protocol or on the desired test-writing flexibility.

Figure 8-4 Layering of Protocols

8.7.3.2 One-to-One, One-to-Many, Many-to-One, Many-to-Many

A conversion mechanism might need to cope with the following situations (see Figure 8-5):

One-to-one—One upper-layer item must be converted into one low-layer item.

One-to-many—One large upper-layer item must be broken down into many low-layer items.

Many-to-one—Many upper-layer items must be combined into one large low-layer item (as in Sonet, for example).

Many-to-many—Multiple upper-layer items must be taken in and converted into multiple lower-layer items. For example, upper-layer packets are 10 bytes long, and low-layer packets are three to 35 bytes long. In this case, there could be remainders.

Figure 8-5 Layer Mapping

8.7.3.3 Different Configurations at Pre-Run Generation and Run Time

A system might need to support different modes of operation as defined by topology, data type, or other application-specific requirements. For example, in one environment, you may have only layer1 items. In another environment, layer1 items might be dictated by layer2 items. You may also want to decouple the layers further, for example, so that layer2 items could drive either layer1 items or layer1 cells (on another interface) or both.

At times, you might have a mix of inputs from multiple sources at run time. For example, you may want to have one low-layer sequencer send items that come from several high-layer sequencers.

8.7.3.4 Timing Control

In some configurations, the upper-layer items drive the timing completely. When upper-layer items are created, they are immediately converted into lower-layer items.

In other configurations, the lower-layer sequences pace the operation. When a lower-layer `uvm_do macro is executed, the corresponding upper-layer item should appear immediately, in zero time.

Finally, there is the case where items are driven to the DUT according to the timing of the lower-layer sequences, but the higher-layer sequences are not reacting in zero time. If there is no data available from the high-layer sequences, then some default value (for example, a zero filler) is used instead. In this case, the upper sequencer try_next_item() would be used by the lower-level sequence.

8.7.3.5 Data Control

In some configurations, the upper-layer items completely dictate which low-layer items reach the DUT—in which case, the lower layer simply acts as a slave.

Often, however, both layers influence what reaches the DUT. For example, the high layer might influence the data in the payload while the lower layer influences other attributes of the items reaching the DUT. In these cases, the choice of sequences for both layers is meaningful.

8.7.3.6 Controlling Sequences on Multiple Sequencers

In the most general case, you have a graph consisting of several sequencers, some of which may control sequence execution on other sequencers and some may generate items directly. Some low-layer driver sequencers are connected to the DUT, some upper-layer driver sequencers are layered above them, and some top-level sequencers feed into all of the driver sequencers below them.

In the example configuration shown in Figure 8-6, Most-General Case of Using Virtual Sequencers, a low-layer sequencer (L1B) receives input from multiple high-layer sequencers (two instances of L2A) as well as from a controlling sequencer.

Figure 8-6 Most-General Case of Using Virtual Sequencers

8.7.4 Using Layered Sequencers Figure 8-7 shows the layering of sequencers.

Figure 8-7 Layered Sequencers

These layered sequencers operate as follows:

Upper-layer sequencers operate as usual, generating upper-layer data items and sending them through the seq_item_pull_export. In most cases, you will not need to change the upper-layer sequencer or sequences that will be used in a layered application.

The lower-layer sequencers connect to the upper-layer sequencer(s) from which information must be pulled. The pulled information (a upper-layer item) is placed in a property of the sequence and is then used to constrain various properties in the lower-layer item(s). The actual connectivity between the layers is done in the same manner as the connection between a sequencer and a driver. To connect to the upper-layer sequencer, you must declare a corresponding uvm_seq_item_pull_port in the lower-layer sequencer (see Example 8–2). The connection itself is performed at the time the containing component’s connect() method is invoked.

The lower-layer sequencers send information to a lower-layer driver that interacts with a DUT’s physical interface.

Assuming you already have created (or are reusing) upper-layer and lower-layer sequencers, follow these steps below to create the layering.

To layer sequencers:

1. Create a lower-layer sequence that does the following:

• Repeatedly pulls upper-layer items from the upper-layer sequencer

• Translates them to lower-layer items

• Sends them to the lower-layer driver

To preserve late generation of the upper-layer items, pull the upper-layer items from within the lower-sequence’s pre_do() task. This ensures that the upper-layer item will be randomized only when the layer driver is ready to start processing the matching lower-layer items.

2. Extend the lower-layer sequencer, and add a TLM interface that will allow it to connect to the upper-layer sequencer. You will need to register a factory override for the new sequencer type to be used instead of the original one.

3. Connect the lower-layer sequencer to the upper-layer sequencer using the same technique as when connecting a driver to a sequencer.

4. Configure the lower-layer sequencer’s default sequence to be the sequence you created in Step 1 above.

Example 8–2 Layered Sequencers

For this example, the lower-layer components are likely to be encapsulated inside an agent modeling the interface protocol. This example shows how to achieve layering without introducing the recommended reuse structure, to keep the code compact.

1 // Upper-layer classes

2 class upper_item extends uvm_sequence_item;

3 ...

4 endclass : upper_item

5 class upper_sequencer extends uvm_sequencer #(upper_item);

6 ...

7 endclass : upper_sequencer 8 // Lower-layer classes

9 class lower_item extends uvm_sequence_item;

10 ...

10 ...

11 endclass : lower_item

12 class lower_sequencer extends uvm_sequencer #(lower_item);

13 uvm_seq_item_pull_port #(upper_item) upper_seq_item_port;

14 ...

15 function new (string name, uvm_component parent);

16 super.new(name, parent);

17 upper_seq_item_port = new("upper_seq_item_port",this);

18 ‘uvm_update_sequence_lib_and_item(...) 19 endfunction : new

20 ...

21 endclass : lower_sequencer

22 class lower_driver extends uvm_driver #(lower_item);

23 ...

24 endclass : lower_driver

Now create a lower-layer sequence that pulls upper-layer items and translates them to lower-layer items:

1 class upper_to_lower_seq extends uvm_sequence #(lower_item);

2 ... // Constructor and UVM automation macros go here.

12 virtual task pre_do(bit is_item);

13 if (is_item) p_sequencer.upper_seq_item_port.get_next_item(u_item);

14 endtask : pre_do

15 // In the post_do task, signal the upper sequencer we are done.

16 // And, if desired, update the upper-item properties for the 17 // upper-sequencer to use.

18 virtual function void post_do(uvm_sequence_item this_item);

19 p_sequencer.upper_seq_item_port.item_done(this_item);

20 endfunction : post_do 21 endclass : upper_to_lower_seq

Example 8–3 Layering Sequences

The following example illustrates connecting a lower-layer sequencer with an upper-layer sequencer.

Note The lower-layer sequencer is likely to be encapsulated inside an interface UVC, therefore, it will be encapsulated in an env and an agent.

This does not change the layering scheme but changes the path used to connect the sequencers to each other in the tb file. The connection of the upper sequencer to the lower sequencer will typically happen in the tb env, whereas the connection from the lower sequencer to its driver will occur in the connect() phase of the agent.

1 // This code resides in an env class.

2 lower_driver l_driver0;

3 lower_sequencer l_sequencer0;