README for mempkg.vhd
---------------------

Copyright Comit Systems, Inc., 1999, 2000
All rights reserved.
Limited rights may assign to the downloader as per COMIT SYSTEMS LICENSE
AGREEMENT accepted at the time of download.


DESCRIPTION OF FILES:
---------------------
This release consists of the following files

 ---------------------------------------------------------------------
 mempkg.vhd              The package that contains the linked list
                         memory data structures and functions.

 memory_model.vhd        VHDL memory model 1Meg locations x 32 bits
                         wide. This model uses mempkg.

 memory_model_tst.vhd    Test bench that runs cycles on the memory
                         model.

 memory_model.vec        Test vectors for the memory test bench. To
                         add more cycles, you will need to edit this
                         file.
 ---------------------------------------------------------------------


COMPILING AND RUNNING:
----------------------

Remember, both mempkg and memory_model are VHDL 93 files. memory_model_tst
however, is a VHDL 87 file. To compile, assuming you are using Modelsim 5.1,
run the following commands (in that order).

 vlib work
 vcom -93 -explicit mempkg.vhd
 vcom -93 -explicit memory_model.vhd
 vcom -87 -explicit memory_model_tst.vhd

To simulate the design, run

 vsim memory1Mx32_tst &

 In the VSIM shell, you need to execute

 add wave /*
 run 350

You can see the waveforms and verify that the memory model behaves correctly.


UNDERSANDING MEMPKG:
--------------------

The first declaration is the memory depth and width. MEM_DEPTH is defined as the
exponent to which 2 must be raised to get the number of memory locations in
the memory. In other words, this is the width of the address bus. MEM_WIDTH
is simply the data bus width. We declare them as global variables because they
are loaded at run time.

  shared variable MEM_DEPTH : natural;   --  Address bus width
  shared variable MEM_WIDTH : natural;   --  Data bus width

  type vmem;                               -- Incomplete type vmem

Next is an incomplete type declaration which declares a type called vmem, but
does not define it. This is required so that the we can declare a pointer
to it, vmem_ptr before using vmem_ptr in the declaration of the vmem record.



  type vmem_ptr is access vmem;            -- Pointer to vmem


  type slv_ptr is access std_logic_vector; 

We need to declare a pointer to std_logic_vector, because the package has no
way of knowing how wide the memory is, or how deep. VHDL 93 requires that
record elements not be unconstrained; i.e., the std_logic_vector inside a
record declaration must have static width.

  --  Basic memory element - each is a linked list element. This is the
  --  actual type declaration of vmem. Note that this declaration uses
  --  vmem_ptr, which needs to be declared above.

  type vmem is
    record
      address             : slv_ptr;
      data                : slv_ptr;
      nextp               : vmem_ptr;
      prevp               : vmem_ptr;
    end record;

This step defines the linked list structure. We then define three functions,
create_mem(), write_mem() and read_mem().

create_mem() creates the head of the list, allocates memory for the address
and data pointers, and, this is important, initializes MEM_DEPTH and 
MEM_WIDTH.

  impure function create_mem
    (depth      : natural; 
     width      : natural
    ) 
    return vmem_ptr;

write_mem() writes a new data word to the address specified in the memory 
structure. This writing is done in an unsorted fashion, and in FIFO order. 
This ensures that oldest writes will be accessed faster in real time. This 
is just one way of doing things, and you will need to understand the
constraints of your designs to be able to choose an optimum storing and
retrieval strategy.
  
  procedure write_mem
    (variable mem_ptr     : in  vmem_ptr;
     address              : in  std_logic_vector(MEM_DEPTH - 1 downto 0);
     data                 : in  std_logic_vector(MEM_WIDTH - 1 downto 0)
    );

read_mem() reads a data word from the address specified. If that address has 
not been written to, this function returns X. The output of this procedure is
assigned to "data", which MUST be a signal.

read_mem()  
  procedure read_mem
    (variable mem_ptr     : in  vmem_ptr;
     address              : in  std_logic_vector(MEM_DEPTH - 1 downto 0);
     signal data          : out std_logic_vector(MEM_WIDTH - 1 downto 0)
    );
  

UNDERSTANDING MEMORY_MODEL:
---------------------------

The memory model declares an SRAM entity. This SRAM has 20 address lines
(1 Meg locations), and 32 data lines, resulting in a total memory of 4 MB.
It has shared data lines, so the OE signal is used to enable the output
tristate buffers. WE is the write enable signal and CE is the chip enable.
Writes can be terminated by CE or WE. To read, CE and OE must both be active
(low). All CE, OE and WE are active low.

library IEEE, WORK;
use IEEE.std_logic_1164.all;
use WORK.mempkg.all;

The library declaration must include WORK, and the package mempkg as above.

entity memory1Mx32 is
  port     (address          : in     std_logic_vector(19 downto 0);
            data             : inout  std_logic_vector(31 downto 0);
            ce               : in     std_logic;
            oe               : in     std_logic;
            we               : in     std_logic
           );
end memory1Mx32;

architecture memory1Mx32_A of memory1Mx32 is

This is the most important declaration in the memory model. It declares
a shared variable my_memory (you could choose any other name) of the type
vmem_ptr, which is defined in mempkg. You have to initialize the memory
right at the time of declaration by calling create_mem() with appropriate
arguments, in this case 20 and 32. You could create any number of memory
cells in this fashion, but remember that each must be assigned to a unique
pointer variable.

  shared variable my_memory : vmem_ptr := create_mem(20, 32);
  
Next we have write and read processes for the memory. The write process
is sensitive to CE and WE, and calls a write on the positive edge of either
when the other is low.

  mem_write_P : process(ce, we)
    
  begin
    --  CE terminated write
    if (ce'event and ce = '1') then
      if (we = '0') then
        write_mem(my_memory, address, data);
      end if;
    end if;

    --  WE terminated write
    if (we'event and we = '1') then
      if (ce = '0') then
        write_mem(my_memory, address, data);
      end if;
    end if;
    
  end process;
  
The memory read process is sensitive to address, CE, and OE. When the address
changes, or OE goes low or CE goes low, it triggers a read from memory. This
read returns the value of data at the address specified on the address bus,
or, if that location has never been written to, X.

  mem_read_P : process(address, oe, ce)
  
  begin
    if (address'event or 
       (oe'event and oe = '0') or
       (ce'event and ce = '0')) then
      read_mem(my_memory, address, data_s);
    end if;

  end process;
  
  --  OE and CE tristate the output.
  --  If you need to model an output delay, you can do something
  --  like 
  --        data <= transport data_s after 30 ns;
  
You will need to take care of the fact that OE and CE tristate the output.
Hence the function read_mem is() called with data_s as the output argument,
and the data bus of the model is assigned data_s when CE and OE are both
low.

  data <= data_s when (oe = '0' and ce = '0') else (others => 'Z');

You can add more processes to do setup/hold checks, or to model other
functionality.


UNDERSTANDING THE TEST BENCH MEMORY_MODEL_TST:
----------------------------------------------

The test bench reads the file memory_model.vec, and uses those vectors to
apply the stimulus to the memory model. It does this till EOF of the vector
file is not reached. This is an autogenerated file; a tool called ATBG
(property of Vijay A. Nebhrajani, available free on request from 
vijayn@comit.com) was used to generate it.


STRUCTURE OF THE VECTORS FILE:
------------------------------

The vectors file has multiple lines, each line of which looks like this:

1 0 1 1 1 20 ns

This is interpreted in the following way. Each number is a value of a signal
applied NOW. The signals here are in the same order as the declaration order
in the entity (DUT). The last time value is interpreted as "Apply these values
NOW and wait for 20 ns".

Looking at a part of the memory_model.vec file:

10011010101011110101 11111111111111111111111111111111 1 1 1 20 ns
10011010101011110101 11111111111111111111111111111111 0 1 0 20 ns
10011010101011110101 11111111111111111111111111111111 0 1 1 20 ns
10011010101011110101 11111111111111111111111111111111 0 1 1 20 ns
01010101101001011010 10101010101010101010101010101010 1 1 1 20 ns

The order, as the order of declaration of ports in memory_model.vhd,
is address, data, ce, oe, we.

The first line says, 

Apply Address = 9AAF5 Hex, Data = FFFFFFFF Hex, CE = 1, OE = 1, WE = 1 and
wait for 20 ns.

Time 0: Naturally this executes at time 0, so this is the initial value of the
model ports, and is held for 20 ns.

Time 20 ns: The next line holds the same address, data, and OE, but makes WE 
and CE low. 

Time 40 ns: WE is made high, creating a WE terminated write.

Time 60 ns: No activity (lines 4 and 3 are exactly the same).

Time 80 ns: CE is made high, address is made 55A5A Hex, data is made AAAAAAAA Hex,
CE, OE, WE are all inactive.

In this way, the vectors file defines two writes (one CE terminated and one WE
terminated), and two reads. You are free to create any other cycles you want.
You can also test the model by creating any other tests and apply them in
any other way you want. In passing, note that the 20 ns is a completely 
arbitrary choice, and you could have any time value at the end of any line.
There is no restriction on the magnitude, and more importantly, there is
no restriction on the units. Thus you could write:

1 0 1 1 7 ps
0 1 1 1 4 ms

This would be OK for the test bench, as long as the value conforms to the
VHDL definition of type "time".


UNDERSTANDING THE LIST FILE:
----------------------------

When the test bench finishes, it creates a list file, which lists out, in a
tabular format, the value of all the ports of the model at each simulation 
delta. This is named memory_model.list, and looks like:

 0 ns    10011010101011110101    UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU   1   1   1
20 ns    10011010101011110101    11111111111111111111111111111111   0   1   0
40 ns    10011010101011110101    11111111111111111111111111111111   0   1   1
60 ns    10011010101011110101    11111111111111111111111111111111   0   1   1
80 ns    01010101101001011010    11111111111111111111111111111111   1   1   1

It describes the port values at in the same order as they were declared in
the model. You could use the list file to view waveforms later, without
running the simulation again. In a sense it is like a simulation dump,
except that it only has ports of the DUT.


CREATING YOUR OWN MEMORY MODELS:
--------------------------------

If you are OK with the predefined structures and functions in mempkg,
you could use them to build your own memory model in much the same way
as described above for memory_model.vhd. If you need a different storage
or retrieval algorithm, you would need to write your own structures
and read/write functions.


<EOF>