В данной статье показаны основные принципы описания модулей ПЗУ и ОЗУ на языке 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
При описании постоянных запоминающих устройств содержимое ячеек необходимо определять при написании программы. Возможно использование нескольких вариантов определения содержимого памяти:
- создание константы или сигнала типа «массив»;
- использование оператора
case
; - использование *.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
. По умолчанию библиотека находится в папке папка_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.
Рисунок 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.
Автор: canny