Rakshit Singhal, Nvidia
Amit Sharma, Synopsys
The VMM MAM (Memory Allocation Manager) package offers the capability to dynamically manage memory shared across multiple clients. It helps to simulate HW memory usage patterns and guarantees memory block allocation based on constraints. This utility is centered on a set of four base classes with configurable memory address ranges and allocation schemes.
vmm_cfg: This class is used to specify the memory managed by an instance of a “vmm_mam” memory allocation manager class.
vmm_mam: This class is a memory allocation management utility similar to C’s malloc() and free(). A single instance of this class is used to manage a single, contiguous address space.
vmm_mam_allocator: An instance of this class is randomized to determine the starting offset of a randomly allocated memory region. This class can be extended to provide additional constraints on the starting offset.
vmm_mam_region: This class is used by the memory allocation manager to describe allocated memory regions. Instances of this class should not be created directly.
The mode of operation is as follows. An instance of “vmm_mam” class would use the “vmm_cfg” class handle to determine the minimum, maximum addresses and difference allocation schemes (mode and locality) of a memory space. A memory space can be reconfigured with new min, max and allocation scheme at run-time through the “vmm_cfg” class with the exception that number of bytes per memory location cannot be modified once a “vmm_mam” instance has been constructed. Additionally the currently allocated regions must fall within the new address space. On every call of request_region() function “vmm_mam” randomizes “vmm_mam_allocator” instance to determine the ‘start_offset’ of the randomly allocated memory region. This region is represented by an instance of “vmm_mam_region” class. Now if this “vmm_mam” is associated with a “vmm_ral_mem” instance, this “vmm_mam_region” can be used to perform read/write operation on the actual memory block through RAL. Thus we have a simple and easy-to-use framework to constrain different kinds of allocation requests based on specific requirements.
However, there are generally additional requirements in different verification environments. For example, a memory map can have a non-contiguous space allocated as device memory and various devices can simply request a portion of this region. Also for a PC memory model, it is required that each memory region has a clear ownership so that we can verify the memory accesses from various clients in system follow the design rules. Moreover some system would work in multiple address modes such as 32-bit or 64-bit mode and depending on which addressing mode is selected the complete memory management and allocation changes. The other requirements were to provide memory owners some control over the alignment of the base addresses of the requested regions. For example a USB driver in a system may ask for a memory region with its base address aligned to cacheline size or page size. It may also be required to make memory allocation and access management to be self checking for ease of verification. For example a cacheable region is accessed only by the designated owners or to build some backdoor polling on specific memory locations to trap memory accesses etc. Portability and extendibility of such environment was also a big concern.
To sum it up all we needed three primary extensions to VMM MAM; one, the ability to independently constrain each allocation/de-allocation request such that memory attributes could be set to the memory map; two, the flexibility to hook up any memory model with VMM MAM to achieve a centralized memory allocation management which could handle multiple memory implementations; three, a place holder to built self checking routines, polling routines and front-door/back-door memory accesses such that we could automate the whole memory verification.
To allow individual clients a control over the attributes set to the memory regions and to choose a specific type of memory we extended the following VMM MAM classes.
1. vmm_mam_allocator: – This has been extended to include features like addressing mode, memory type and address alignment by adding in new variables. Addressing mode specifies the address size i.e. if the unit/system requires a 32bit, 64bit etc addressing. Memory type attribute specifies the allocation of memory of the similar type within a specified range. Example: All the MMIO, write-cache, write-back etc addresses can be restricted to a specific region in memory and allocation can be done for each type from within their sub-regions. Address alignment specifies if the required allocation has to be a cache-aligned, word-aligned etc
typedef enum {R, RW, RSVD} mem_acc_t;
typedef enum {NONE, Above4GB, Below4GB, PreFetch, Cacheable} mem_attr_t;
typedef enum {PCIE, SATA, MAC, USB, USB3} mem_owner_t;
typedef enum {BYTE_ALIGN, WORD_ALIGN, CACHE_ALIGN, PAGE_ALIGN}address_align_t;
typedef enum {BIT32, BIT40, BIT64} address_mode_t;
class nv_mam_allocator extends vmm_mam_allocator;
address_align_t addr_align;
.
.
.
// system memory allocation
constraint sys_mem_alloc_cons {
this.start_offset > 64′hFFFF; // reserved memory area below FFFF
(addr_align == WORD_ALIGN) -> {this.start_offset[1:0] == 2′b0}; // Allocate only word (4B) aligned addresses
(addr_align == CACHE_ALIGN) -> {this.start_offset[7:0] == 8′b0}; // Allocate only cacheline (256B) aligned addresses
}
endclass : nv_mam_allocator
2. vmm_mam_region – this class has been extended to apply various user-defined attributes to memory regions and sub-regions. This has a customized vmm_mam::request_region() function which can take user variables as input arguments to constraint “vmm_mam_region” allocation. Addtionally, the psdisplay() of “vmm_mam” and “vmm_mam_region” is overridden to print out values of these variable for debugging purpose. Ownership of a region could also be queried from testbench using an inbuilt function get_owner.
class nv_mam_region extends vmm_mam_region;
mem_owner_t mem_owner;
function new(mem_addr_t start_offset, end_offset, offset_range, int unsigned n_bytes, nv_mam parent);
super.new(start_offset, end_offset, offset_range, n_bytes, parent);
this.n_bytes = n_bytes;
endfunction
function mem_owner_t get_owner();
return this.mem_owner;
endfunction
endclass : nv_mam_region
3. vmm_mam – This class has been extended such that the features added to the above 2 classes are made use of in the allocate/de-allocate methods. The handles of the allocated regions are stored and used in the release regions method. There is also a provision to release the regions particular to an owner or a group of owners.
class nv_mam extends vmm_mam;
nv_mam_allocator default_nv_alloc;
vmm_mam_region in_use[$];
nv_mam_region nv_in_use[$];
address_mode_t amode;
// request region
function nv_mam_region nv_request_region(int unsigned n_bytes, mem_owner_t mem_owner=UNKNOWN, address_align_t addr_align=WORD_ALIGN,mem_attr_t mem_type=NONE);
vmm_mam_region region;
nv_mam_allocator alloc;
alloc=new(addr_align, this.amode, mem_type);
region=super.request_region(n_bytes, alloc);
nv_request_region = new(region.get_start_offset(),region.get_end_offset(), region.get_len(),region.get_n_bytes(),this);
nv_request_region.mem_owner = mem_owner;
this.in_use.push_back(region);
this.nv_in_use.push_back(nv_request_region);
endfunction
// release OWNER specific regions
function void release_mem_region(mem_owner_t mem_owner=UNKNOWN);
vmm_mam_region region;
`vmm_note(this.log, $psprintf("Releasing all regions Owned by %s mem owner", mem_owner.name()));
foreach(this.nv_in_use[i]) begin
`vmm_verbose(this.log,$psprintf("REGION OWNER",this.nv_in_use[i].mem_owner.name()));
if(this.nv_in_use[i].mem_owner==mem_owner) begin
super.release_region(this.in_use[i]);
this.in_use.delete(i);
this.nv_in_use.delete(i);
break;
end
end
endfunction
.
.
endclass
Memory Model:
The above customized MAM can be wrapped around in a class in order to bind memory configuration class with specific HW/SW memory implementation, memory monitors, checker, fw_load functions and various read write tasks. In the code below, sys_mam_trace could be called anytime an actual read/write operation is performed on the hw/sw memory. This function provides notifications to indicate read/write to a certain address location. Two race free blocking functions wait_for_rd and wait_for_wr are implemented around these which could be used anywhere in the verification system to trap memory accesses. Based on these trap a cache protocol checker or any other checker could be implemented easily. Function load_fw can read an image file and preload any firmware code at the desired location in system memory. It also reserves the memory region so as to prevent any read/writes or allocation of the same address range to some other client in the system. Thus with the customizations, we made to the off-the-shelf application class, we were able to efficiently meet our verification requirements!
class sys_mam_c extends vmm_subenv;
rand vmm_mam_cfg cfg;
nv_mam_allocator sys_malloc;
nv_mam mam;
// setting up memory traps
event indicate_rd, indicate_wr;
bit rd_access[*];
bit wr_access[*];
// memory model – theh memory is implemented as an associative array of class objects
mem_loc sys_mem[*];
// constraint memory configuration as per the selected addressing mode
constraint sys_mam_valid_cons {
// 40 bit address space each address pointing to 4 bytes.
cfg.n_bytes == 1; //no of bytes each address points to.
cfg.start_offset == 0; //
(amode == BIT32) -> {cfg.end_offset == 64′hFFFFFFFF};
(amode == BIT64) -> {cfg.end_offset == 64′hFFFFFFFFFFFFFF};
cfg.mode == vmm_mam::GREEDY;
cfg.locality == vmm_mam::BROAD;
}
.
.
.
// debug/monitor/notification function
function void sys_mam_c :: sys_mam_trace(bit rw, bit [63:0] addr, bit[31:0] data, bit [3:0] byte_en, string requester);
// indicate mem read/write notifications
if (rw) begin
rd_access[addr] = 1;
-> indicate_rd;
end else begin
wr_access[addr] = 1;
-> indicate_wr;
end
// printing a range of addresses
if (mem_trace_en | wr_trace_en | rd_trace_en) begin
if (trace_sa < trace_ea) begin
if ((addr >= trace_sa) && (addr < trace_ea+1)) begin
// `vmm_debug(this.log, $psprintf("Memory Trace Enabled [trace_sa=%h:trace_ea=%h]", trace_sa, trace_ea));
// run time print of memory read/writes
if (!rw && (wr_trace_en | mem_trace_en)) begin
`vmm_note(this.log, $psprintf("MEM_WRITE: addr %h, data :%h byte_en :%b requester %s:", addr, data, byte_en, requester));
end else if (rw && (rd_trace_en | mem_trace_en)) begin
`vmm_note(this.log, $psprintf("MEM_READ: addr %h, data :%h, requester %s:", addr, data, requester));
end
end
end else begin
// memory read/writes trace
if (!rw && (wr_trace_en | mem_trace_en)) begin
`vmm_note(this.log, $psprintf("MEM_WRITE: addr %h, data :%h byte_en :%b requester %s:", addr, data, byte_en, requester));
end else if (rw && (rd_trace_en | mem_trace_en)) begin
` vmm_note(this.log, $psprintf("MEM_READ: addr %h, data :%h, requester %s:", addr, data, requester));
end
end
end
endfunction : sys_mam_trace
// trap memory read accesses
task sys_mam_c :: wait_for_rd(bit [63:0] mem_addr);
`vmm_note(this.log, $psprintf("Waiting For a Read Access to Mem Addr #%0h", mem_addr));
while (!this.rd_access.exists(mem_addr)) begin
@(indicate_rd);
end
this.rd_access.delete(mem_addr);
endtask: wait_for_rd
// trap memory write accesses
task sys_mam_c :: wait_for_wr(bit [63:0] mem_addr);
`vmm_note(this.log, $psprintf("Waiting For a Write Access to Mem Addr #%0h", mem_addr));
while (!this.wr_access.exists(mem_addr)) begin
@(indicate_wr);
end
this.wr_access.delete(mem_addr);
endtask: wait_for_wr
.
.
.
// load fw in memory with an img file
function void sys_mam_c::load_fw(string fw_file_name);
bit [31:0] mem [*];
bit [63:0] addr;
static bit addr_reserved[*];
mem_loc load_loc;
$readmemh(fw_file_name, mem);
foreach (mem[i]) begin
// i is word aligned address. Change it to byte aligned
addr = i<<2;
// reserved the address if not already reserved
if (!addr_reserved[addr]) begin
this.mam.nv_reserve_region(addr, 4);
addr_reserved[addr] = 1;
end
write_dw(addr, mem[i], 4′b1111, "load_fw");
`vmm_note(this.log, $psprintf("MEM i=%0h and ADDR=%0h Contains Data %0h", i, addr, mem[i]));
end
endfunction : load_fw
endclass
ShareThis...