A System Call to Implement Virtual Memory Management Scheme

Copyright by
Swapnajit Mittra
Vineyard Reasearch Inc.

(This article, in a dissertation form, was first published in the Proceedings of 4th International Verilog Conference organized by IEEE).




Quite often in a big design (such as a processor, micro-controller or a DSP processor) to verify the functionality of the chip at the system level, you need to have a large chunk of memory modelled along other components of the system. In simulators like Verilog-XL, however, memory variables are statically allocated making it necessary to crash if that much memory is not available on the simulating machine. Since main memory sizes of several MB are commonplace today in complex systems, it is, of course, not possible to put that much memory on the simulating machine.

The system call $mem_read provides a convenient way to integrate theoretically infinite amount of memory (constrained by the size of your hard disk) into your system. You define a small memory in your Verilog code. Before running the code you create a file which has been initialized with the data that you want to put into your memory. Then through the system call $mem_read you would be able to read from any location of the memory. With very little modification, it is possible to use the code for writing into the memory too. It is left as an exercise for the readers. The complete example with both read and write can also be found at Principle of Verilog PLI by Swapnajit Mittra.

You might wonder what is the use of defining a smaller Verilog memory when all accesses to the memory are done to or from the file. The small memory acts as a local cache and stops your code from going to the hard-disk (or making a cache flush) all the time, thus greatly increasing the simulator performance.The size you choose for this memory is a trade-off between simulation performance and system resources.

List 1 describes one sample code which uses/tests the system call. List 2 shows a sample file with randomly generated data in it. List 3 gives the partial listing of the results. Note that, since index 16 or 17 is beyond the range of the file, the memory is initialized with Xs in those cases. List 4 has the complete listing of the code.


module mymodule;

reg [15:0] mymemory [3:0];
reg [15:0] val;

reg [15:0] reference [15:0];
integer i, j;

initial begin
   $readmemb("file.dat", mymemory);
   $readmemb("file.dat", reference);

   j=0;
   while (j<100) begin
      i=$random%18;
      if (i<0) i = -i;
      #1;
      $mem_read(mymemory[0],
                "file.dat",
                i,
                val,
                4);
      #1;
      $display(" index=%d, val = %b", i, val);

      #1;
      if (reference[i] !== val) begin
         $display(" ERROR!!! Data Mismatch ...");
         $display($time,": reference[%d] = %b, val = %b ",
            i, reference[i], val);
         $stop;
      end
      j = j+1;

   end

   $finish;
end
endmodule

List1 : Sample Code


1001110110110000
0000100000000000
0011000100000111
0011001000010001
0011111001010000
1110010101111101
0100110001001000
0100000011000000
0001000101100110
0111010000000100
0000010001011011
0110101000101101
0101110100001010
1000111001010010
0101100010010011
0000101000001100

List2 : Sample Data File


 index=          8, val = 0001000101100110
 index=         13, val = 1000111001010010
 index=          9, val = 0111010000000100
 index=         17, val = xxxxxxxxxxxxxxxx
 index=         15, val = 0000101000001100
 index=         17, val = xxxxxxxxxxxxxxxx
 index=         17, val = xxxxxxxxxxxxxxxx
 index=          0, val = 1001110110110000
 index=          9, val = 0111010000000100
 index=         13, val = 1000111001010010
 index=          0, val = 1001110110110000
 index=         17, val = xxxxxxxxxxxxxxxx

List3 : Result of Using $mem_read


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "veriuser.h"
#include "acc_user.h"

#define MEMNAME  1
#define FILENAME 2
#define INDEX    3
#define VALUE    4
#define DEPTH    5

void mem_read_check();
void mem_read_call();

char *read_it_from_memory();
void read_in_values_from_the_file();
void write_back_the_memory_into_file();
void put_str_into_memory();

void set_memory_bit();
void set_bit(), clear_bit();

s_tfnodeinfo info;

    /* usertask, 0, mem_read_check, 0, mem_read_call, 0, "$mem_read" */

/**********************************************
   Function name : mem_read_check().

   Author: Swapnajit Mittra.

   Description: This is the checktf function.
   It checks the validity of each parameter
   that the PLI routine takes.
**********************************************/

