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
|