Назад в библиотеку

Глава 16 книги "The Designer guide to HTML" под названием "Файловый ввод\вывод".

Автор: Peter J. Ashenden, Jim Lewis

Автор перевода: Ю.Ю. Морозов

Источник: The Designer guide to VHDL, Third Edition (Systems on Silicon) by Peter J. Ashenden, 936p., 2008


В этой главе мы рассмотрим средства в VHDL для файлового ввода\вывода. Файлы служат для целого ряда целей, одним из которых является, обеспечение хранения долгосрочной информации. В этом контексте "долгосрочная информация" означает информацию вне жизни одного запуска моделирования. Файлы могут быть использованы для хранения данных, которые будут загружены в модели при запуске или для хранения результатов, полученных с помощью моделирования. VHDL также предоставляет специализированные версии файловых операций для работы с текстовыми файлами. Мы покажем, как текстовый ввод и вывод может быть использован для расширения пользовательского интерфейса симулятора со специфичными для модели операциями.

16.1 Файлы

Мы начинаем наше обсуждение файлов с рассмотрения механизмов общего назначения, предусмотренных в VHDL для файлового ввода\вывода. VHDL обеспечивает последовательный доступ к файлам с помощью операций, таких, как "open", "close", "read" и "write", которые являются привычными для пользователей традиционных языков программирования.

16.1.1 Объявление файла

VHDL файл является классом объектов использующихся для хранения данных. Таким образом, как и с другими классами объектов, мы должны добавить определение файла в модель. Синтаксис для определения типа файла следующий:

file_type_definition ? file of type_mark

Определение типа файла просто указывает тип объекта, который может храниться в файле данного типа. Например, объявление типа:

type integer_file is file of integer;

определяет integer_file указывающий на тип файла, который может хранить только целые числа. Файл может содержать только один тип объекта, но таким типом может быть почти любой тип VHDL, включая типы скалярных записей и одномерных массивов. Единственные типы, которые не могут быть сохранены в файл, являются многомерные массивы, типы доступа, типы защиты и другие файлы.

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

file_declaration ?

file identifier {,…} : subtype_indication

[[open file_open_kind_expr] is string_expression];

Определение файла создает один или несколько объектов данного типа файла. Мы можем добавить декларацию файла в любой декларативной части, в которой мы можем создать объекты, такие как: описание архитектуры, блоки, процессы, пакеты и подпрограммы.

Необязательные части декларации файла позволяют сделать связь между файловым объектом и физическим файлом в файловой системе. Если мы добавим это объявление, файл автоматически открывается для доступа во время моделирования. Строка после ключевого слова представляет собой логическое имя файла, которое идентифицирует файл в операционной системе для доступа. Так как различные операционные системы используют различные форматы для именования файлов, многие симуляторы обеспечивают некоторую форму отображения между логическими строками имен, которые мы включаем в наших моделях и имен файлов, используемых в файловой системе. Например, если мы объявим файл как

file lookup_table_file : integer_file is "lookup-values";

симулятор, работающий под управлением операционной системы UNIX, может связать файловый объект с физическим файлом с именем "lookup-values" в текущем рабочем каталоге. Другой симулятор, работающий под операционной системой Windows, может связать файловый объект по-разному, так как имена файлов, как правило, включают в себя расширение файла в данной операционной системе. Так что, можно ассоциировать объект с физическим файлом под названием "lookup-values.dat" в текущем рабочем каталоге.

Опциональное выражение после открытия ключевых слов позволяет нам определить, как физический файл, связанный с объектом файловой системы должен быть открыт. Это выражение должно иметь значение предопределенного типа file_open_kind, заявленным в стандартном пакете. Определение имеет вид:

type file_open_kind is (read_mode, write_mode, append_mode);

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

VHDL-87. Синтаксис определения файла для VHDL-87:
file_declaration ?
file identifier : subtype_indication is
[in or out] string_expression ;

