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

в 9:53, , рубрики: fpga, 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.

Описание блоков памяти на языке VHDL
Рисунок 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.
Описание блоков памяти на языке VHDL
Рисунок 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.
Описание блоков памяти на языке VHDL
Рисунок 3 — Моделирование блока памяти из примера 2

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

Этот вариант определения содержимого памяти работает лишь с продукцией компании Altera, но и разрешает очень быстро изменять содержимое памяти. Для описания необходимо подключить библиотеку атрибутов синтеза компании Altera altera_syn_attributes и использовать атрибут ram_init_file. По умолчанию библиотека находится в папке папка_quartuslibrariesvhdlaltera. Этот атрибут задает 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.

Описание блоков памяти на языке VHDL
Рисунок 4 — Содержимое модуля памяти

Описание блоков памяти на языке VHDL
Рисунок 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.
Описание блоков памяти на языке VHDL
Рисунок 6 — Временные диаграммы работы ОЗУ

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

Автор: canny

Источник

Поделиться

* - обязательные к заполнению поля