Skip to content

AXI4 VIP development and integration - #688

Open
tchilikov-semify wants to merge 25 commits into
lowRISC:mainfrom
tchilikov-semify:axi_driver_pr_new
Open

AXI4 VIP development and integration#688
tchilikov-semify wants to merge 25 commits into
lowRISC:mainfrom
tchilikov-semify:axi_driver_pr_new

Conversation

@tchilikov-semify

@tchilikov-semify tchilikov-semify commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

This PR does a few things:

  1. Pulls @rswarbrick's AXI driver from his Caliptra repository as a local copy and expands on its functionality with multi-beat support
  2. Merges it with Csaba's AXI monitor from Axi4 passive vip #391, creating one unified agent for Active (manager) and Passive (monitor) modes
  3. Applies various bug and compilation fixes to the agent
  4. Integrates the agent in the top TB as a monitor on each port of the crossbar
  5. creates an environment level scoreboard

This was tested with the top level CVA6-based test cases.

Will close issue #168

@rswarbrick rswarbrick left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for doing this (and getting my completely untested code working...)

Do you think you could cherry-pick the various fixes in "axi agent compilation fixes" to the commits that made the mistakes? Since we don't use squash merge in Mocha, this is a way to avoid putting Rupert's silly mistakes into the history :-)

@tchilikov-semify

Copy link
Copy Markdown
Collaborator Author

Thank you very much for doing this (and getting my completely untested code working...)

Do you think you could cherry-pick the various fixes in "axi agent compilation fixes" to the commits that made the mistakes? Since we don't use squash merge in Mocha, this is a way to avoid putting Rupert's silly mistakes into the history :-)

Hey Rupert,

Sure, I can merge the patches into your commits. This would also encompass the handshaking bugfix commit, for a total of two commits :)

rswarbrick and others added 8 commits July 22, 2026 14:40
This is the first step towards a simple AXI agent. It will only have
to handle the subset of AXI that is in use in the blocks in
question (which work by translating to TLUL, so they aren't doing
anything particularly exciting).

In this commit, we're just defining interfaces for the five
channels (which are AW, W, B, AR and R).

Co-authored-by: tchilikov-semify <christian.tchilikov@semify-eda.com>
The axi_read_request_item and axi_read_data_item classes are intended
to be randomised (when a sequence wishes to send either read requests
or data responses). The axi_read_item class can be used by a monitor
that sees a read request and then one or more responses.

Co-authored-by: tchilikov-semify <christian.tchilikov@semify-eda.com>
When the agent is representing an AXI Manager, this will be useful for
the B and R channels.

Co-authored-by: tchilikov-semify <christian.tchilikov@semify-eda.com>
These drive write requests (AW) and write data (W) and also drive the
write response channel (B).

Co-authored-by: tchilikov-semify <christian.tchilikov@semify-eda.com>
Co-authored-by: tchilikov-semify <christian.tchilikov@semify-eda.com>
Co-authored-by: tchilikov-semify <christian.tchilikov@semify-eda.com>
For an example use-case, suppose you are reading with AR / R
transfers. You send an AR transfer with some ARID that will have a
burst with k beats. Now you want consume read data with that many (k)
R transfers using the some ID.

To do so:

  - Run k axi_mgr_read_data_seq sequences, calling on_response() after
    each finishes.

  - (In parallel) call wait_for_response() k times.

The trick is that these sequences might respond with different IDs,
but that doesn't matter: the sequences are just actings as tokens to
allow *something* to come back.
Comment thread hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_burst_vseq.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_agent_cfg.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_agent.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_agent.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_agent.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mon_item.svh Outdated
@tchilikov-semify

Copy link
Copy Markdown
Collaborator Author

Hey @rswarbrick,
Thanks a lot for the in-depth review! I'll go through all of the comments and address them.

In the meanwhile, I've folded the old compilation bugfixes commit into the respective commits that you made, so the commit history should be clear of bugs now.

@tchilikov-semify
tchilikov-semify force-pushed the axi_driver_pr_new branch 9 times, most recently from a5c0dab to 6550b3a Compare July 23, 2026 09:27
@tchilikov-semify
tchilikov-semify force-pushed the axi_driver_pr_new branch 3 times, most recently from c90d65e to b5b685e Compare August 17, 2026 15:12

@rswarbrick rswarbrick left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are some notes about the "fix axi handshaking" and "multi-beat transaction support" commits.

These both look really good: I like the code, and it's just some nitty notes about comments.

Comment thread hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_burst_vseq.svh Outdated
Comment thread hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_burst_vseq.svh Outdated
Comment thread hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_burst_vseq.svh Outdated
Comment thread hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_burst_vseq.svh Outdated
Comment thread hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_burst_vseq.svh Outdated

