The integration team moves the DMA from address 0 to 0x4000_0000. The block's hand-written tests, with 'h08 for LEN, now write into some other block's registers; the regression turns red in confusing ways. The register-model tests change nothing at all, because they never knew LEN's address. One number changed, in one place: the map's base.
This lesson is about that one place. It shows how the DMA's map turns names into addresses, how the predictor turns addresses back into names, and what the map's width and endianness settings really do.
A map (uvm_reg_map) is the address book of one block as seen from one bus interface. It has a base address, a bus width in bytes (n_bytes), an endianness, and a flag saying whether addresses count bytes or bus words. Each register is placed in it at an offset with access rights (RW, RO or WO). A block with two bus ports, say APB for software and a debug port, has two maps, and a register can appear in both at different offsets.
The map works in both directions. Going out, rm.len.write() asks the map for LEN's address: base plus offset. Coming back, the predictor sees an APB transfer at some address and asks the map which register is that? with get_reg_by_offset(). Both directions come from the same table, so they can never disagree with each other, only with the hardware.
These are the last lines of dma_reg_block::build(), shown in full in the previous lesson:
// snippet: the end of dma_reg_block::build()
// One APB map: base 0, 4-byte bus, little endian.
map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);
map.add_reg(ctrl, 'h00, "RW");
map.add_reg(status, 'h04, "RW");
map.add_reg(len, 'h08, "RW");
map.add_reg(key, 'h0C, "RW");
map.add_reg(count, 'h10, "RO");base is added to every offset in the map. For the DMA it is 0; the subsystem model in the next lesson places a second DMA at 0x1000.
n_bytes is the bus width: 4 for the DMA's 32-bit APB. A register wider than this takes several bus accesses.
endian orders those accesses (below). The first map created in a block also becomes its default_map.
byte_addressing (default 1) means one address per byte, so consecutive 32-bit registers are 4 apart, as in the DMA. With 0, addresses count bus words, and COUNT would sit at offset 4, not 0x10.
The offset is relative to the map's base. rights record how this map may reach the register: COUNT is added RO, while STATUS stays RW because its W1C bits must be written to clear them. Rights are coarse; the field policies decide how each write and read is predicted. unmapped and frontdoor are for registers with no plain address, such as indirect registers, which Register Access covers.
Register | Offset | Address (base 0) | Address (base 0x4000_0000) |
|---|---|---|---|
ctrl | 0x00 | 0x00 | 0x4000_0000 |
status | 0x04 | 0x04 | 0x4000_0004 |
len | 0x08 | 0x08 | 0x4000_0008 |
key | 0x0C | 0x0C | 0x4000_000C |
count | 0x10 | 0x10 | 0x4000_0010 |
rg.get_address() returns a register's bus address through its default map (including any parent map's offsets), and map.get_reg_by_offset(addr) goes the other way. The reverse lookup works only after lock_model(), because locking is when the library builds its address table, and it returns null for an address no register covers, such as 0x14 on the DMA.
Map endianness orders the bus beats when a register is wider than the bus. In UVM 1.2 that is all it does: it does not swap bytes inside one bus word. Every DMA register is 32 bits on a 4-byte bus, so each one is a single beat and the endianness setting changes nothing. Put a 32-bit register on a 2-byte bus and it becomes two beats: UVM_LITTLE_ENDIAN puts the low half at the lower address, UVM_BIG_ENDIAN puts the high half there. Where each byte travels within a beat is the adapter's and the driver's business (byte enables, strobes, lane wiring), not the map's.
32'hA1B2C3D4 at offset 0x20
n_bytes = 4 (the DMA): one beat, endianness has no effect
0x20 -> A1B2C3D4
n_bytes = 2, UVM_LITTLE_ENDIAN n_bytes = 2, UVM_BIG_ENDIAN
0x20 -> C3D4 0x20 -> A1B2
0x22 -> A1B2 0x22 -> C3D4Every APB transfer in dma_ral_job_test goes through both directions of the map. At +UVM_VERBOSITY=UVM_HIGH the log shows the outgoing access, the monitor's view of the bus, and the predictor's reverse lookup landing on the right register (paths shortened):
UVM_INFO @ 115000: reporter [RegModel] Wrote register via map regmodel.map: regmodel.len=0x6
UVM_INFO dma_tb.sv(151) @ 115000: uvm_test_top.env.apb.mon [APB_MON] WR addr=08 data=00000006
UVM_INFO reg/uvm_reg_predictor.svh(248) @ 115000: uvm_test_top.env.predictor [REG_PREDICT] Observed WRITE transaction to register regmodel.len: value='h6 : updated value = 'h6The model wrote LEN; the monitor saw address 08; the predictor turned 08 back into regmodel.len. A scratch run that builds a second, standalone dma_reg_block and asks its map directly gives the same answer: solo map, 'h08 -> solo.len, len @ 8.
A team once spent two days on a DMA that moved the wrong number of words. Their generated model had LEN and KEY swapped, 0x0C and 0x08, because a spreadsheet row had been reordered. Every register test passed. A frontdoor write to "LEN" went to 0x0C, and a frontdoor read of "LEN" came back from 0x0C, so the model and the DUT agreed perfectly about the wrong register. Even the scoreboard, decoding KEY from the APB monitor by address, agreed. Only the data path, with the DUT's real LEN holding the low bits of a key, told the truth.
The lesson: frontdoor tests prove the map is consistent, not that it is right. Something must compare the model with the design by name, outside the bus. That is what the backdoor does in dma_ral_job_test: it reads KEY from the RTL signal key and compares it with the mirror. A swapped map fails that check at once. Generating the model, the RTL decoder and the documentation from one source removes the cause.
A quieter failure is an access to an address no register covers. The predictor looks it up, gets null, and drops the transfer with a message at UVM_FULL verbosity, so at normal verbosity a write to a misdecoded address leaves no trace in the model.
n_bytes = 4 and UVM_BIG_ENDIAN. Which half goes to address 0x40?The high 32 bits. The register needs two beats, at 0x40 and 0x44, and big endian puts the most significant part at the lower address. With little endian the low 32 bits would go to 0x40. In both cases the bytes inside each 32-bit beat are not reordered by the map.
byte_addressing = 0. Where does COUNT land, and what breaks?Offsets now count 4-byte words, so offset 0x10 means word 16, byte address 0x40, and the model's 'h10 no longer matches the RTL's decoder. Frontdoor accesses go to the wrong place. The offsets in a word-addressed map would have to be 0, 1, 2, 3 and 4.
get_reg_by_offset() fail when called before lock_model()?The address table it searches is built when the model is locked: until then the library has not resolved offsets, submap bases and multi-beat registers into addresses. Called early, it reports Cannot get register by offset: Block ... is not locked and returns null.
Next: one DMA is one block. Nested Blocks and Submaps puts two of them into one subsystem model.