The DMA's TLM model must refuse non-secure accesses to its KEY register, the way the RTL does when the AXI PROT[1] bit is set. But the generic payload has a command, an address, data and byte enables. It has no AXI ID, no PROT, no QoS. Someone suggests encoding PROT in the top address bit. Someone else suggests a custom payload class. Both break interoperability with every other model on the platform. Where does protocol sideband go, and how does a target say no in a way every initiator understands?
The generic payload (uvm_tlm_generic_payload, typedef uvm_tlm_gp) is TLM 2.0's single memory-mapped transaction: command, address, data, lengths, byte enables, streaming width and response status. Every compliant model reads and writes those fields the same way, which is why a UVM initiator can drive a SystemC target it has never seen. For anything else, the payload carries extensions: small objects attached by type, which models that know about them read and models that do not simply ignore.
Think of a standard shipping envelope. Every post office knows where the address and the stamp go. A sender who needs fragile, keep upright attaches a sticker; offices that understand it handle the parcel accordingly, and offices that do not still deliver it.
Field | Accessors | Default after | Notes |
|---|---|---|---|
Command |
|
| IGNORE asks the target to do nothing (useful for probing extensions) |
Address |
| 0 | 64 bits, the first byte of the transfer |
Data |
| empty | A byte array; the initiator owns and sizes it |
Data length |
| 0 | Bytes to transfer; must match the data array |
Byte enables |
| length 0 | 0xFF enables a byte, 0x00 disables it; length 0 means all enabled |
Streaming width |
| 0 | Set it equal to the data length for a normal transfer |
Response status |
|
| Set by the target |
DMI hint |
| 0 | A flag only: UVM-SV has no DMI interface |
Use the accessors, not the m_* members. The members are public for historical reasons, but accessors are what SystemC models and adapters expect, and they keep your code portable. Never randomize() a whole generic payload: the data array, lengths and byte enables must agree, and random values will not.
Status | Value | A target returns it when |
|---|---|---|
| 1 | The transfer was executed |
| 0 | Never as a result: the initiator sets it before the call, so a target that forgot to set a status is detectable |
| -1 | Any other failure |
| -2 | The address or range is not supported |
| -3 | The command is not supported here (or refused, as for a non-secure access) |
| -4 | The data length or streaming width is not supported |
| -5 | Byte enables are not supported, or the pattern is not |
is_response_ok() is true only for OK, the one positive value, and is_response_error() is simply its negation. So INCOMPLETE counts as an error: a target that never set a status looks like a failure, not a success, which is exactly why the initiator sets it before the call.
Byte enables select which bytes of the data array take part. The array may be shorter than the data: it is applied repeatedly, so byte i uses be[i % byte_enable_length]. Streaming width models a FIFO-like target: with a data length of 16 and a streaming width of 4, the target sees four 4-byte beats to the same 4-byte address window. A plain memory sets streaming width equal to data length; a target that cannot stream returns BURST_ERROR for anything else.
An extension is a class derived from uvm_tlm_extension #(T) with itself as T. The parameter gives each extension type a unique static handle, T::ID(), which is the key the payload stores it under. Register it with ` uvm_object_utils ` and give it a do_copy`, because copying a payload clones its extensions.
// snippet: from tlm2_gp_test in tlm_tb.sv
class axi_attr_ext extends uvm_tlm_extension #(axi_attr_ext);
bit [3:0] id;
bit [2:0] prot; // prot[1] = non-secure
`uvm_object_utils(axi_attr_ext)
function new(string name = "axi_attr_ext");
super.new(name);
endfunction
function void do_copy(uvm_object rhs);
axi_attr_ext r;
super.do_copy(rhs);
$cast(r, rhs);
id = r.id; prot = r.prot;
endfunction
endclass
// initiator: build the payload, attach the extension, call
gp = uvm_tlm_generic_payload::type_id::create("gp");
gp.set_command(UVM_TLM_WRITE_COMMAND);
gp.set_address(64'h20);
gp.set_data(data);
gp.set_data_length(data.size());
gp.set_byte_enable(be); // empty array: all bytes enabled
gp.set_byte_enable_length(be.size());
gp.set_streaming_width(data.size());
gp.set_response_status(UVM_TLM_INCOMPLETE_RESPONSE);
attr.id = 4'h3; attr.prot = 3'b010;
void'(gp.set_extension(attr)); // returns the previous one of this type, or null
isock.b_transport(gp, delay);
// target (uvm_tlm_extension_base xb; declared with the task's other variables):
// look it up by type; null if the initiator attached none
xb = gp.get_extension(axi_attr_ext::ID());
if (xb != null && $cast(attr, xb) && attr.prot[1]) begin
gp.set_response_status(UVM_TLM_COMMAND_ERROR_RESPONSE); // non-secure access refused
return;
endA payload holds at most one extension of each type; set_extension with a second one replaces the first and returns it.
get_extension(T::ID()) returns a base-class handle, or null when no extension of that type is attached. Test for null before you $cast and use it, as the target above does: an initiator that knows nothing about your extension is legal.
clear_extension(T::ID()), clear_extensions() and get_num_extensions() complete the set.
The register layer's uvm_reg_tlm_adapter converts register operations to and from uvm_tlm_gp, so the same fields and statuses appear in RAL.
The lesson's TLM2 test, after the writes and read shown in Blocking transport. The second write had byte enables FF FF 00 00; the read-back shows only the first two bytes changed. Then a read past the end of the 256-byte model, a non-secure write, and a clone:
UVM_INFO tlm_tb.sv(502) @ 20000: uvm_test_top.ini [GP] UVM_TLM_WRITE_COMMAND [0x0000000000000010] = aa bb -- -- (status=OK) -> OK
UVM_INFO tlm_tb.sv(514) @ 30000: uvm_test_top.ini [GP] read back aa bb 33 44
UVM_INFO tlm_tb.sv(502) @ 30000: uvm_test_top.ini [GP] UVM_TLM_READ_COMMAND [0x00000000000000fe] = aa bb 33 44 (status=ADDRESS_ERROR) -> ADDRESS_ERROR
UVM_INFO tlm_tb.sv(502) @ 30000: uvm_test_top.ini [GP] UVM_TLM_WRITE_COMMAND [0x0000000000000020] = aa bb cc dd (status=COMMAND_ERROR) -> COMMAND_ERROR
UVM_INFO tlm_tb.sv(523) @ 30000: uvm_test_top.ini [GP] clone has 1 extension(s), prot=10, same object=0convert2string() prints disabled bytes as --, and get_response_string() turns the status into OK, ADDRESS_ERROR and so on.
The ADDRESS_ERROR read still shows data: the initiator's stale buffer. Status first, data second.
The clone carries its own copy of the extension (same object=0) with the same contents (prot is 3'b010, printed in binary as 10).
A team's scoreboard cloned every generic payload it received from a passthrough monitor, then looked up the AXI attributes extension to decide which checks applied. Every cloned payload came back with no extension, so every transfer was treated as secure, and the security checks never ran. The extension class had no ` uvm_object_utils ` macro. uvm_tlm_extension itself returns null from create(), so clone() on the unregistered extension failed, with a CRFLD warning (*the create method failed ... object cannot be cloned*) that nobody read, and the copied payload got a null in place of the extension. Registering the extension with the factory and adding a do_copy fixed it, and a check that get_extension is not null` when the sideband is mandatory would have caught it on day one.
UVM's SystemVerilog TLM 2.0 is a subset of the IEEE 1666 SystemC standard. Interviewers who know SystemC ask about the gaps:
SystemC TLM 2.0 has | UVM-SV has |
|---|---|
Direct memory interface: | Only the |
Debug transport: | Nothing |
| Nothing: initiators keep their own offset (temporal decoupling) |
Payload event queues ( | Nothing: use your own queues and threads |
Convenience sockets ( | The fixed socket classes, one target per initiator socket |
Memory management: | Garbage-collected objects; extensions stay until cleared |
None of this matters inside a pure UVM bench. It matters at the SystemC boundary, where a co-simulation layer such as UVM Connect translates between the two, and where a SystemC model that relies on DMI or debug transport needs a different path.
b_transport returns, is_response_error() is 1 but get_response_string() says INCOMPLETE. What happened, and why is this the behaviour you want?The target never set a status, so the value the initiator set before the call is still there. is_response_error() is the negation of is_response_ok(), so INCOMPLETE counts as an error. Had the initiator not set INCOMPLETE first, a stale OK from a reused payload could have made a target that did nothing look successful.
FF 00 FF 00. Which data bytes are written?The pattern repeats every 4 bytes, so bytes 0, 2, 4, 6, 8, 10, 12 and 14 are written and the odd bytes are left unchanged. A target that cannot handle byte enables should return UVM_TLM_BYTE_ENABLE_ERROR_RESPONSE rather than ignore them.
axi_attr_ext to the same payload. What does the payload hold afterwards?One extension of that type: the second set_extension replaced the first and returned the old one. If both pieces of information are needed, they belong in one extension, or in two different extension classes.
Next: with TLM 1 plumbing and TLM 2.0 sockets covered, the course turns to the stimulus side. UVM sequences is where the transactions flowing through all these connections are created.