From Hamsterworks Wiki!
Please make sure that you have completed the earlier modules of FPGA course.
Having problems with your code? Do I need to change something? Want a reference solution to the project? Email me at "email@example.com", after removing the "nospam-" bit. I'll try to get back to you in a day or so.
Aims of module
- Use the IP Core Generator to create two components - a counter and a block RAM acting as a ROM.
- Wire them together and to the LEDs
- Set predefined data values into the ROM
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?
The Spartan 3 FPGAs (and I assume most others) have super-flexible RAM blocks. On the Spartan 3E each of these have 18 kilobits of RAM, and can be presented to the system in different widths.
Eighteen kilobits is an odd size - but it is used as it allows 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 simplifies many designs (such as building FIFOs).
See http://www.xilinx.com/support/documentation/application_notes/xapp463.pdf for complete documentation and some very cunning uses for BRAM.
Using the Core Generator with BRAM
Using the Core generator makes build 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 userful 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(29 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 it's 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,
But for this project, here is a sample file that you can download and save File: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 length 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.
The finishing touches
Now add the Implementation Constraints file (this file is for the Nexys2 - I will update with Basys2 constraints soon):
NET "clk" LOC = "B8"; NET "Leds<0>" LOC = "J14"; NET "Leds<1>" LOC = "J15"; NET "Leds<2>" LOC = "K15"; NET "Leds<3>" LOC = "K14"; NET "Leds<4>" LOC = "E17"; NET "Leds<5>" LOC = "P15"; NET "Leds<6>" LOC = "F4"; NET "Leds<7>" LOC = "R4";
Rebuild the project, download it and watch the lights!
Ready to carry on?
Click here to carry on to the next module.