VHDL-87 не предусматривает предопределенный тип file_open_kind. Вместо этого, ключевые слова in и out используются в описании файла для открытия для чтения или записи соответственно. По умолчанию файл открывается в режиме чтения. Обратите внимание, что синтаксис определения файла VHDL-87 не является подмножеством синтаксиса VHDL-93 и VHDL-2002. Если модель включает в себя один из ключевых слов in или out, она не может быть успешно проанализирована с помощью анализатора VHDL-93 или VHDL-2002.

16.1.2 Чтения из файла

Если файл открыт в режиме чтения, то последовательные элементы данных считываются из файла, используя операцию read. Чтение начинается с первого элемента в файле, и каждый раз указатель в файле переходит к следующему элементу. Мы можем использовать операцию endfile чтобы определить последний считанный элемент в файле. Объявление, учитывающие тип файла, выглядит следующим образом:
type file_type is file of element_type;

Операции read и endfile описываются так:

procedure read (file f : file_type; value : out element_type);
function endfile (file f : file_type) return boolean;

Далее в этой главе мы рассмотрим все параметры файлов.

Пример 16.1 Инициализация содержимого ПЗУ из файла

Мы можем использовать файловые операции для инициализации содержимого ПЗУ (ROM) из файла. Ниже приводится определение объекта для ROM, который содержит общую константу, указывающую имя файла, из которого должно загрузиться содержимое ROM.
library ieee; use ieee.std_logic_1164.all;
entity ROM is
generic (load_file_name : string);
port (sel : in std_ulogic;
address : in std_ulogic_vector;
data : inout std_ulogic_vector);
end entity ROM;

