Build Customized Memory Allocator using VMM MAM
Youhua Yangyouhua.yang@exar.com
EXAR INC.
Abstract
The memory allocator is an important verification component in DMA based design verification environment. DMA design usually uses buffer descriptor to describe a memory region. A buffer descriptor contains buffer start address and buffer length. The memory allocator is used to create buffer descriptor which satisfy DMA design requirement. VMM MAM provides the randomized memory allocation capability. This paper introduces a specific DMA design that requires special memory allocator and walkthrough the way to build customized memory allocator using VMM MAM.
1. Introduction
DMA (Direct Memory Access) is a design component which transfer data without CPU involved. In DMA design, the buffer descriptor is used to describe a memory region. DMA component reads a DMA command which contain sets of buffer descriptor, and then transfers the data inside the buffers to the destination specified by DMA command.
In DMA verification environment, there should be a CMD Gen (DMA command generator) which sends command to DUT (Device under Test). The CMD Gen requests the free memory space from Memory Allocator. The Memory Allocator should maintain all used memory spaces and free memory spaces, provides memory allocation and memory release interface to other verification components.

Figure 1 Memory Allocator
Figure 1 shows a simple memory allocator, the start_offset and end offset defines the whole range of memory space. The used memory region and free memory space are recorded. VMM MAM (Memory Allocation Manager) provides the simply way to build Memory Allocator.
2. Memory Allocation Manager (MAM)
Memory allocation manager utility class similar to C’s malloc() and free(). It provides classes to maintain the memory regions, to describe the memory region, to control the memory allocation result and to configure MAM. For more details about these classes, please refer to RAL User Guide.
2.1 Class Structure of VMM MAM

Figure 2 VMM MAM Class
2.2 vmm_mam_cfg
This class is used to specify the managed memory configuration. There are three major configuration variables.
n_bytes: Number of bytes in each addressable location, this is also the smallest memory size that vmm_mam can be allocated.
start_offset: The offset of first addressable location under management.
end_offset: The offset of last addressable location under management.
2.3 vmm_mam_region
This class is used to describe a memory region. A memory region uses start offset and end offset to define the range it covered.
function bit [63:0] get_start_offset() – Return the start offset of a memory region.
function bit [63:0] get_end_offset() – Return the end offset of a memory region.
Note: the physical byte address of memory region start or end address is offset multiply the n_bytes (from the vmm_ mam_cfg).
2.4 vmm_mam_allocator
This class is used to determine the starting offset of memory region when user requests a new unused memory region. We extend this class to build customized allocator by adding user constraints.
rand bit [63:0] start_offset: Starting offset of the randomly allocated region.
bit [63:0] min_offset, bit [63:0] max_offset: min_offset, max_ offset specify the memory range under management.
2.5 vmm_man
This class is a memory allocation manger. User creates the vmm_mam instance by passing an instance of vmm_mam_ cfg. Class vmm_mam provides functions to reserve/request/ release memory region.
function vmm_mam_region reserve_region(bit [63:0] start_ offset, int unsigned n_bytes) – Reserve a memory region of specified number of bytes starting at the specified offset in the memory.
If the memory region is overlapped with an already-allocated region or it is outside the address range of vmm_mam_cfg specified, a vmm_error message will be reported and null object is returned. If memory reservation is successful, a vmm_man_region instance will be created by start_offset, n_ bytes and is returned.
function vmm_mam_region request_region(int unsigned n_bytes, vmm_mam_allocator alloc = null) – Request and reserve a memory region by specify the memory region byte length at a random location in the memory.
If optional vmm_mam_alloctor alloc is not provided, default_ alloc in vmm_mam will be used. The start_offset of returned vmm_mam_region is determined by randomizing result of alloc or default_alloc. In some cases, the request can’t be satisfied because of largest consecutive locations size is smaller than n_bytes requested. In these cases, a vmm_error message will be reported and null object is returned.
function void release_region (vmm_mam_region region) – Release the previously specified allocated memory region. If region is not seen by vmm_mam, a vmm error message will be reported.
3. PCIE DMA Design Requirement
PCIE DMA is a component inside our look aside storage/ security acceleration chip. It fetches the DMA commands from host memory via PCIE interface then gathers the source data described by buffer descriptors inside the DMA commands into the appreciate acceleration engine, finally it sends out transformation result data to destination buffers specified by DMA commands.
The DMA commands is linked by a command pointer ring, each command pointer point to the location where DMA command is. Each DMA command contains the transformation method, the source data buffer descriptors and destination buffer descriptors. Figure 3 shows these data structures in the host memory.
Command Pointer Ring start address should be 8 bytes alignment and should inside lower 4G memory space.
Command Structure start address should be 128 bytes alignment.
Buffer start address can be any byte address. All buffers should not overlap in Double Word (8 bytes) level.
For example: Buffer 0 (byte address 0 to byte address 9),Buffer 1 (byte address 10 to byte address 15) are overlap at Double Word address 1 (byte address 8 to byte address 15).

