Packing and unpacking data with uvm_packer.

Let’s present a scenario, where SPI protocol is used to generate various commands for your project.

SPI functions by transmitting a series of bits that are driven on MISO and MOSI. To interpret these bits, the user defines a set of commands that each series represents. For example, to trigger a write transaction, the command should look like this:

Vtool_Packing_and_unpacking_data_with_uvm_packer

First we need to send the transaction id (2b), followed by the opcode (1b), the address (16b), and finally the data (32b).

The SPI VIP is simple; it only recognizes zeros and ones, and doesn’t interpret their meaning. The user must create an extension of the base SPI base transaction, and create fields that represent the necessary information, which can be randomized and controlled through the sequences later on during verification.

The SPI drivers work with mosi_q and miso_q, meaning the extended transaction fields eventually need to be stored in the base transaction’s MISO/MOSI queues so they can be driven by the SPI drivers. The same applies to monitors. MOSI/MISO data collected by the SPI monitor is just an array of bits sampled from the interface. To get those bits to represent some meaningful data, the sampled array needs to be unpacked in the fields that the user created.

All of this can be done manually by implementing “for” loops to pack data into the MOSI/MISO queues, or to unpack it into needed fields, but UVM developers were kind enough to implement that behavior for us.

The uvm_packer class provides a policy object for packing and unpacking uvm_objects. These policies determine how packing and unpacking are performed. Packing an object causes the object to be placed into a bit array (byte or int).

A uvm_packer is useful when working with serial protocols, especially where the commands are user-defined. The uvm_packer is helpful for creating a clean and easily controlled environment for sequences, transactions, and scoreboards. Information in the objects is packed and unpacked easily without needing to create functions that will use many ’for’ loops to extract data from your objects.

A few steps must be followed to get this working.

Implementing packing methods

The uvm_object class contains do_pack() and do_unpack() hooks, which are user-defined functions that can be implemented in a transaction class, in order to specify how data should be packed or unpacked. These hooks are called by the pack() and unpack().

Note: Each type of command needs its own packing and unpacking hooks, as each command is different. It is up to the user to decide the best way to organize their commands.

Generating transactions in sequences

Let’s say we want to send spi_wr_tr. We can create and randomize this object, add constraints if desired, etc.

Now, can we send that object directly to the SPI sequencer? The answer is no, you cannot send it directly. Remember, the SPI driver works only with MISO/MOSI arrays/queues and SPI base transactions. Therefore, we need to cast our write object to a base object before sending it.

We can do this by implementing a function in our sequence library that will generate MISO/MOSI data by using information from the extended objects.

This will most likely need to be done for all types of commands (read, write, etc.).Note: The constraint may not work;it’s just an example.

Collecting transactions in scoreboards

Now we need to write checkers for our transactions. The SPI monitor will sample the MISO/MOSI data, store the information in the base SPI transaction, and then send it to the scoreboards.

We need to do the reverse now, and cast the SPI base transaction into read/write transactions.

Vtool_Packing_and_unpacking_data_with_uvm_packer_3

The bitstream operation << or >> depends on MSB/LSB data driving. To understand how it works take a look at this example on EDA Playground. Note: The examples are simplified in order to explain the idea of the packing/unpacking. It is up to the user to organize the command, implement the functions, and more.