--/-- GPmem -- --| A general purpose memory, intended to map well to the FPGA block RAM's. --| --| Highlights: --| * Optionally dual port. --| * Arbitrary memory size. --| * Memory content can be initiated from a separate file. --| * Data port type is std_logic_vector. --| * Latency is one clock cycle. --| * Read before write (if you write to a memory content, you can read what was there before). --| --| Generics: --| * AWidth: Number of adress bits. I.e., there is 2^AWidth rows. --| * DWidth: Width of memory. --| * Port{A,B}Mode: A string defining the role of the port. --| - "OFF": The port is unused (applicable for PortBMode only). --| - "ROM": Read only port. --| - "RAM": Read-write. --| * init_filename: An optional file to load initial data from. --| (this file has at least 2^AWidth rows, where each row start with --| DWidth bits, '0' or '1'. Any more content on the line are ignored. --| Example of my_memory_program.txt (first 4 lines, 16 bits): --| | 0100000100110101 0 ldi r1, 0x35 --| | 1001001001100001 1 ld r2,(r1) --| | 0110000100000000 2 loop: dec r2 --| | 1110001011111110 3 breq loop --| In simulation, this file is read at simulation/restart time. --| I.e. If you change the data in the text file, this will take effect --| if you run "restart -f" in modelsim. --| --| Ports: --| * clk: clock (output is one clock flank after input). --| * addr{A,B}: Address of port A or B. --| * rData{A,B}: Data output (one clock cycle delay). --| * wData{A,B}: Data to be written to the memory. --| * wEn{A,B}: Write enable. Active high. --| --| Usage examples: --| * In declaration part: --| | component GPmem is --| | generic( ); --| | port( ); --| | end component; --| (i.e., you can ignore the "begin" and asserts) --| * Instantiate as a single port ROM (with initialization): --| | mcode_rom : GPmem --| | generic map(AWidth=>8, DWidth=>36, PortAMode=>"ROM", init_filename=>"compiled_microcode.txt") --| | port map(clk=>clk, addrA=>muaddr, rDataA=>microcode_line); --| * Instantiate as a single port RAM (with initialization): --| | data_mem : GPmem --| | generic map(AWidth=>12, DWidth=>8, PortAMode=>"RAM", init_filename=>"random_data.txt") --| | port map(clk=>clk, addrA=>ram_addr, wDataA=>ram_wdata, wEnA=>ram_wen, rDataA=>ram_rdata); --| * Instantiate as a simple dual-port RAM (initiated with zeros): --| | video_buffer_mem : GPmem --| | generic map(AWidth=>16, DWidth=>3, PortAMode=>"RAM", PortBMode=>"ROM") --| | port map(clk=>clk, addrA=>wAddr, wDataA=>wData, wEnA=>wEn, addrB=>VGA_addr, rDataB=>VGA_RGB); --| * Instantiate without the component declaration: --| | mcode_rom : entity work.GPmem --| | generic map(...) --| | port map(...); --| (but for this to work, you must compile GPmem before it is used) --| Note: There is no ";" after the generic map. --\-- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use std.textio.all; -- read text file use ieee.std_logic_textio.all; -- read text file as std_logic_vector entity GPmem is generic( AWidth : integer := 8; -- Address width. #rows = 2^AWidth DWidth : integer := 8; -- Data width PortAMode : string := "ROM"; -- "ROM" or "RAM". Ignore wEn in ROM mode. PortBMode : string := "OFF"; -- "OFF", "ROM" or "RAM". init_filename : string := ""); -- if provided: read init content from this file. port(clk : in std_logic; addrA, addrB : in unsigned(AWidth-1 downto 0) := (others=>'0'); wDataA, wDataB : in std_logic_vector(DWidth-1 downto 0) := (others=>'0'); wEnA, wEnB : in std_logic := '0'; rDataA, rDataB : out std_logic_vector(DWidth-1 downto 0) ); begin assert PortAMode="ROM" or PortAMode="RAM" report "PortA must be of mode ""ROM"" or ""RAM""." severity failure; assert PortBMode="OFF" or PortBMode="ROM" or PortBMode="RAM" report "PortB must be of mode ""OFF"", ""ROM"" or ""RAM""." severity failure; end entity; architecture rtl of GPmem is type mem_t is array(0 to 2**AWidth-1) of std_logic_vector(DWidth-1 downto 0); impure function read_init_file return mem_t is file f : text; variable l : line; variable res : mem_t := (others=>(others=>'0')); begin -- this is performed in compilation time, and should be fine to synthesize. if init_filename'length > 0 then file_open(f, init_filename, read_mode); l1:for n in res'range loop if endfile(f) then report "Unexpected end of file in " & init_filename severity warning; exit l1; end if; readline(f, l); read(l, res(n)); -- read a std_logic_vector to address n. end loop; end if; return res; end function; signal mem : mem_t := read_init_file; begin process(clk) begin if rising_edge(clk) then -- port A: if wEnA = '1' and PortAMode = "RAM" then mem(to_integer(addrA)) <= wDataA; end if; rDataA <= mem(to_integer(addrA)); -- port B: if PortBMode = "ROM" or PortBMode = "RAM" then if wEnB = '1' and PortBMode = "RAM" then assert not( wEnA='1' and PortAMode="RAM" and addrA=addrB) report "Both ports try to write to the same address" severity warning; mem(to_integer(addrB)) <= wDataB; end if; rDataB <= mem(to_integer(addrB)); end if; end if; end process; end architecture; -- The block RAM looks like this in the Spartan6 FPGA: -- * A mandantory register on address and wData -- * An optional register on rData. -- * A common memory area of 18 kib (can be split into two 9-kib-memories). -- * Two ports, each can be (individually) configured as the aspect ratios: -- - 16384x1, 8192x2, 4096x4, 2048x8, 1024x16, 512x32, 2048x9, 1024x18, 512x36 bits. -- (The 9, 18 and 36 bits include the optional parity). -- - Limitation: Either none or both ports must use parity, so you cannot combine e.g. portA:8192x2 with portB:512x36. -- * Each port has its own clock input. -- * Writing to the same location may corrupt data. -- * Read more: Google for Xilinx UG383 -- Some possible modifications, for which this will still map to block RAMs: -- * Change data type from std_logic_vector to unsigned/signed. -- * Individual clocks for the ports (clkA, clkB). -- * Individual aspect ratios for the ports (AWidth{A,B}, DWidth{A,B}). -- * Add an extra pipeline level on output. -- -- Bad-idea-modifications, if you want to map it to block RAMs: -- * Add a third port (the block RAMs only have two ports). -- * Make the writing combinational (the input adress and data are registered). -- * Make the output combinational (the input adress is registered). -- * Installing FreeBSD in GPmem (not enough memory in FPGA).