Wednesday 18 January 2017

Using the FPGA’s internal RAM

As well as the resources required for implementing digital logic, FPGAs also have a small amount of RAM built in. This RAM is very useful and can meet the entire RAM needs of many projects.

Each Vendor’s RAM blocks have differing capabilities and is configured differently, so it makes sense to use this as a way of introducing the IP Core Generator.

This project is very "GUI" based - unlike the last module it is very much a walk through.

What is Block RAM? What can it do?

On the Spartan 3E each RAM block has 18 kilobits of RAM, and can be presented to the system in different widths. Eighteen kilobits is an odd size, but it is designed that way to allow for either parity or ECC bits to be stored.

The most common configuration I’ve used is 2048 words of 8 bits, but it can be configured as one of either 16k x 1bit, 8k x 2 bits, 4k x 4 bits, 2k x 8 bits, 2k x 9 bits, 1k x 16 bits, 1k x 18 bits, 512 x 32 bits, 512 x 36 bits or 256 x 72 bits.

The blocks are especially useful as they are dual-port - there are two independent address, read and write ports that simplify many designs (such as building FIFOs).

Using the CORE Generator with BRAM

Using the CORE generator makes building BRAM components very simple - and if required it also transparently constructs larger memories out of multiple primitives. In the project you will use the CORE Generator as creating them directly in VHDL is quite cumbersome and complex.

Preparing the project

• Create a new project - I called mine "flashylights".
• Add a module which has the clock signal as the only input and the eight LEDs as the output
You should get a module that looks like this:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity FlashyLights is
Port ( clk : in STD_LOGIC;
LEDs : out STD_LOGIC_VECTOR (7 downto 0));
end FlashyLights;
architecture Behavioral of FlashyLights is
begin
end Behavioral;

We now need to add a couple of Wizard generated components.

Using the IP CORE Generator

Add a new source file to the project:

Select "IP" and call the module counter30 - it will be a 30 bit counter

You will be presented with the "Select IP" dialogue box. Tick the "Only IP compatible with chosen part" tickbox:


Navigate down into "Basic Elements"/"Binary Counter" and click "Next"

After a long delay, the options for Binary Counter will appear. Set the "Output Width" to 30 - and if you want, click on the "Datasheet" button:

Then click "Generate".
In the Hierarchy window you will now have a "counter30" component. Click on it and then under the Processes tree select "View HDL Instantiation Template":



Copy and paste the useful bits into your top level project - add a signal "counter" to be connected to the output of the counter.  Here’s the completed source:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity FlashyLights is
Port ( clk : in STD_LOGIC;
LEDs : out STD_LOGIC_VECTOR (7 downto 0));
end FlashyLights;
architecture Behavioral of FlashyLights is
COMPONENT counter30
PORT (
clk : IN STD_LOGIC;
q : OUT STD_LOGIC_VECTOR(29 DOWNTO 0)
);
END COMPONENT;
signal count : STD_LOGIC_VECTOR(30 downto 0);
begin
addr_counter : counter30
PORT MAP (
clk => clk,
q => count
);
end Behavioral;

Adding the ROM component

Add another new IP module called "memory", but this time select the Block Memory Generator:


The Block Memory Generator has 6 pages of settings - at the moment we only need to enter things on the first three. Just click "Next" on the first screen:


Select that we want a Single Port ROM, then click "Next":


Set "Read Width" to 8 - we have eight LEDs to light. Set the "Read Depth" to 1024. Click "Next":


Don’t bother going through the rest of the screens - they don’t apply at the moment - just click "Generate"
You will now have another component, and you can view its instantiation template.


Add it to the source, connecting the top 10 bits of the counter to the ROM’s address bus (addra), and the data bus (douta) to the LEDs:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity FlashyLights is
Port ( clk : in STD_LOGIC;
LEDs : out STD_LOGIC_VECTOR (7 downto 0));
end FlashyLights;
architecture Behavioral of FlashyLights is
COMPONENT counter30
PORT (
clk : IN STD_LOGIC;
q : OUT STD_LOGIC_VECTOR(29 DOWNTO 0)
);
END COMPONENT;
COMPONENT memory
PORT (
clka : IN STD_LOGIC;
addra : IN STD_LOGIC_VECTOR(9 DOWNTO 0);
douta : OUT STD_LOGIC_VECTOR(7 DOWNTO 0)
);
END COMPONENT;
signal count : STD_LOGIC_VECTOR(29 downto 0);
begin
addr_counter : counter30
PORT MAP (
clk => clk,
q => count
);
rom_memory: memory
PORT MAP (
clka => clk,
addra => count(29 downto 20),
douta => LEDs
);
end Behavioral;


Once built, you can view the RTL schematic - looks as you would expect:



Setting the contents of the ROM

At the moment the ROM is blank (all ’0’s). When the FPGA is configured, the contents of the block RAM can be set to values that are predefined in the configuration bit stream.

Page 4 of the Block Memory Generator gives you the option to set the contents of the ROM using a ".coe" file. Here’s enough of the file that you will be able to write your own from scratch:

memory_initialization_radix=10;
memory_initialization_vector=
128,
128,
127,
127,
127,

Here’s another, using binary (as memory_initialization_radix=2) for a memory with a data width of 15:
memory_initialization_radix=2;
memory_initialization_vector=
001110000000001,
010110000000010,
000010000000011,
000010000000100,
000010000000101,
000010000000110,

Create a sample file of 8 bit binary values - make the 1 bits zig-zag from left to right, or some other pattern - the more lines the merrier. Call it "flashy.coe".

Edit the "memory" component (just double-click it in the Hierarchy tree) and skip through to Page 4. Set the initialisation file to flashy.coe.


It is always a good idea to click on the "Show" button - it will give you a warning if your .coe file is not correct. Click the Generate button to update the IP module.

As an aside, there are other ways to do this, allowing you to inject contents (e.g., maybe bootloader) after the .bit file is built. This allows you to avoid a lengthy rebuild of a whole project just to change the initial values in a BRAM. It is also a good way to allow an end-user to customise the .bit file without providing access to your source code. Search for "Xilinx data2mem" on Google.

The finishing touches

NET LEDs(7) LOC = "P5" | IOSTANDARD=LVCMOS25;
NET LEDs(6) LOC = "P9" | IOSTANDARD=LVCMOS25;
NET LEDs(5) LOC = "P10" | IOSTANDARD=LVCMOS25;
NET LEDs(4) LOC = "P11" | IOSTANDARD=LVCMOS25;
NET LEDs(3) LOC = "P12" | IOSTANDARD=LVCMOS25;
NET LEDs(2) LOC = "P15" | IOSTANDARD=LVCMOS25;
NET LEDs(1) LOC = "P16" | IOSTANDARD=LVCMOS25;
NET LEDs(0) LOC = "P17" | IOSTANDARD=LVCMOS25;
NET "clk" LOC="P89" | IOSTANDARD=LVCMOS25 | PERIOD=31.25ns;

Rebuild the project, download it and watch the lights!

No comments:

Post a Comment