@rswarbrick rswarbrick left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two more minor notes about "add multi-beat transaction support"

Comment thread hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_listed_data_seq.svh Outdated
Comment thread hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_listed_data_seq.svh Outdated

@rswarbrick rswarbrick left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A minor note about error messages in "add clk_rst_if to axi interfaces". (I really like this change though!)

Comment thread hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_read_request_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_write_data_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh Outdated

@rswarbrick rswarbrick left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some notes about the (excellent!) change that adds axi_widths_pkg. They're all about alignment: sorry - very boring.

Comment thread hw/ip/dv/axi_agent/seq_lib/axi_mgr_txn_request_seq.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_fixed_read_req_item.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_fixed_write_req_item.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_read_data_if.sv Outdated
Comment thread hw/ip/dv/axi_agent/axi_read_data_if.sv Outdated
Comment thread hw/ip/dv/axi_agent/axi_write_request_if.sv Outdated
Comment thread hw/ip/dv/axi_agent/axi_write_request_if.sv Outdated
Comment thread hw/ip/dv/axi_agent/axi_write_response_if.sv Outdated
Comment thread hw/ip/dv/axi_agent/axi_write_response_if.sv Outdated
Comment thread hw/ip/dv/axi_agent/axi_write_response_if.sv Outdated
Comment thread hw/ip/dv/axi_agent/axi_monitor.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mon_item.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mon_item.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_mon_read_item.svh
Comment thread hw/ip/dv/axi_agent/axi_mon_read_item.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_monitor.svh
Comment thread hw/ip/dv/axi_agent/axi_monitor.svh
Comment thread hw/ip/dv/axi_agent/axi_monitor.svh
Comment thread hw/ip/dv/axi_agent/axi_monitor.svh Outdated
Comment thread hw/ip/dv/axi_agent/axi_monitor.svh Outdated

@rswarbrick rswarbrick left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One specific question: Is there a good reason to use dv_base_agent for the IP? It's designed to a very specific OpenTitan shape, as chosen by the DV lead at the time.

It's also actively against my design for this agent. If someone more important than me mandates that we use the class, I guess we have to. If not, I'd prefer we didn't.

parameter int UserWidth = 1,
parameter uvm_pkg::uvm_active_passive_enum IsActive = uvm_pkg::UVM_ACTIVE,
parameter string InstId = "axi_mgr", // names the published axi_agent_cfg
parameter string CfgScope = "*" // config_db publish scope glob

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this design following a pattern for which you have a reference? I'm slightly surprised that you don't set things in the config db from the testbench, rather than the interface that gets instantiated.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! The intent of this file is perhaps quite unclear from the perspective of this PR... Its purpose is to take the shape of the AXI peripherals used in mocha. In our agent, the AXI bus is represented by 5 interfaces, which map to the channels. In mocha, the AXI devices have ports which are represented by 2 interfaces, basically inputs and outputs (req and resp). This interface bridges the two.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. I wonder whether it would make sense to split this file out of the axi_vip directory. Although it's notionally parametised on the type of e.g. req_t, it isn't really!

Lines like this aren't really parametric in the type!

    assign axi_req.aw.id     = $bits(axi_req.aw.id)'(aw_if.awid);

(This is an example of the standard fact from type theory that there is only one way to define a function A -> A for all types, A)

Comment thread hw/ip/dv/axi_agent/axi_vip/axi_vip_if.sv Outdated
Comment thread hw/ip/dv/axi_agent/axi_vip/axi_vip_if.sv
// clear, the payload is randomised instead: still meaningless, but defined.
//
// Defaults set, so an idle channel drives X.
bit drive_x_when_idle = 1'b1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm repeating myself here, but I think this should be set by calling a setter on the agent, which calls a setter on each of the drivers.

Note that you wrote earlier that the agent config just contained a list of virtual interfaces. I wasn't convinced. I'm still not :-)

@tchilikov-semify tchilikov-semify Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes perhaps an agent wide config permeated into the configs... You were right in this regard. 😉

For my understanding, are you proposing that the agent reads this config and then calls the setters of the drivers, rather than passing the config directly to the drivers? This value is a genuine agent-wide knob, so I think it should live in the config. Is this what you had in mind?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I was rather confused when I was writing that suggestion... I think there's a nice way to structure things though! (details below). Does it sound reasonable to you?

@tchilikov-semify

tchilikov-semify commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

One specific question: Is there a good reason to use dv_base_agent for the IP? It's designed to a very specific OpenTitan shape, as chosen by the DV lead at the time.

