YAMM – Yet Another Memory Manager

Ionut Tolea and Andrei Vintila presented the paper Yet Another Memory Manager (YAMM) at SNUG Conference 2016 – Munich. The code of YAMM library is released to the public using AMIQ’s GitHub repository.


YAMM Overview

Yet Another Memory Manager (YAMM) is a SystemVerilog library that provides support for memory based operations:

  • Buffers can be allocated following 6 allocation modes with any granularity or address alignment
  • Buffers can be inserted by user (non-overlapping)
  • Buffers can be deallocated either by address or by handle
  • Buffers can be searched for in the memory space by address or by handle
  • Buffers support payload, which can be assigned by the user, randomly generated, read and compared.
  • Implements a fast buffer search algorithm

Beside these features YAMM provides debug facilities (e.g. memory map dump, usage statistics) and it is easy to integrate it with existing verification environments.


Basic Concepts

A memory map’s space is identified by a start address and a size. Similarly, a buffer is a continuous address space defined by a start address and a size. There are two types of buffers:

  • free buffer: can be used for further allocation/insertion of user buffers
  • user buffer: user allocated space

The memory map is initialized to a free buffer. To use a specific area in the memory you must allocate a buffer with a non-0 size. By allocating buffers in the memory, the initial free buffer is split and resized to make room for the new buffers as the example below shows.

Allocation example

De-allocation of a buffer is the inverse operation of allocation. De-allocation replaces a user buffer with a free buffer; YAMM checks if there are neighboring free buffers and merges them into a single free buffer to minimize the list of free buffers.

YAMM provides two classes which enable the memory management functionality:

  • yamm_buffer: base class that implements buffer specific features
  • yamm: inherits yamm_buffer and implements the memory map specific features

Structurally YAMM handles the memory map as a chain of free and occupied buffers managed as two double linked lists (see picture below):

  • all buffers list: chains all buffers in memory
  • free buffers list: chains only the free buffers
YAMM Double Linked Lists


Usage Examples

The following sections will present YAMM’s API through examples in order to get a basic understanding of the YAMM capabilities. Also, as a new user you should have a look at the yamm_tutorial.sv example.

Initialization

Define a memory map by instantiating the yamm object and calling the build() function. At this stage the memory has a name and a size and contains one free buffer that stretches the whole memory space.

import yamm_pkg::*;

// Instantiate a memory map
automatic yamm mem = new; 
// Initialize the memory map space to 1MByte and set it's name to YAMM_MEMORY_MAP
mem.build("YAMM_MEMORY_MAP", 1024*1024);

Buffer Operations: Insertion, Allocation, De-allocation

After initialization, buffers can be allocated by using one of the allocation methods (allocate(), allocate_by_size()). The memory manager will search for an appropriate free buffer to hold the desired memory space (i.e. the size of the free buffer is big enough to hold the allocated buffer). The search for a free buffer will take into account the allocation mode:

  • RANDOM_FIT – Random free buffer that fits the requested memory space, picks a random address inside the free buffer
  • FIRST_FIT – First free buffer that fits the requested memory space, picks first address inside the free buffer
  • BEST_FIT – Smallest free buffer that fits the requested memory space, picks first address inside the free buffer
  • UNIFORM_FIT – Biggest free buffer that fits the requested memory space, fits the allocated buffer in the middle of the free space
  • FIRST_FIT_RANDOM – First free buffer that fits the requested memory space, picks random address inside the free buffer
  • BEST_FIT_RANDOM – Smallest free buffer that fits the requested memory space, picks random address inside the free buffer

All allocation methods return an indication if the allocation was successful or not. In case of successful allocation they return a pointer towards the buffer or a 1’b1, otherwise they return a null buffer or a 1’b0, depending on the method being used.
In the code box below you can see a couple of examples on buffer allocation:

  // declare a yamm_buffer variable
  yamm_buffer a_buffer = new;
  a_buffer.size = 256;
  assert(mem.allocate(a_buffer, RANDOM_FIT)) else `uvm_warning("APB_SEQ", "Can not insert the desired buffer!");
  a_buffer.set_name("A_Buffer");

  ........................
  // allocate by size
  yamm_buffer x_buffer = mem.allocate_by_size(256, RANDOM_FIT);
  assert(x_buffer != null) else `uvm_error("APB_SEQ", "Can not insert the desired buffer!");
  x_buffer.set_name("X_Buffer");

