В данной статье показаны основные принципы описания модулей ПЗУ и ОЗУ на языке VHDL. Статья ориентирована на начинающих. Ее цель — дать общее понятие об описании модулей памяти на языке VHDL. Примеры и иллюстрации предены для пакета Quartus II v. 9.1. Предполагается, что читатель знает как создавать проект в пакете Quartus II, проводить его компиляцию и симуляцию.
Память на языке VHDL описывается как массив (
Например, для модуля памяти из 32 ячеек, каждая из которых содержит 8 бит, необходимо объявить массив, в котором содержится 32 вектора, каждый из которых является восьмиразрядным.
Дальше необходимо описать входы адреса, входы и выходы данных, управляющие сигналы. Тип портов данных должен совпадать с типом данных отдельной ячейки. Для приведенного выше примера – это
Тип данных для адреса –
Описание памяти лучше выполнять с помощью параметризованих модулей. Это разрешает повторно использовать написанный код. Ниже приведен пример параметризованного модуля размером 32×8. В примере для описания модуля памяти используется параметры
При описании постоянных запоминающих устройств содержимое ячеек необходимо определять при написании программы. Возможно использование нескольких вариантов определения содержимого памяти:
Из трех вариантов два первых могут быть реализованы на микросхемах ПЛИС любого производителя, а третий возможен лишь в пакете Quartus II.
При использовании этого варианту сначала необходимо объявить тип, который будет отвечать размеру блока памяти. Потом объявляется константа этого типа и определяется содержимое всех ячеек массива.
Например, объявим новый тип ROM, который представляет собой массив с 8 ячеек, каждая из которых имеет размер 8 бит. Потом определим константу Content типа ROM.
Для использования такой константы необходимо просто адресовать необходимую ячейку в массиве с помощью входных данных с линий адреса. Исходный порт данных должен иметь тот же самый тип, что и тип ячейки блока памяти. Для приведенного выше примера исходный порт
Пример 1. Рассмотрим пример полного описания блока памяти с использованием константы. Блок памяти, который отвечает этому описанию, показан на рисунке 1.
Рисунок 1 — Блок памяти, описанный в примере 1
Строки 13 и 14 объявляют тип массив из 32 ячеек, каждая из которых содержит 8 бит.
Строки с 15 по 23 задают значение ячеек массива. Отдельно определяются значения только для первых 16 ячеек – строки с 16 по 22. Все другие ячейки заполняются одинаковым значением «1111 1111» с помощью слова others – строка 23.
Работа модуля памяти описывается с помощью оператора процесса, в список инициализации которого входят сигналы
Строка 29 проверяет наличие переднего фронта тактового сигнала
Строки 30-35 описывают процесс чтения информации из ПЗУ. Если сигнал
Результаты моделирования приведены на рисунке 2.
Рисунок 2 — Результаты моделирования блока памяти
Определение содержимого памяти с помощью оператора
При использовании оператора case необходимо объявить порты, а определение содержимого памяти происходит в архитектурном теле. Каждому значению адреса относится в соответствие содержимое этой ячейки памяти. Конструкция будет иметь такой вид:
Пример 2. В качестве примера рассмотрим описание модуля памяти объемом 256×6. Адрес описывается восьмиразрядным вектором типа
Результаты моделирования блока памяти из примера 2 приведенные на рисунке 3.
Рисунок 3 — Моделирование блока памяти из примера 2
Этот вариант определения содержимого памяти работает лишь с продукцией компании Altera, но и разрешает очень быстро изменять содержимое памяти. Для описания необходимо подключить библиотеку атрибутов синтеза компании Altera
Для использования этого атрибута необходимо декларировать атрибут синтеза, как строчный тип:
Создать связь атрибута
Пример 3. В примере рассматривается описание блока памяти объемом 256×8. Строки 1-4 описывают библиотеки и модули этих библиотек. Видно, что в строках 1 и 4 описывается библиотека атрибутов синтеза.
Строки 5-9 описывают интерфейсную часть модуля.
В строках 11 и 12 вводятся тип
В строке 13 задается атрибут
Использование модуля памяти описано в строке 19 и представляет собой лишь вывод на исходный порт содержимого ячейки, которая определяется адресным сигналом.
Содержимое mif файла определяется по помощи редактора mif файлов. Для приведенного примера окно с содержимым памяти показано на рисунке 4, а результаты модулирования — на рисунке 5.
Рисунок 4 — Содержимое модуля памяти
Рисунок 5 — Временные диаграммы работы модуля памяти из примера 3
Описание оперативных запоминающих устройств (ОЗУ) отличается от описания постоянных запоминающих устройств лишь тем, что в ОЗУ можно проводить запись.
В качестве примера рассмотрим синхронное ОЗУ. Его работа описывается следующей таблицей:
Как и в предыдущих примерах для работы с памятью определяем новый тип как массив, который имеет размеры необходимого модуля памяти.
Результаты работы стимулятора модуля памяти изображены на рисунке 6. Здесь необходимо обратить внимание на то, что начальное содержимое всех ячеек памяти равняется нулю. Это видно во время чтения ячеек с номерами 6-8.
Рисунок 6 — Временные диаграммы работы ОЗУ
Более подробно в Quartus II Hahdbook любой версии. Например текущей — 13. Раздел 6 — Recommended HDL Coding Style.
Общие положения
Память на языке 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
. По умолчанию библиотека находится в папке папка_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.