It's also actively against my design for this agent. If someone more important than me mandates that we use the class, I guess we have to. If not, I'd prefer we didn't.

Honestly - its not strictly needed. This was a suggestion from an internal review that Martin did with Claude, which he told me to implement if it seems fitting. I thought it was fitting, since I saw that other mocha TBs use the dv base classes, but honestly I don't have a strong opinion on this (and honestly, I think the less dependencies an independent agent has, the better).

Shall I remove it?

Merge per-channel reset monitors into a unified reset monitor
by introducing a shared clock and reset interface to the agent.
Add AXI transaction monitor which snoops the 5 channels and rebuilds
AXI transactions, then broadcasts them. Monitor is built
unconditionally.
@tchilikov-semify

Copy link
Copy Markdown
Collaborator Author

Hey @rswarbrick - thanks for the in depth review again! It seems this PR is starting to come together 😄 I think I've addressed most of your comments. There's still 1 or 2 open questions, but I've re-triggered another review. Thanks!

@rswarbrick

rswarbrick commented Aug 30, 2026

Copy link
Copy Markdown

@tchilikov-semify, thanks for the prompt! I'll go through the commits in detail again in a minute, but I've been thinking a bit about how configuration should probably work, and I think that (at last!) I've finally worked out a sensible structure.

Thinking about configuration for the agent, I think there are two different sorts of configuration:

  • Structure: This is things like a bus width. The interface doesn't need things like this to be parameters exactly (this is the whole point of the "max footprint stuff"), but they are static and completely governed by the dut RTL. As such, I think the right thing is for this to be customised by the testbench as it instantiates the interface.
  • Behaviour: For example, I might want to configure how quickly the agent responds to requests, or maybe how frequently it sends pings, or maybe the behaviour you're configuring with drive_x_when_idle. This sort of thing should probably be configured by the test or environment, "downstream" of tb.sv.

Focusing on the experience of an environment that wants to use the agent, I think the right thing is to create something like what you've currently got in the config file: essentially, it's a list of virtual interfaces, together with the behaviour configuration parameters.

