Implementing Built in RAM Functions in MaxPlus

Courtesy of the Digital Theremin Design Group: Chris Manners
                                                                                  Richard Miranda
                                                                                  Allen Chung


Summary

As with all things in life, doing things the easy way is probably the best. As in the case with implementing memory in a design, MaxPlus2 provides many built in options that can make your life much easier. Depending on your design and the type of FPGA you are using there is no reason to design your own memory module (unless you really want to…).

This application note will (hopefully) give you an overview of how to use the built in functions for memory in MaxPlus2.

The following are the possible choices for implementing RAM:
 

Megafunction
Description
lpm_ram_dp 

(Dual Port RAM)

Parameterized Dual-Port RAM
lpm_ram_dq 

(Dual Port RAM)

Parameterized RAM with Separate Input and Output Ports. Used to implement asynchronous memory or memory with synchronous inputs and/or outputs.
lpm_ram_io Parameterized RAM with a Single I/O Port
lpm_rom Parameterized ROM

Overview

You’ll notice that in each description it says that the function is a "parameterized" one. This means that the function’s behavior is dependent on one or more parameters. Usually, it involves handshaking signals that help to enable the memory to store data (write enable), output data (read enable) or do nothing.

Before you can use any of the "lpm" megafunctions you need to include the appropriate library:

                                        library lpm;
                                        use lpm.lpm_components.all;

From there, the standard VHDL coding guidelines can be followed to implement the memory function. Basic steps include:

    1) Entity declaration that includes the necessary signals and ports for handshaking and control signals.
    2) Architecture declaration that includes the component declaration for the function. This can be found in the help menu in
        MaxPlus2.
    3) Finally, the last step is to complete the port mapping.

Here is a simple example of a program using the lpm_ram_io megafunction:
 
library IEEE;
use IEEE.std_logic_1164.all;
library lpm; -- required for all lpm functions
use lpm.lpm_components.all;

entity io_ram is

port(  address : in std_logic_vector(3 downto 0);
         data : inout std_logic_vector(7 downto 0);
         write_enable : in std_logic;
         clk : in std_logic;
         outputen : in std_logic 
);
end io_ram;

architecture test of io_ram is

-- component declaration for lpm_ram_io

COMPONENT lpm_ram_io

GENERIC (   LPM_WIDTH: POSITIVE;
                      LPM_TYPE: STRING := "LPM_RAM_IO";
                      LPM_WIDTHAD: POSITIVE;
                      LPM_NUMWORDS: NATURAL := 0;
                      LPM_FILE: STRING := "UNUSED";
                      LPM_INDATA: STRING := "REGISTERED";
                      LPM_ADDRESS_CONTROL: STRING := "REGISTERED";
                      LPM_OUTDATA: STRING := "REGISTERED";
                      LPM_HINT: STRING := "UNUSED");

PORT (        address: IN STD_LOGIC_VECTOR(LPM_WIDTHAD-1 DOWNTO 0); 
                    we: IN STD_LOGIC;
                    inclock: IN STD_LOGIC := '0'; 
                   outclock: IN STD_LOGIC := '0'; 
                   outenab: IN STD_LOGIC := '1';
                   memenab: IN STD_LOGIC := '1'; 
                   dio: INOUT STD_LOGIC_VECTOR(LPM_WIDTH-1 DOWNTO 0));

END COMPONENT;

Begin

ram_instance: COMPONENTlpm_ram_io 

GENERIC MAP(  LPM_WIDTH => 8, 
                              LPM_WIDTHAD => 4
                           )

PORT MAP ( address => address, 
                       we => write_enable,
                       inclock => clk, 
                       outclock => clk,
                       outenab => outputen,
                       dio => data
);

end test;

    The above code shows the basic implementation of the lpm_ram_io function.  This function is a single line I/O port parameterized function, which means that the input and output lines are the same.

    For the handshaking signals, only two were required, write_enable and outputen.  As the names imply, when write_enable is high then the memory will store the data and when outputen is asserted then the memory will output its contents.

    Another important step that must be followed in order for the memory function to work is specifying an address location for the data being stored.  This is simply acheived by providing any arbitrary number for each piece of data to be stored.  This will also required when reading out the stored data.  In the above code, a simple 4-bit counter was used to determine address locations.  By using a counter it also makes it easier to see how much data is in the memory contents in case you exceed the capacity and lose important data.  When reading out the data the address is also required in order to pull out correct information.  That is why a counter was used as an address specifier because it is easy to manipulate how the data is outputted and less chance of missing some data.

    Finally, another important detail of using the lpm_ram_io function is that before the data can be read from memory, the data line must be set to high impedance.  Since the data line is a bi-directional one, the input line must be "disabled" so that the data can be outputted.  If not then data coming in would conflict with the data coming out and result in unpredictable errors.

    Below is a small example of how the RAM behaves:


    For more detailed information on how to use lpm_ram_io function or any of the other ones, please consult the help files built into MaxPlus2.



Special Thanks to Raymond Sung who took the time to explain the lpm functions to our group.  Thanks Raymond!  We owe you beers!