How to handle complex bus sequences using the Register Layer

Hello,

We’re currently working on implementing various bus agents and also investigating the use of the Register Layer. We’ve encountered an issue that we’re not sure about how to handle properly.

Ideally we would like to use the Adapter construct to easily connect Register Layer blocks to some bus. However, the adapter seems to only be able to generate sequence items from register transactions (and the other way around).

The way we would like to implement our bus agents is by having very simple sequence items that only control the simplest part of a transaction (e.g. transferring a word on the AW stream of an AXI interface), in order to have fine control on all properties of the transaction. An actual bus transaction would then be handled by a layer of sequences.

So, the issue here is that our sequence items are not suitable for a full bus transaction, and thus the register layer adapter cannot use those items to start a transaction.

We tried:

  1. Generating a higher level sequence instead of a sequence item in the adapter (since a sequence extends upon a sequence item). This does not work; there are errors like “# UVM_FATAL @ 0: reporter@@default_parent_seq [SEQNOTITM] attempting to start a sequence using start_item() from sequence ‘default_parent_seq’. Use seq.start() instead.”
  2. Adding a second type of sequence_item that does in fact specify a full transaction; adding a sequencer to the agent for those items; and connect it to a “driver” that takes those sequence items and starts sequences on the other sequencer.

2 works, but it seems like overcomplicating things.

What would be the “normal” way to handle situations like this?

Thanks!

Although it is possible to avoid creating a fully specified AXI item, I recommend against it. But first, I’ll address your question.

uvm_reg_adapter only supports converting to/from sequence items that fully specify register transactions.

The alternative is to use a translation sequence. It’s poorly documented and not used as much, so it’s hard to find examples. The following article, which is focused on using a translation sequence to implement support for register bursts, covers the topic pretty well.

Back to my advice against doing this. Using this approach effectively makes uvm_reg_item your fully defined sequence item. AXI has order dependencies between the various channels, and that is why I recommend against this approach for AXI.

If you think only of the simplest case, where you issue a series of register reads and writes from a single sequence ( using a single value of AWID/ARID ), then it’s straight forward. However, once you account for multiple sequences running concurrently with different IDs, each arbitrating to get access to the proper channels in the proper order, going through a fabric where different slaves respond at different rates, it quickly get’s much more complicated.

You will need to embed all of this coordination logic into your register translation sequence, which is possible. The problem is, this logic is not unique to register sequences. You also need to enforce these ordering rules when directly issues transactions on the AXI without using the register model.

You might think, well I’ll just directly use uvm_reg_item as my transaction type when I want these ordering rules enforced. The problem is, uvm_reg_item does not expose all of the AXI features, so you won’t have full freedom to model any possible AXI transaction. For these reasons, it’s best to define a fully defined axi_item.

In reply to warnerrs:

Thanks for your response! I think we agree on the fact that using some high-level sequence item is not preferable.

I looked into the translation sequences and this looks like it is exactly what we need. We’re quite new to UVM, so it turned out we did not actually know the correct term for a translation sequence. So instead of an adapter we now use a translation sequence to translate a uvm_reg_item into starting some layered sequences that ultimately drive the bus.

Do you know if it is possible to also implement explicit prediction when no adapter is used?

In reply to frefra:

In reply to warnerrs:
Thanks for your response!

np.

I think we agree on the fact that using some high-level sequence item is not preferable.

That’s precisely opposite of what my last sentence says.

I looked into the translation sequences and this looks like it is exactly what we need. We’re quite new to UVM, so it turned out we did not actually know the correct term for a translation sequence. So instead of an adapter we now use a translation sequence to translate a uvm_reg_item into starting some layered sequences that ultimately drive the bus.
Do you know if it is possible to also implement explicit prediction when no adapter is used?

You can embed prediction calls into the translation sequence, which would be equivalent to enabling auto_predict with a register adapter. If you want full prediction, for non-uvm_reg based transactions, you’ll need a monitor to decode the AXI activity. Yet another reason to build a high level axi_item.

In reply to warnerrs:

That’s precisely opposite of what my last sentence says.

Sorry, I think I misread your post before.

While it’s a sidestep from the original question, I would like to ask about the sequence items, because I’m not sure I understand your rationale for using the high-level items.

The way we see it now (as new UVM users), is that having a high-level sequence item (where high-level is something that describes a full transaction, such as the uvm_reg_item) is not preferable, because it would mean that all logic to handle the bus transaction would need to be in the driver. I would say that the sequence item going into the driver should be as low-level as possible, for AXI it should maybe even be separate drivers (or even AXI-Stream agents?) per stream, where one item would be one transfer on a stream. All higher level logic would then be implemented by a layer of sequences, where a top-level sequence may be something like a burst_write. This burst_write would then be started by the RAL translation sequence which takes in uvm_reg_items. A uvm_reg_item may very well result in a burst if the register that is being accessed is wider than the bus width. The same burst_write sequence may also be started by some process that, for example, transfers a 4k image.

Because there are multiple layers of sequences that are being started, it is much easier to inject errors, for example, by re-implementing one of the intermediate sequences.

I would say that having high-level sequence items complicates things because the driver becomes much more complex. I also think it would be much harder to implement error injection and handling of out of order transactions.

Can you comment on this? We would gladly like to hear other viewpoints and considerations since we’re new to UVM as a company and still figuring out the best ways to go about things.

Thanks!

In reply to frefra:

(where high-level is something that describes a full transaction, such as the uvm_reg_item) is not preferable, because it would mean that all logic to handle the bus transaction would need to be in the driver.

Using a translation sequence with RAL doesn’t avoid having a high level item. It just means UVM supplies uvm_reg_item as that item for you.

Your high level driver could be a layered agent, which models the translation from high level axi_item to address and data items in a virtual translation sequence.

This also becomes the place you enforce AXI ordering rules. If you use a bunch of independent sequences, running in parallel, you won’t have a common place to enforce that.