For the internals of the agent, I don't think that the various components (monitors and drivers) should see that config object. The danger is that everything ends up "very global" (a recurring problem in OpenTitan) and the thing that's called an "agent config" ends up being a way to store state and communicate between the different components. In the limit, you end up with "everything is a global variable". Yuck! Instead of doing that, I think the agent should responsible for copying the configuration information down to the components it creates (in the agent's build_phase).

That should separate concerns nicely:

  • A driver can see the way it has been configured (with the relevant config values stored as class variables inside the driver).
  • It can also see information about its internal state.
  • But it can't see stuff it doesn't need. For example, a monitor shouldn't need to care about drive_x_when_idle.
  • What's more, a test or environment can't really see components' internal state: it can just see the agent config object.
  • If there is internal state that we want to expose, we can pull it explicitly through the agent (not the agent config! Sequences shouldn't see this sort of thing!) with a my_agent.get_monitor_xyz_state() function.

This is not how the historical dv_base_agent class worked. But I think we can do much better than that! And I also think that we've got most of the right structure in this PR now.

@rswarbrick rswarbrick left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just read carefully through the read burst virtual sequence and have left lots of suggestions (sorry!)

They will apply to the write side of things as well.

//
// To use it:
//
// - Create it, then call set_sequencers() and set_read_response_router().

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be my fault (sorry), but we've got a double "it" here. How about "To use the sequence:" on the previous line?

// To use it:
//
// - Create it, then call set_sequencers() and set_read_response_router().
// - Choose the request by randomising this sequence. m_ar_req is rand, so a plain randomize()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation is a bit wonky here: I think the lines after the - line are indented by an extra space each time.

// - Choose the request by randomising this sequence. m_ar_req is rand, so a plain randomize()
// picks a legal burst. To direct it, constrain the fields that matter in the same call:
//
// if (!vseq.randomize() with { m_ar_req.m_addr == 'h1000;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd suggest a more obviously arbitrary address here. How about 'h1234 ?

// On completion, rsp is an axi_fixed_read_rsp_item where:
// - m_ar_status is the status of the AR transfer. Its m_sending_complete is 0 if a reset
// stopped the request being sent.
// - m_read_data holds the beats that arrived, in beat order. The same beats are also left in

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd avoid "left" here: saying that something is "left in a queue" makes it sound a bit like it's there and nowhere else. Thinking about it, I don't think I'd talk about m_read_beats at all: isn't that just an implementation detail?

// the public m_read_beats queue.
//
// If a reset happens part-way through, the sequence still completes and still populates rsp, but
// m_read_data is short: it holds only the beats that had already arrived, possibly none at all. A

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar nit: This would be more standard as "... it only holds ..."

Comment on lines +128 to +133
for (int unsigned i = 0; i < n_beats; i++) begin
automatic int unsigned beat_num = i;
fork
accept_one_beat(beat_num);
join_none
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a big deal, but I don't think there's any need for these accept_one_beat tasks to run in parallel with each other. Couldn't this be:

      fork begin
        for (int unsigned i = 0; i < n_beats; i++) accept_one_beat(i);
      end join_none

?

if (!$cast(r_seq, m_r_accept.clone())) begin
`uvm_fatal(get_full_name(), "Clone of m_r_accept is not an axi_mgr_read_data_seq.")
end
// The clone carries the template's name, so rename it to say which beat it is.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't quite correct: it might be accepting a beat that has no relation to this sequence's request! I suppose a counter might be slightly useful to someone debugging in the future, but I think the comment needs changing.

axi_read_data_item read_data_item;

m_read_response_router.wait_for_response(m_ar_req.m_id, base_item);
if (base_item == null) break; // reset

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expand this to something like // We have seen a reset, to make sure it doesn't look like an instruction to reset.

if (read_request_sequencer == null) `uvm_fatal(get_full_name(), "No read_request_sequencer.")
if (read_data_sequencer == null) `uvm_fatal(get_full_name(), "No read_data_sequencer.")

m_read_request_sequencer = read_request_sequencer;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Extra space before the "=" signs on these two lines.

// m_use_fixed_XYZ=1 and set m_fixed_XYZ to the required value. Do this before starting the
// If non-null, this exact item is sent verbatim; no randomisation, and the
// m_use_fixed_* pins below are ignored.
axi_txn_request_item m_req;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this, I think it might make more sense to define an extra sequence for the fixed example. It's extremely simple! I just did a bit of typing, and I think it ends up like this, and avoids a load of do-nothing variables.

// Copyright lowRISC contributors
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

// A fixed sequence that sends a single fixed item.
//
// This item is neither created nor randomised: a virtual sequence that wants to use this sequence
// should provide the item before starting the sequence.
//
// When the sequence completes, the rsp field will contain a status item that shows whether the
// sequence ran to completion (rather than being interrupted by a reset).

class axi_mgr_fixed_txn_request_seq extends uvm_sequence #(axi_txn_request_item, axi_status_item);
  `uvm_object_utils(axi_mgr_fixed_txn_request_seq)

  // This item is sent verbatim; no randomisation. Provide the item before starting the sequence.
  axi_txn_request_item m_req;

  extern function new(string name="");
  extern task pre_start();
  extern task body();
endclass

function axi_mgr_fixed_txn_request_seq::new(string name="");
  super.new(name);
endfunction

task axi_mgr_fixed_txn_request_seq::pre_start();
  super.pre_start();
  if (m_req == null) `uvm_fatal(get_full_name(), "Cannot run fixed sequence with no item.")
endtask

task axi_mgr_fixed_txn_request_seq::body();
  axi_txn_request_item item;
  uvm_sequence_item base_status_item;

  if (m_req != null) begin
    // Send the caller-supplied item verbatim: it is already built, so no randomisation.
    item = m_req;
    start_item(item);
    finish_item(item);
  end

  // Get a response, which will always be sent by the driver (and is available already: there's no
  // pipelining and finish_item just completed).
  get_base_response(base_status_item);
  if (!$cast(rsp, base_status_item)) begin
    `uvm_fatal(get_full_name(), "Status response is not an axi_status_item")
  end
endtask

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we really don't want to repeat ourselves, we should probably define a base version of this sequence as a virtual class and give it a pure virtual "give me an item to run" function.

I'm not really convinced that's worth the hassle though: I think we repeat about 5 lines.

@rswarbrick

Copy link
Copy Markdown

One specific question: Is there a good reason to use dv_base_agent for the IP? It's designed to a very specific OpenTitan shape, as chosen by the DV lead at the time.
It's also actively against my design for this agent. If someone more important than me mandates that we use the class, I guess we have to. If not, I'd prefer we didn't.

Honestly - its not strictly needed. This was a suggestion from an internal review that Martin did with Claude, which he told me to implement if it seems fitting. I thought it was fitting, since I saw that other mocha TBs use the dv base classes, but honestly I don't have a strong opinion on this (and honestly, I think the less dependencies an independent agent has, the better).

Shall I remove it?

Yeah, if you're happy to :-)

I think that Claude recommends writing code that matches other code nearby. A very good general principle! But we're trying to do better...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AXI crossbar DV - Top-level AXI monitor to feed the scoreboard

2 participants