Buffers can also be “manually” inserted at a specific address by using one of the insert methods (e.g. insert(), insert_by_access()). In this case the memory manager will only check if there is enough space at the given address and do the buffer insert.

  yamm_buffer a_buffer = new;
  a_buffer.set_name("A_Buffer");
  a_buffer.size = 256;
  a_buffer.start_addr = 'hCAFE1001;
  // insert() function returns 1 if the insert is successful
  assert (!mem.insert(a_buffer)) else `uvm_warning("APB_SEQ", "Can not insert the desired buffer!");

You probably noticed that buffers get a type name through the set_name() call. Although this operation is not mandatory, it can be helpful if you need to search buffers by type.

The verification components should deallocate buffers as soon as they stop being used. This can be achieved by calling deallocate() for a buffer or deallocate_by_addr() for an address.

  // deallocate by buffer
  mem.deallocate(a_buffer);
  ..............................

  // deallocate by address 
  mem.deallocate_by_addr('hCAFE1001);

Once a buffer is allocated by a verification component it can be searched and used for various purposes (e.g. configuration, checking, payload retrieval) by other verification components. YAMM provides a search API that accepts as search criteria an address, an address range (or an access) or a buffer type. Depending on the search operation a buffer or a list of buffers will be returned; if the search is unsuccessful a null buffer or an empty list will be returned.

   // search buffer by address
   yamm_buffer a_buffer = mem.get_buffer('hCAFE1001);

   ..............................
   // search buffers by address range or by access
   yamm_access basic_access = new;
   basic_access.start_addr = 0;
   basic_access.size = mem.size/2;
   // Get all the buffers contained in the first half of the memory
   yamm_buffer queue[$] = mem.get_buffers_by_access(basic_access);

   ..............................
   // search buffers by range
   yamm_buffer queue[$] = mem.get_buffers_in_range(0, mem.size/2);

   ..............................
   // search buffers by type
   yamm_buffer queue[$] = mem.get_all_buffers_by_type("a_buffer");

Buffer Content Operations

Class yamm_buffer provides API to handle buffer’s contents or payload (i.e. a list of bytes). The user can overwrite the generate_contents() method to generate specific buffer contents. User can directly set buffer’s contents by calling set_contents() or retrieve buffer’s contents by calling get_contents().
The code box below shows few examples of using contents API:

   // set buffer's content
   a_buffer.set_contents('{‘h54,’h45,’h53,’h54});
   ...................................

   // generate buffer's content
   a_buffer.generate_contents();
   ...................................

   // retrieve buffer's content
   byte contents[] = a_buffer.get_contents();

   ...................................
   // Compare buffer's contents to a list of bytes
   assert (general_buffer.compare_contents(access.payload)) else `uvm_error("APC_SCBD", "Contents is not what I expect!");

If the method get_contents() is called without previous initialization of buffer’s contents, it will automatically call the generate_contents() method.


Comparison with uvm_mam

UVM provides a memory model that comes with a simple memory manager called uvm_mam (see Memory Allocation Manager). We used uvm_mam as a reference for feature and performance comparison purposes.

Feature-Wise Comparison
# Category MAM YAMM
1 Memory uvm_mam is linked to uvm_mem which provides the memory locations used for storing data The YAMM top level, as well as every individual buffer contains a memory map composed of multiple buffers that can store simple data
2 Allocation Can only allocate on previously unallocated memory and has only 2 allocation modes Permits allocation in previously allocated memory or inside an already allocated buffer and has 6 allocation modes
3 Deallocation Releases the specific region Releases the region and can display a warning if the deallocated buffer contains other buffers
4 Finding buffers Provides an iterator that user has to use for any needs Provides support for finding and modifying buffers by different criteria
5 Ease of use It’s complex and rather hard to use and for features beyond reserving and freeing regions the user has to go to objects higher in the hierarchy Everything is provided in the same package and can be easily accessed. Memory map can be accessed by calling functions on the top level. Specific regions can be accessed by calling the same functions on the chosen buffers

We measured the performance using the following scenario:

  • Memory space of 1GB
  • 5000 buffer allocations of size 100Bytes
  • Measured the time taken for every 100 allocations
  • Used the broad policy for MAM’s request_region()
  • Used the RANDOM_FIT allocation mode for YAMM’s allocate_by_size()

