Hi everyone!
I’m working on a VIP where I’m observing packet transactions on my monitor, and I’m wondering if you folks could help me think through the architecture a bit.
Here’s what I’ve got:
-
A shared memory_model sitting in the environment (which is a uvm_object containing support for read and write operations)
-
An agent with driver, monitor, and sequencer
-
Monitor Samples the request from below layers
-
Driver drives the response packet to the below layers
-
Each packet has a request (READ/WRITE) with some address/data info
The thing is, I need to update the memory based on what’s in these packets, but I’m not 100% sure where to actually call those memory operations.
- Where should I actually perform the memory read/write operations?
Should I be doing this in the:
-
Driver (before driving the response stimulus)?
-
Monitor (after I observe the transaction)?
-
Sequence (after fetching the packet from the monitor generating requests)?
-
Somewhere else entirely?
I want to make sure its reusable across multiple agents and doesn’t create weird coupling issues. What have you found works well?
2. What about error injection – where does that live?
If I want to inject errors (for response generation), is it better to:
3. Is it okay to call the memory model directly from the monitor?
I’m wondering if it’s fine to have the monitor just hold a reference to the environment’s memory_model and call it directly… or is that UVM-compatible?
These are more of a architectural choices.
As per UVM:
- monitor output is used to update the memory.
- Error injection is done in Driver.
- Regarding the flow; should it be in separate sequence or separate test, that you need think the bigger picture and decide.
- No. Monitor just monitors the transaction and broadcasts. Updates should be part of some other component in env or scoreboard.
Thank you for the feedback. Based on UVM methodology and our use case, here’s something I would like to say:
Since memory operations must be performed by agent components (not the environment or scoreboard directly), which are highly responsible for the same , I must use a driver or a monitor, or maybe a sequence, to call the memory model and perform the write/read operation.
I must write/read via agent components as this is a slave/sink agent which will process the request and generate a response too.
You’ve asked many good questions, and I hope I can contribute meaningfully to this thread.
-
First, it would be helpful to both of us if you could provide a block diagram of your testbench components and the DUT you are verifying.
-
A monitor is a passive component by nature and is instantiated inside a UVM agent. It should NOT perform any active processing (such as updating a shared memory model). It should only collect transactions from the interface signals (input/output ports of the DUT interface) according to the protocol/handshake applied to that interface (e.g., AXI, APB, AHB, I2C, UART, ready/valid, etc.), and broadcast them to all components subscribed to the monitor’s TLM analysis port.
-
Regarding your question: “Where should I actually perform the memory read/write operations?” It would help if you could provide a sketch of your testbench. Does your VIP work in slave mode? If so, in some cases the slave VIP might include a memory model. In any case, I would avoid updating the memory model within the sequence or driver, as this may cause race conditions.
-
Regarding your question: “What about error injection – where does that live?” You could add support for it in your reference model as another form of traffic, so that error scenarios are handled naturally without requiring new sequences or tests.