Общие положения
Память на языке 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.
ссылка на оригинал статьи http://habrahabr.ru/post/197750/
Добавить комментарий