Описание блоков памяти на языке VHDL

от автора

В данной статье показаны основные принципы описания модулей ПЗУ и ОЗУ на языке VHDL. Статья ориентирована на начинающих. Ее цель — дать общее понятие об описании модулей памяти на языке VHDL. Примеры и иллюстрации предены для пакета Quartus II v. 9.1. Предполагается, что читатель знает как создавать проект в пакете Quartus II, проводить его компиляцию и симуляцию.

Общие положения

Память на языке VHDL описывается как массив (array) векторов. Разрядность вектора определяется разрядностью ячейки памяти, а количество векторов — количеством ячеек в модуле памяти.

Например, для модуля памяти из 32 ячеек, каждая из которых содержит 8 бит, необходимо объявить массив, в котором содержится 32 вектора, каждый из которых является восьмиразрядным.

type mem is array (0 to 31) of std_logic_vector (7 downto 0); 

Дальше необходимо описать входы адреса, входы и выходы данных, управляющие сигналы. Тип портов данных должен совпадать с типом данных отдельной ячейки. Для приведенного выше примера – это std_logic_vector (7 downto 0).

data_in: in std_logic_vector (7 downto 0); data_out: out std_logic_vector (7 downto 0); 

Тип данных для адреса – integer или основанные на нем типы. Тип integer необходим потому, что адрес используется как индекс массива памяти.

addr: in integer range 0 to 31; 

Описание памяти лучше выполнять с помощью параметризованих модулей. Это разрешает повторно использовать написанный код. Ниже приведен пример параметризованного модуля размером 32×8. В примере для описания модуля памяти используется параметры addr_width и data_width, которые задают разрядность шин адреса и данных соответственно. Количество ячеек в блоке памяти в этом случае определяется как 2**addr_width, а их разрядность равняется data_width.

generic (addr_width: natural:= 5; data_width: natural:=8); port ( 	addr: in integer range 0 to 2**addr_width - 1; 	data_in: in std_logic_vector (data_width-1 downto 0);     data_out: out std_logic_vector (data_width-1 downto 0) ); type mem is array (2**addr_width-1 downto 0) of std_logic_vector (7 downto 0); 

Описание постоянных запоминающих устройств на языке VHDL

При описании постоянных запоминающих устройств содержимое ячеек необходимо определять при написании программы. Возможно использование нескольких вариантов определения содержимого памяти:

  1. создание константы или сигнала типа «массив»;
  2. использование оператора case;
  3. использование *.mif файла и атрибутов синтеза.

Из трех вариантов два первых могут быть реализованы на микросхемах ПЛИС любого производителя, а третий возможен лишь в пакете Quartus II.

Определение содержимого памяти с помощью константы или массива.

При использовании этого варианту сначала необходимо объявить тип, который будет отвечать размеру блока памяти. Потом объявляется константа этого типа и определяется содержимое всех ячеек массива.
Например, объявим новый тип ROM, который представляет собой массив с 8 ячеек, каждая из которых имеет размер 8 бит. Потом определим константу Content типа ROM.

type ROM is array (0 to 7) of  std_logic_vector  (7 downto 0); constant Content: ROM := (     0 => "00000001",	     1 => "00000010",	     2 => "00000011",	     3 => "00000100",      4 => "00000101",	     5 => "00000110",      6 => "00000111",	     7 => "00001000",  );  

Для использования такой константы необходимо просто адресовать необходимую ячейку в массиве с помощью входных данных с линий адреса. Исходный порт данных должен иметь тот же самый тип, что и тип ячейки блока памяти. Для приведенного выше примера исходный порт Data_out должен иметь тип std_logic_vector (7 downto 0). Доступ к содержимому памяти будет выглядеть таким образом:

Data_out <= Content (Addr); 

Пример 1. Рассмотрим пример полного описания блока памяти с использованием константы. Блок памяти, который отвечает этому описанию, показан на рисунке 1.


Рисунок 1 — Блок памяти, описанный в примере 1