void mem_read_check() {

   FILE *fp;

   if (tf_nump() != 5) {
      io_printf("=mem_read: USAGE: $mem_read( name_of_mem_variable,\n");
      io_printf("=mem_read:                   name_of_the_file,\n");
      io_printf("=mem_read:                   index_to_be_read,\n");
      io_printf("=mem_read:                   variable,\n");
      io_printf("=mem_read:                   depth_of_temp_memory);\n");
      io_printf("=mem_read:\n");
      exit(2);
   }

   if (tf_typep(MEMNAME) != tf_readwrite) {
      io_printf(" Argument 1 should be a memory variable; Exiting ...\n");
      exit(2);
   } else {
      tf_nodeinfo(MEMNAME, &info);
      if (info.node_type != tf_memory_node ) {
         io_printf(" Argument 1 should be a memory variable; Exiting ...\n");
         exit(2);
      }
   }

   if (tf_typep(FILENAME) != tf_string) {
      io_printf(" Argument 2 should be a string - a filename; Exiting ...\n");
      exit(2);
   } else {
      if ((fp = fopen(tf_strgetp(FILENAME),"r+")) == (FILE *)NULL) {
         io_printf(" Sorry, your buffer file %s does not exist...exiting;\n",
                   tf_strgetp(FILENAME));
         exit(2);
      } else
         fclose(fp);
   }

   if ((tf_typep(INDEX) != tf_readonly) && (tf_typep(INDEX) != tf_readwrite)) {
      io_printf(" Argument 3 should be an integer or const; Exiting ...\n");
      exit(2);
   }

   if (tf_typep(VALUE) != tf_readwrite) {
      io_printf(" Argument 4 should be a register variable; Exiting ...\n");
      exit(2);
   }

   if (tf_typep(DEPTH) != tf_readonly) {
      io_printf(" Argument 5 should be a constant integer; Exiting ...\n");
      exit(2);
   }
}

/**********************************************
   Function name : mem_read_call().

   Author: Swapnajit Mittra.

   Description: This is the calltf function.
   It is the main body of the PLI application.
   It decides whether the index whose value has
   to be read is already in the memory or not,
   and depending on that takes appropriate
   actions.
**********************************************/

void mem_read_call() {
static int strt_index = 0;
int depth, index;

   depth = tf_getp(DEPTH);
   index = tf_getp(INDEX);

   if (( strt_index <= index) && ((strt_index+depth) > index)) {
#ifdef DEBUG
      io_printf("strt_index = %d, index = %d, depth = %d: Found in the cache\n",
                strt_index, index, depth);
#endif
      tf_strdelputp(VALUE, tf_sizep(VALUE), 'b', read_it_from_memory(index-strt_index), 0, 0);
   }
   else {
#ifdef DEBUG
      io_printf("strt_index = %d, index = %d, depth = %d: NOT Found in the cache\n",
                strt_index, index, depth);
#endif
      read_in_values_from_the_file(index);
      strt_index = index;
   }
}

/**********************************************
   Function name : read_it_from_memory().

   Author: Swapnajit Mittra.

   Description: If the index whose value has to
   be read, is already in the memory, this function
   is called. It reads the value from the memory
   and send it back to mem_read_call() as a
   string.
**********************************************/

char *read_it_from_memory(index)
int index;
{
int i, j, k;
int this_bit_a, this_bit_b, ngroups;
char a, b, temp, *aptr, *bptr, *cptr;

   ngroups = info.node_ngroups;
   cptr = (char *)calloc(8*ngroups, 1);
   /* assuming sizeof(char) = 1 */

   aptr =  info.node_value.memoryval_p + 2*ngroups*index;
   bptr =  info.node_value.memoryval_p + 2*ngroups*index + ngroups;

   for (i=0; i<ngroups; i++) {
      a = aptr[i];
      b = bptr[i];

      for (j=0; j<8; j++) {
         this_bit_a = (a & 0x1);
         this_bit_b = (b & 0x1);

         cptr[i*8+j] = ((this_bit_a == 0) && (this_bit_b == 0)) ?  '0':
                        ((this_bit_a == 1) && (this_bit_b == 0)) ? '1':
                        ((this_bit_a == 0) && (this_bit_b == 1)) ? 'z': 'x';
         a >>= 1; b >>= 1;
      }
   }

   /* null terminate the string and let j
      indicates  the actual string length */
   cptr[(i-1)*8+j] = '\0';

   j += (i-1)*8;
   for (k=0;k<j/2;k++) {
      temp = cptr[k];
      cptr[k] = cptr[j-k-1];
      cptr[j-k-1] = temp;
   }

   return(cptr);
}
/*****************************************************
   Function name : read_in_values_from_the_file().

   Author: Swapnajit Mittra.

   Description: If the index ii, whose value has to
   be read, is not in the memory at this time, this
   function is called. It reads  the value from the
   file, and since this is a cache  miss, swaps out
   the previous contents of the memory and then loads
   new locations into it.
*****************************************************/