Figure 3 Host Memory Data Structures
4. Building Customized Memory Allocator
4.1 Memory Allocator Function Definition
Based on the DMA design requirement, our memory allocator (mem_alloc) class provides memory allocation and release functions to other components.
class mem_alloc;
vmm_mam_cfg mam_cfg;
vmm_mam mam;
function new(vmm_mam_cfg mam_cfg);
……
endfunction
function bit allocate_mem(bit [63:0] mask, int unsigned len, ref logic [63:0] addr, input logic [63:0] addr_min = 1, input logic [63:0] addr_max = 64′hffff_ffff_ffff_fffe);
…..
endfunction
function bit release_mem(logic [63:0] addr);
…..
endfunction
endclass
function bit allocate_mem(bit [63:0] mask, int unsigned len, ref logic [63:0] addr, input logic [63:0] addr_min = 1, input logic [63:0] addr_max = 64′hffff_ffff_ffff_fffe) – This function tries to allocate a memory region with following constraints:
1. Byte length is len.
2. if addr == 0 then( start address & (~mask)) == 0; else start address == addr;
3. Memory region lies in the memory range from addr_min to addr_max.
The start address is assigned to ref logic [63:0] addr. This function returns zero if allocation is failed, return 1 if allocation is successful.
function bit release_mem(logic [63:0] addr) – Release a pre- allocated memory region whose start address is addr.
4.2 Using VMM MAM in Memory Allocator
Let’s define the constructor of our mem_alloc by passing a vmm_mam_cfg instance, and create a vmm_mam instance.
function new(vmm_mam_cfg mam_cfg);
bit status;
// let’s create a new vmm_mam_cfg if user passes a null mam_cfg object.
if(mam_cfg == null) begin
this.mam_cfg = new();
status = this.mam_cfg.randomize() with {
start_offset == 1;
end_offset == 64′hffff_ffff_ffff_ffff;
};
if(!status) `vmm_fatal(log, “mam_cfg.randomize() is failed!”);
end else this.mam_cfg = mam_cfg;
// always cfg n_bytes to 1, thus the memory region start byte address is equal to start_offset.
this.mam_cfg.n_bytes = 1;
mam = new(”mam”, this.mam_cfg);
endfunction
Based on requirement of function allocate_mem, we need to add some additional constraints to vmm_mam_allocator. This can be implemented by define a new class which is extended from vmm_mam_alloctor.
class exar_mem_allocator extends vmm_mam_allocator;
// define the requirement of start_offset, mask which is usedto set address alignment requirement.
// e.g. mask = 64’hffff_ffff_ffff_fff8 : 8 bytes alignment.
bit [63:0] mask;
// define the range of allocated memory space.
bit [63:0] addr_min;
bit [63:0] addr_max;
// additional constraint for start_offset.
constraint mem_alloc_cons
{ (start_offset & (~mask)) == 0; }
// additional configuration for memory range under management.
function void pre_randomize();
min_offset = addr_min;
max_offset = addr_max;
endfunction
endclass
Then, we add exar_mem_alloctor instance to our memory allocator. And also all allocated memory regions should be stored to implement release_mem().
exar_mem_alloctor alloc;
// the allocated memory regions index by start address.
vmm_mam_region in_use[*];
function new()
…
this.alloc = new();
endfunction
4.3 Implement allocate_mem()
Now we have added most of constraints to exar_mam_ allocotor except buffer should not overlap in Double Word level. We will implement it inside allocate_mem(), additional code is added around vmm_mam::reserve_region() and vmm_mam::request_region().
function bit allocate_mem(bit [63:0] mask, int unsigned len, ref logic [63:0] addr, input logic [63:0] addr_min = 1, input logic [63:0] addr_max = 64′hffff_ffff_ffff_fffe);
vmm_mam_region region = null;
if(addr != 0) begin
// adjust the start_offset and n_bytes passed to reserve_ region(), let’s the actual reserved region // start offset is 8 byte alignment.
logic [63:0] temp_addr = addr;
len += temp_addr[2:0];
temp_addr[2:0] = 0;
region = mam.reserve_region(temp_addr, len);
end else begin // Use our exar_mem_allocator
alloc.mask = {mask[63:3], 3′b0}; // Force actual allocated region is 8 bytes alignment.
alloc.addr_min = addr_min > mam_cfg.start_offset ? addr_min : mam_cfg.start_offset;
alloc.addr_max = addr_max < mam_cfg.end_offset ? addr_max : mam_cfg.end_offset;
region = mam.request_region(len+mask[2:0], alloc);
// Select random addr inside the allocated memory region.
if(region != null) addr = region.get_start_offset() + ($urandom_range(7, 0) & mask[2:0]);
end
if(region != null) in_use[addr] = region; // store the allocated memory region.
allocate_mem = region != null; // set the function return value. endfunction
4.4 Implement release_mem()
Because all pre-allocated memory region are stored in associate array in_use, we simply read in_use array and pass to vmm_mam::release_region to release this region inside vmm_mam, and also remove it from our in_use array.
function bit release_mem(logic [63:0] addr);
vmm_mam_region region;
if(in_use.exists(addr)) begin
region = in_use[addr];
mam.release_region(region);
in_use.delete(addr);
end else begin
`vmm_fatal(log, $psprintf(”release_mem(),addr = 64′h%16h
memory region doesn’t exist”,addr));
release_mem = 1;
endfunction
5. Conclusion
We have walked through the way to build customized memory allocator. The major point is setup your requirement in the subclass of vmm_mam_allocator or around the vmm_ mam::reserve_region() and vmm_mam::request_region().
6. Reference
[1] VMM Register Abstraction User Guide from Synopsys
[2] Using the VMM Memory Allocation Manager for DMA Verification by Syed Sheeraj Ghouse (http://www. vmmcentral.org/pdfs/sv_mam_final.pdf)