Строки 13 и 14 объявляют тип массив из 32 ячеек, каждая из которых содержит 8 бит.
Строки с 15 по 23 задают значение ячеек массива. Отдельно определяются значения только для первых 16 ячеек – строки с 16 по 22. Все другие ячейки заполняются одинаковым значением «1111 1111» с помощью слова others – строка 23.
Работа модуля памяти описывается с помощью оператора процесса, в список инициализации которого входят сигналы clk, cs – тактовый и выбора кристалла соответственно. Если сигнал cs равняется единице исходные линии ПЗУ переходят в Z-состояние (строки 27 и 28). Если же сигнал cs равняется нулю, то выходы переходят к рабочему состоянию и происходит работа микросхемы.
Строка 29 проверяет наличие переднего фронта тактового сигнала clk.
Строки 30-35 описывают процесс чтения информации из ПЗУ. Если сигнал rd равняется единице, то разрешается чтение информации, если же он равняется нулю – исходные линии переходят в Z-состояние (строка 32). Для доступа к конкретной ячейке в модуле памяти используется строка 30. Константа content имеет тип данных ячейки std_logic_vector, что отвечает типу исходного сигнала data_out. Сигнал адреса в модуле памяти также типа std_logic_vector, поэтому для адресации ячейки в массиве content необходимое преобразование типа std_logic_vector к типу integer, что выполняется с помощью конструкции to_integer (unsigned (address). В этой конструкции сначала сигнал типа std_logic_vector превращается к сигналу типа unsigned, а уже потом – к типу integer. Более наглядно о преобразовании типов тут.

1	library ieee; 2	use ieee.std_logic_1164.all; 3	use ieee.numeric_std.all;  4	entity ROM is 5	port (clk    : in  std_logic; 6	        cs      : in  std_logic; 7	        rd      : in  std_logic; 8	        address : in  std_logic_vector(4 downto 0); 9	        data_out: out std_logic_vector(7 downto 0)); 10	end ROM;  11	architecture behav of ROM is 12	type ROM_array is array (0 to 31)  13	of std_logic_vector(7 downto 0);  14	constant content: ROM_array := ( 15	          0 => "00000001",		 16	          1 => "00000010",		 17	          2 => "00000011",		 18	           . . .  19	         12 => "00001101",      		 20	         13 => "00001110",		 21	         14 => "00001111",		 22	         others => "11111111");        23	begin 24	process(clk, cs) 25	begin 26	      if(cs = '1' ) then 27	              data_out <= "ZZZZZZZZ"; 28 	      elsif (clk'event and clk = '1') then 29	             if rd = '1' then 30	                    data_out <= content(to_integer (unsigned (address))); 31	             else 32	                    data_out <= "ZZZZZZZZ"; 33	            end if; 34	    end if; 35	end process; 36	end behav; 

Результаты моделирования приведены на рисунке 2.

Рисунок 2 — Результаты моделирования блока памяти

Определение содержимого памяти с помощью оператора case.

При использовании оператора case необходимо объявить порты, а определение содержимого памяти происходит в архитектурном теле. Каждому значению адреса относится в соответствие содержимое этой ячейки памяти. Конструкция будет иметь такой вид:

when адрес  => исходный_порт <= содержимое_ячейки; 

Пример 2. В качестве примера рассмотрим описание модуля памяти объемом 256×6. Адрес описывается восьмиразрядным вектором типа std_logic, выход данных – шестиразрядным вектором типа std_logic. С помощью оператора case отдельно определяется содержимое первых десяти ячеек блока памяти, все другие определяются вместе с помощью операторов when others.

library ieee;  use ieee.std_logic_1164.all;  entity mem is port (      clock    : in  std_logic;      address  : in  std_logic_vector (7 downto 0);      data_out : out std_logic_vector (5 downto 0)); end mem;  architecture rtl of mem is begin process (clock) begin 	if rising_edge (clock) then 		case address is 			when "00000000" => data_out <= "000111"; 			when "00000001" => data_out <= "000110"; 			when "00000010" => data_out <= "000010"; 			when "00000011" => data_out <= "100000"; 			when "00000100" => data_out <= "100010"; 			when "00000101" => data_out <= "001110"; 			when "00000110" => data_out <= "111100"; 			when "00000111" => data_out <= "110111"; 			when "00001000" => data_out <= "111000"; 			when "00001001" => data_out <= "100110"; 			when others => data_out <= "101111"; 		end case; 	end if; end process; end rtl; 

Результаты моделирования блока памяти из примера 2 приведенные на рисунке 3.

Рисунок 3 — Моделирование блока памяти из примера 2

Определение содержимого памяти с помощью mif файла.

Этот вариант определения содержимого памяти работает лишь с продукцией компании Altera, но и разрешает очень быстро изменять содержимое памяти. Для описания необходимо подключить библиотеку атрибутов синтеза компании Altera altera_syn_attributes и использовать атрибут ram_init_file. По умолчанию библиотека находится в папке папка_quartus\libraries\vhdl\altera. Этот атрибут задает mif файл, который содержит информацию о содержимом памяти.
Для использования этого атрибута необходимо декларировать атрибут синтеза, как строчный тип:

attribute ram_init_file : string; 

Создать связь атрибута ram_init_file с сигналом, который описывает блок памяти. Значение атрибута должно совпадать с именем *.mif файла:

attribute ram_init_file of rom : signal is "mem.mif"; 

Пример 3. В примере рассматривается описание блока памяти объемом 256×8. Строки 1-4 описывают библиотеки и модули этих библиотек. Видно, что в строках 1 и 4 описывается библиотека атрибутов синтеза.
Строки 5-9 описывают интерфейсную часть модуля.
В строках 11 и 12 вводятся тип mem_t и сигнал rom этого типа, которые описывают модуль памяти.
В строке 13 задается атрибут ram_init_file типа строка, а в строке 14 этот атрибут связывает с сигналом rom и делается ссылка на файл mem.mif, в котором приведенное содержимое модуля памяти.
Использование модуля памяти описано в строке 19 и представляет собой лишь вывод на исходный порт содержимого ячейки, которая определяется адресным сигналом.

1	library ieee, altera; 2	use ieee.std_logic_1164.all; 3	use ieee.numeric_std.all; 4	use altera.altera_syn_attributes.all; 5	entity mem is 6		port (clk: in std_logic; 7			  addr: in natural range 0 to 255; 8			q: out std_logic_vector (7 downto 0)); 9	end entity; 10	architecture rtl of mem is 11		type mem_t is array (255 downto 0) of std_logic_vector(7 downto 0); 12		signal rom: mem_t; 13		attribute ram_init_file: string; 14		attribute ram_init_file of rom: signal is "mem.mif"; 15	begin 16		process(clk) 17		begin 18		if(rising_edge(clk)) then 19			q <= rom(addr); 20		end if; 21		end process; 22	end rtl; 

Содержимое mif файла определяется по помощи редактора mif файлов. Для приведенного примера окно с содержимым памяти показано на рисунке 4, а результаты модулирования — на рисунке 5.


Рисунок 4 — Содержимое модуля памяти


Рисунок 5 — Временные диаграммы работы модуля памяти из примера 3

Описание оперативных запоминающих устройств

Описание оперативных запоминающих устройств (ОЗУ) отличается от описания постоянных запоминающих устройств лишь тем, что в ОЗУ можно проводить запись.
В качестве примера рассмотрим синхронное ОЗУ. Его работа описывается следующей таблицей:

Wn_R CSn Do[3..0] Режим работы
0 0 ZZZZ Запись
1 0 Исходные данные Чтение
× 1 ZZZZ Сохранение информации

Как и в предыдущих примерах для работы с памятью определяем новый тип как массив, который имеет размеры необходимого модуля памяти.

library ieee;   use ieee.std_logic_1164.all;  use ieee.std_logic_unsigned.all; use ieee.numeric_std.all;  entity mem is   port (clk  : in  std_logic;          Wn_R : in  std_logic;         CSn  : in  std_logic;         addr : in  std_logic_vector(4 downto 0);           Di   : in  std_logic_vector(3 downto 0);           Do   : out std_logic_vector(3 downto 0));   end mem;    architecture syn of mem is     type ram_type is array (31 downto 0) of std_logic_vector (3 downto 0);     signal RAM : ram_type;   begin     process (clk, CSn)     begin       if CSn = '0'  then   		if (clk'event and clk = '1') then 			if (Wn_R = '0') then 				RAM(to_integer(unsigned(addr))) <= Di;  				Do <= "ZZZZ"; 			else Do <= RAM(to_integer(unsigned(addr)));   			end if;   		end if;	 	else 		Do <= "ZZZZ";     end if;     end process;   end syn; 

Результаты работы стимулятора модуля памяти изображены на рисунке 6. Здесь необходимо обратить внимание на то, что начальное содержимое всех ячеек памяти равняется нулю. Это видно во время чтения ячеек с номерами 6-8.

Рисунок 6 — Временные диаграммы работы ОЗУ

Более подробно в Quartus II Hahdbook любой версии. Например текущей — 13. Раздел 6 — Recommended HDL Coding Style.

ссылка на оригинал статьи http://habrahabr.ru/post/197750/