void read_in_values_from_the_file(ii)
int ii;
{
int i, j, beyond;
char *s;
int depth, width;
FILE *fp;

   /* Initialize all the local variables */
   depth   = tf_getp(DEPTH);
/* depth   = info.node_mem_size; */
      /* depth of the memory -  node_mem_size is NOT SUPPORTED IN VCS */
   width   = info.node_vec_size;

   if ((fp = fopen(tf_strgetp(FILENAME),"r+")) == NULL)
      tf_error("File %s can not be opened\n", tf_strgetp(FILENAME));

   /* Take the file pointer right before the indexed element */
   fseek(fp, 0, SEEK_SET);

   beyond = 0;
   i = 0;
   while (i<ii)  {
      s = (char *)malloc(width + 2);
      if (fgets(s, width+2, fp) == (char *)NULL) {
         i = ii;
         beyond = 1;
      }
      free(s);
      i++;
   }

   /* Now read as many elements as the depth of the memory
      from the file and put it into memory.  */

   for (j=ii; j<(ii+depth); j++) {
      /* Read an element */
      s = (char *)malloc(width + 2);
         /* Each line contain one row of the memory (of
            width = width) + one new line + one null char */
      if ((fgets(s, width+2, fp) == (char *)NULL) ||
          (beyond == 1)) {
         for (i=0; i<width; i++)
            s[i] = 'x';
         s[i] = '\0';
      }
      s[width] = '\0'; /* chopping off the newline from the end */

      /* If this is the memory element to be read, copy
         it to the assigned variable */
      if (j==ii)
         tf_strdelputp(VALUE, tf_sizep(VALUE), 'b', s, 0, 0);

      /* Also update the actual memory */
      put_str_into_memory(s,j-ii);
      free(s);
   }

   fclose(fp);
}

/*****************************************************
   Function name : put_str_into_memory()

   Author: Swapnajit Mittra.

   Description: Taking the value of an index ind as a
   string s and this function writes this value to the
   memory.
*****************************************************/

void put_str_into_memory(s, ind)
char *s;                /* string read from data file */
int ind;                /* index of the current location */
{
char *aPtr, *bPtr;
int k, ngroups, memWidth;

   ngroups = info.node_ngroups;
   memWidth = info.node_vec_size;

   aPtr = info.node_value.memoryval_p+2*ind*ngroups;
   bPtr = info.node_value.memoryval_p+2*ind*ngroups+ngroups;

   for (k=0; k<memWidth; k++)
      set_memory_bit(aPtr, bPtr, k, ngroups, s[k]);

}

void set_memory_bit(aVal, bVal, bitPos, ngroups, bitValue)
char *aVal, *bVal;
int bitPos, ngroups;
char bitValue;
{

   int bitNum = bitPos%8;
   int groupVal = ngroups - 1 - bitPos/8;

   switch (bitValue) {
         case '0':
            clear_bit(aVal+groupVal, bitNum);
            clear_bit(bVal+groupVal, bitNum);
            break;
         case '1':
            set_bit(aVal+groupVal, bitNum);
            clear_bit(bVal+groupVal, bitNum);
            break;
         case 'z':
         case 'Z':
            clear_bit(aVal+groupVal, bitNum);
            set_bit(bVal+groupVal, bitNum);
            break;
         case 'x':
         case 'X':
            set_bit(aVal+groupVal, bitNum);
            set_bit(bVal+groupVal, bitNum);
            break;
      }
}

void set_bit(val_ptr, bit_index)
char *val_ptr;
int bit_index;
{

   int mask = 0x80;

   mask >>= bit_index;
   *val_ptr |= mask;
}

void clear_bit(val_ptr, bit_index)
char *val_ptr;
int bit_index;
{
   int mask = 0x80;

   mask = ~(mask>>bit_index);
   *val_ptr &= mask;
}


List4 : Listing for the system call $mem_read
Share/Save/Bookmark



Verification Management
Join Verification Management Group


Book of the Month


From Our Press