After running the performance test I obtained the following statistics graphs.

MAM performance graph


YAMM performance graph

The measurements show that allocation of 5000 buffers takes 2 seconds for YAMM compared to MAM which takes 525 seconds. Also the curve that dictates the time dependency with increasing number of allocations is linear in case of YAMM and exponential in case of MAM.

Resources

The YAMM code is provided as an opensource library under Apache License 2.0.
You can download the YAMM library from GitHub.
For getting up to speed you can download YAMM’s User Manual or browse the HTML documentation.

Roadmap

YAMM library is ready to be used for SystemVerilog/UVM-based verification right away. Our roadmap includes few items:

  • Release the C/C++ version
  • Release the e-language version
  • Port the existing SystemVerilog tests to UVM/SV or SVUnit tests
  • Implement access policies

For information on YAMM releases and bug fixes, follow AMIQ’s blog or GitHub repository.

Comments

12 Responses

  1. hello!
    great pkg for memory allocation. helps a lot.
    I think i saw a bug in the allocate_by_size func:

    function yamm_buffer yamm_buffer::allocate_by_size(yamm_size_width_t size, yamm_allocation_mode_e allocation_mode = RANDOM_FIT);

    // Create a buffer and give it the specified size
    yamm_buffer new_buffer = new;
    new_buffer.size = size;

    // Allocate it using the allocation function
    if(this.allocate(new_buffer, allocation_mode)) begin
    return new_buffer;
    end

    It doesn’t copy also the configured granularity/start_addr_align, so I get an irrelevant address.

    a quick fix:

    new_buffer.size = size;
    new_buffer.set_granularity(this.get_granularity);
    new_buffer.set_start_addr_alignment(this.get_start_addr_alignment);

    what do you say?

  2. Hello Elihai,

    Firstly, sorry for such a late response.

    You are correct, there is no way at the moment to use the allocate_by_size() function with different granularities. Conceptually speaking, we don’t want to force the granularity of the above layer of buffers on the sub-buffers. That can be already be done by using allocate() or insert() of buffers created by you. Any granularity can be used.

    I can have a talk with some other users and eventually, i can provide a new argument to the function that would incorporate the functionality you suggested (e. g. port the same granularity as the buffer on the layer above). At the moment my idea would be to keep the default functionality “as is”, but if nobody objects, i can also change it permanently the way you suggested.

    I can get back to you once the git version was updated.

    Cheers!

    Andrei

  3. thanks for your response!
    the fixed that i offered works well for me..

    another issue i’ve found, is that when i use allocate_by_size, the given buffer is not in: start_addr-end_addr range, but in: 0:size range.
    is this in purpose?

    code:

    
    module yamm_tb();
      yamm        m_memory_manager[3];
    
      initial begin
        foreach (m_memory_manager[ii]) begin
          m_memory_manager[ii] = new();
        end
        
        m_memory_manager[0].build("system memory", 16*1024*1024);
        m_memory_manager[0].set_granularity(4);
        m_memory_manager[0].set_start_addr(32'h1000_0000);
    
        //...
    
        foreach (m_memory_manager[ii]) begin
          m_memory_manager[ii].reset();
        end
    
        $display("mem[0]");
        repeat (10) begin
          m_buff = m_memory_manager[0].allocate_by_size(512);
          $display("addr := 0x%08h", m_buff.get_start_addr);
    
        end
      
        //...
    
        $finish();
      end
    endmodule
    
    results:
    mem[0]
    addr := 0x00815e8c
    addr := 0x00415fec
    addr := 0x00c15d24
    addr := 0x0061b718
    addr := 0x002108b8
    addr := 0x003160b4
    addr := 0x00294b24
    addr := 0x0010b0b8
    addr := 0x002d6100
    addr := 0x00253540
    
    1. Hi Elihai,

      Sorry for the late reply, it’s been a difficult period.

      Unfortunately, you are correct, when building a memory, only the size and end address are taking in account. For our own projects we use our own interchangeable offsets when accessing different memories, especially since we also have other misc access rules. For this purpose, the offset for the created memories has been left as 0, to not conflict with user defined adaptation layers.

      Cheers!

      Andrei

  4. Hi

    I have a question on insert operation for buffer .
    When there is insert operation at two different addresses , the insert fails for second operation
    triggering an error “The buffer is already allocated somewhere in the memory , Insertion failed ”

    Can you Please enlighten me here what is the reason for such failure ?
    Is the insert operation doesn’t work for inserting memory at multiple locations ?

    PS : The Addresses and size for above Operations do not overlap!

    1. Hi Alex,

      The buffer you are inserting has to be unique. After the first call on the “insert” function, that buffer that you gave as an argument is now part of the memory and the handle that you have points to the buffer in the memory. To do a second insertion at a different location, you have to create a new buffer that you insert.

      If you tried to re-use the same buffer for a second insertion without calling the constructor you should also see warnings when you tried to use the setters. They should not work, each buffer integrity is protected.

      Alternatively you can just allocate using a “yamm_access” class and calling function “insert_access(obj_of_type_yamm_access)”.

      Regards,
      Andrei

  5. yamm_buffer_locals.sv:
    line 88 : if (!randomize(start_addr) with …

    start_addr is not rand variable,ranomize will take no effect unless using std::randomize instead

    1. Hi Adam! Do you mind telling me what simulator you are using?

      You are indeed correct that the better option would have been to use std::randomize(), to avoid maybe getting warnings from simulator, but i have never seen a randomization fail on any commercial simulator.

      The VIP has been used extensively for years by multiple teams without any issues.

  6. Hi Andrei:
    I’m sorry,u r right,i make a experiment,diffrent result i get when i use different seed (i’m using vcs)
    there is no problem in your code according to 18.11 Inline random variable control of the 1800-2012 LRM

    >> bsub -Is ./simv -l run.log +ntb_random_seed=916249649                                                                                                                                                                                      
    
    start_addr : 6981baa0
    
    start_addr : fa34d9c0
    
    start_addr : b9624b00
    
    $finish at simulation time                    0
               V C S   S i m u l a t i o n   R e p o r t 
    >> bsub -Is ./simv -l run.log +ntb_random_seed=916249640
    
    start_addr : 1c388480
    
    start_addr : d2ac37a0
    
    start_addr : c7882320
    >> cat local.sv 
    class locals;
        protected bit [32-1:0] start_addr;
    
        function compute_start_addr();
            randomize(start_addr) with {
                start_addr%32 ==0;
            };
            $display("start_addr : %0h\n",this.start_addr);
        endfunction
    endclass
    
    program p_locals;
        locals obj;
    
        initial begin
                obj = new;
                obj.compute_start_addr();
                obj.compute_start_addr();
                obj.compute_start_addr();
        end
    endprogram
  7. hi,
    I instantiated a yamm with a size of 32bit, and then I want to allocate multiple yamm buffers with 4-byte alignment and a size range of 1-1000. I found the start address of the first generated yamm buffer is always 32’h815fxxxx. I’m using VCS2018, here is my test code:

    module yamm_compute_start_addr();

    initial begin
    yamm yamm_mem = new();
    yamm.build(“yamm_mem”, 33’h1_0000_0000);

    for(int i=0; i std::randomize instead
    This will solve the problem of randomization, but i don’t know why, could you help me find the reason?

  8. Hello, I .failed to understand how to have the yamm initiating a bus transaction when writing to a buffer and how to leverage burst read/write if the bus allows it.

    The reason why I ask is because I’d like to have the memory manager to allocate memory so host objects can concurrently access the memory fabric with our without sharing resources.

    Maybe I missed some details in the documentation or in the examples.

    Thank you

    1. Hello!

      A memory manager is inherently a virtual component that doesn’t have attached to it a bus or a protocol. The way you use YAMM to generate a bus transaction is by having it be accessed from a sequence that belongs to your bus protocol specific UVC.

      To generate/write a non-overlapping to populate your memory you can do subsequent buffer allocations and then use the addresses and size of the allocated buffers to constraint the fields of you physical UVC item.

      To do accesses to an already allocated memory, you can generate interesting scenarios with partially or total overlapping writes/reads by using YAMM to retrieve different regions. You can also use a YAMM access or your own custom wrapper to randomize different accesses and check their expected outcome against the model.

      To draw a parallel to hardware, YAMM is a memory model that keeps track of what is allocated and where. Your bus UVC is the controller which is tied to the bus and can physically access your memory. They are meant to be used together to generate accesses.

      Regards,
      Andrei

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to our newsletter

Do you want to be up to date with our latest articles?