Тело архитектуры для ПЗУ, показанного ниже, использует имя файла в объявлении файла, для создания объекта файла, связанного с физическим файлом данных. Процесс, который реализует поведение ROM загружает массив данных в ROM, читая последовательные слова данных из файла, используя endfile, когда нужно остановиться.
architecture behavioral of ROM is
begin
behavior : process is
subtype word is std_ulogic_vector(0 to data'length - 1);
type storage_array is
array (natural range 0 to 2**address'length - 1) of word;
variable storage : storage_array;
variable index : natural;
... -- other declarations
type load_file_type is file of word;
file load_file : load_file_type
open read_mode is load_file_name;
begin
-- load ROM contents from load_file
index := 0;
while not endfile(load_file) loop

read(load_file, storage(index));
index := index + 1;
end loop;
-- respond to ROM accesses
loop
...
end loop;
end process behavior;
end architecture behavioral;

В приведенном выше примере, каждый элемент файла является standard-logic вектором фиксированной длины, определяющийся шириной порта данных ROM. Тем не менее, мы не ограничены массивом фиксированной длинны в качестве элементов файлов. Мы можем объявить тип файла без всяких ограничений или частично ограниченного типа массива для типа элемента, при условии, что тип элемента массива является скаляром типа или полностью ограничен композитным подтипом, например:
type bit_vector_file is file of bit_vector;

Данные в файле этого типа представляют собой последовательность битовых векторов, каждый из которых может быть различной длины. Для такого файла, операция чтения занимает несколько иную форму, из-за того что мы не знаем длину следующего элемента, пока мы его не считали. Операция неявно объявляется как
procedure read (file f : file_type;
value : out element_type; length : out natural);

Когда мы вызываем такую операцию для чтения, мы должны передать через переменную достаточно большой массив, чтобы получить значение, которое ожидается для чтения, и еще одну переменную, чтобы получить фактическую длину прочитанного значения. Например, если мы сделаем следующие объявления:
file vectors : bit_vector_file open read_mode is"
vectors.dat";
variable next_vector : bit_vector(63 downto 0);
variable actual_len : natural;

мы можем вызвать операцию чтения следующим образом:
read(vectors, next_vector, actual_len);

Это позволяет нам считать разрядный вектор длинной до 64 бит. Если следующее значение в файле меньше или равно 64 бит, то это значение будет помещено в крайнюю левую часть next_vector, а оставшиеся биты будут неизменны. Если значение в файле длиннее, чем 64 бит, первые 64 бита значения помещаются в next_vector, а оставшиеся биты отбрасываются. В обоих случаях actual_len установлено в значение фактической длины файла, будь оно короче или длиннее, чем длина второго аргумента для чтения. Это позволяет нам проверить, была ли утрачена информация. Если выражение
actual_len > next_vector'length

истинно, то вектор недостаточной длинны, чтобы вместить все биты.

Пример 16.2 Чтение значений из файла

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


stimulate_network : process is
type packet_file is file of bit_vector;
file stimulus_file : packet_file
open read_mode is "test packets";
variable packet : bit_vector(1 to 2048);
variable packet_length : natural;
begin
while not endfile(stimulus_file) loop
read(stimulus_file, packet, packet_length);
if packet_length > packet'length then
report "stimulus packet too long - ignored"
severity warning;
else
for bit_index in 1 to packet_length loop
wait until stimulus_clock;
stimulus_network <= not stimulus_network;
wait until not stimulus_clock;
stimulus_network <= stimulus_network
xor packet(bit_index);
end loop;
end if;
end loop;
wait; -- end of stimulation: wait forever
end process stimulate_network;

Процесс объявляет файловый объект, stimulus_file, содержащий битовые векторы переменной длины. Каждый элемент файла считывается в пакете с битовыми-векторами длиной битового вектора считываемого из файла, хранящегося в packet_length. Если битовый вектор в файле длиннее размера битового вектора, процесс сообщает об этом, и игнорирует этот пакет. В противном случае значение в packet_length используется для определения того, сколько битов из пакета должно быть использовано в качестве битов данных для симуляции сети.

16.1.3 Запись в файл

Если файл открыт в режиме записи, то в файловой системе создается новый пустой файл, и последовательные элементы данных добавляются с помощью операции write. Для каждого типа файлов, операция записи неявно объявляется как:

procedure write (file f : file_type; value : in element_type);

Один из вариантов использования файлов, это сохранение информации, собранной измерительным кодом. Когда моделирование завершено, или по какому-то другому условию окончило работу, можно использовать запись данных в файл для последующего её анализа.

Пример 16.3 Запись частоты CPU

Когда мы разрабатываем новый набор инструкций для CPU, полезно знать, как часто каждая команда используется в различных программах. Мы измеряем это, имитируя программу и имея CPU, можем вести учет, как часто он выполняет каждую команду. Когда программный код завершает работу (например, по достижении инструкции завершения работы), он пишет накопленные счетчики в файл.

Тело архитектуры для процессора, показанной ниже, иллюстрирует этот подход. Он содержит файл, instruction_counts, открытый в режиме записи. Существует также процесс, интерпритатор, который получает и интерпретирует инструкции. Он содержит массив счетчиков с проиндексированными значениями кодов операций. Поскольку интерпретатор команд декодирует каждую команду, он увеличивает соответствующий счетчик. Когда все инструкции выполнены, интерпретатор останавливает выполнение и записывает значения счетчика в виде последовательных элементов в файле instruction_counts.
architecture instrumented of CPU is
type count_file is file of natural;
file instruction_counts : count_file
open write_mode is "instructions";
begin
interpreter : process is
variable IR : word;
alias opcode : byte is IR(0 to 7);
variable opcode_number : natural;
type counter_array is
array (0 to 2**opcode'length - 1) of natural;
variable counters : counter_array := (others => 0);
...
begin
... -- initialize the instruction set interpreter
instruction_loop : loop
... -- fetch the next instruction into IR
-- decode the instruction
opcode_number := convert_to_natural(opcode);
counters(opcode_number) := counters(opcode_number)+1;
...
-- execute the decoded instruction
case opcode is
...
when halt_opcode => exit instruction_loop;
...
end case;
end loop instruction_loop;
for index in counters'range loop
write(instruction_counts, counters(index));
end loop;
wait; -- program finished, wait forever
end process interpreter;
end architecture instrumented;

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