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:
|
|
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);
architecture test of io_ram is -- component declaration for lpm_ram_io COMPONENT lpm_ram_io GENERIC ( LPM_WIDTH:
POSITIVE;
PORT (
address: IN STD_LOGIC_VECTOR(LPM_WIDTHAD-1
DOWNTO
0);
END COMPONENT; Begin ram_instance: COMPONENTlpm_ram_io GENERIC MAP( LPM_WIDTH
=> 8,
PORT MAP ( address => address,
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.