Вы находитесь на странице: 1из 8

7.

Подпрограммы
6.1. Подпрограммы: Task и Function
Как и в любом другом языке программирования подпрограммы позволяют разбивать
большой код на отдельные фрагменты. Verilog располагает двумя типами подпрограмм:
задачи (task) и функции (function). Основная цель функции – сформировать значение,
которое может быть использовано в выражении. Она всегда возвращает одно значение и
должна иметь хотя бы один входной аргумент. В задаче допускается генерирование
значений нескольких выходных переменных или вообще ни одного, она может не иметь и
входных аргументов. Задачи допускают применение задержек, временного и событийного
управления, функции – нет, поэтому в функциях не должно быть операторов,
начинающихся с символов #, @ или wait. Для декларации аргументов в подпрограммах
используются такие же ключевые слова, как и для описания портов модулей. Синтаксис:
//Синтаксис декларации функции
// Старый стиль
function [ automatic ] [ signed ] [ range_or_type ] identifier ;
function_item_declaration { function_item_declaration }
function_statement
endfunction

// Новый стиль
function [automatic] [signed] [range_or_type] identifier (function_port_list ) ;
block_item_declaration { block_item_declaration }
function_statement
endfunction

// вызов функции
function_identifier ( expression { , expression } )

//Синтаксис описания задачи


// Старый стиль
task [ automatic ] task_identifier ;
{ task_item_declaration }
statement
endtask

// Новый стиль
task [ automatic ] task_identifier ( task_port_list ) ;
{ block_item_declaration }
statement
endtask

task identifier;
parameter_declaration;
input_declaration;
output_declaration;
inout_declaration;
register_declaration;
event_declaration;
statement;
endtask

// Вызов задачи
task_identifier [ ( expression { , expression } ) ] ;

Параметр identifier описывает имя функции или задачи. Для функции – это также
имя возвращаемого значения, для которого параметром range_of_type задается тип и

1
размер. Если последний пропущен, то по умолчанию функция возвращает однобитовое
значение типа reg. Можно также присвоить возвращаемому значению тип: integer, real,
realtime или time. Аргументы задач, объявленные как inout и out, могут использоваться
только в процедурных операторах и должны быть: переменной регистрового класса (reg,
integer, real, realtime или time), памятью, конкатенацией регистров переменных памяти,
элементом или диапазоном регистрового вектора.
Входые аргументы функции могут быть описаны одним из двух способов. В первом
случае после имени функции ставится точка с запятой. После этого следует описание
одного или нескольких входных портов (старый стиль). Во втором случае описание входов
записывается в круглых скобках, через запятую после имени функции (новый стиль).
Пример определяет функцию getbyte, с использованием первого (листинг 6.1 а)) или
второго (листинг 6.1 б)) способа описания входных параметров. Аналогичным образом
определяются входные параметры и для зада, что иллюстрируется листингом на примере
задачи my_task, которая имеет пять аргументов: два входных, два выходных и один
двунаправленный.
Листинг 6.1. Примеры описания входных параметров функции.
а)//Старый стиль
function [7:0] getbyte;
input [15:0] address;
begin
...
getbyte = result_expression;
end
endfunction

б) //Новый стиль
function [7:0] getbyte (input [15:0] address);
begin
...
getbyte = result_expression;
end
endfunction
Листинг 6.2. Примеры описания входных параметров задач
а) //Старый стиль
task my_task;
input a, b;
inout c;
output d, e;
begin
. . . // операторы, определяющие работу задачи
c = foo1; // присвоение результирующих значений
d = foo2;
e = foo3;
end
endtask
б)// Новый стиль
task my_task (input a, b, inout c, output d, e);
begin
. . . // операторы, определяющие работу задачи. . .
c = foo1; // присвоение результирующих значений
d = foo2;
e = foo3;
end
endtask

Оба вида подпрограмм определяются в модуле и являются локальными для него. В


табл. 6.1 приведена сравнительная характеристика свойств функций и задач.
Подпрограммы могут иметь локальные переменные, но не допускается использование
переменных класса цепи. Реализуются они только с помощью поведенческих операторов.
2
Однако они не могут включать в себя конструкции always и initial, но способны вызываться
из них или других подпрограмм (функции не могут вызывать задачи).
Таблица 6.1 Свойства функций и задач

Листинг 6.3 представляет собой пример использования в задаче параметров, событий,


входных, выходных и двунаправленных аргументов. При вызове задачи первый аргумент
должен быть значением типа integer, второй – 4-разрядной переменной типа reg, третий –
однобитовой переменной типа reg. Событие e, определенное в задаче first_task, может быть
доступно, через иерархическое имя first_task.e.
Листинг 6.4 содержит пример создания функции для вычисления вещественных
значений. Еще один пример (листинг 6.5) иллюстрирует использование функций для
создания модели светофора. Функция управляет временем между переключениями цветов
светофора.
Листинг 6.3 Пример создания и использования задачи
task first_task;
parameter size=4;
input a; integer a; // Входной аргумент
inout [size-1:0] b; // Двунаправленный аргумент
output c; // Выходной аргумент
reg [size-1:0] d; // Внутренняя переменная
event e; // Декларация события
begin
d = b;
c = |d;
b = ~b;
if (!a) -> e;
end
endtask
...
integer x;
reg a, b, y;
reg [3:0] z;
reg [7:0] w;
...
first_task(x, z, y); // x – переменная integer, z – 4-разрядный вектор,
// y – 1-битовый reg
first_task(x, w[7:4], w[1]); // x – переменная integer, 4-разрядный диапазон
// вектора w, w[1] – 1-битовый reg
first_task(1,{a,b,w[3], x[0]}, y);
// константа типа integer, оператор конкатенации
// формирует– 4-разрядный вектор, x[0 y – 1-битовый reg

always @(first_task.e) // Использования именного события из задачи


count= count+1;

3
Листинг 6.4 Пример использования функции
function real multiply;
input a, b;
real a, b;
multiply = ((1.2 * a) * (b * 0.17)) * 5.1;
endfunction
//...
real a;
initial begin
a = multiply(1.5, a);
end

Листинг 6.5. Использование задачи для модели светофора


module traffic_lights;
reg clock, red, amber, green;
parameter on = 1, off = 0, red_tics = 350;
parameter amber_tics = 30, green_tics = 200;
// Инициализация значений цвета
initial red = off;
initial amber = off;
initial green = off;
always begin // последовательность управления светофором
red = on; // включение красного цвета
light(red, red_tics); // ожидание
green = on; // включение зеленого цвета
light(green, green_tics); // ожидание
amber = on; // включение желтого цвета
light(amber, amber_tics); // ожидание
end
// Формируется задержка на tics положительных фронтов синхросигнала
// до включения света color
task light;
output color;
input [31:0] tics;
begin
repeat (tics) @ (posedge clock);
color = off; // выключение света
end
endtask
always begin // формирование сигнала синхронизации
#100 clock = 0;
#100 clock = 1;
end
endmodule

6.2 Автоматические функции и задачи


Задачи и функции без ключевого слова automatic реализуются как статические; все
элементы таких подпрограмм объявляются статическими и являются общими для всех
вызовов таких задач и функций.
В автоматических задачах и функция (объявленных с ключевым словом automatic) все
элементы будут динамическими и создаются при каждом вызове подрограммы.
Автоматические функции позволяют реализовывать рекурсию. Элементы автоматических
функций и задач не могут быть доступны через иерархическое имя.
Для автоматических задач не разрешается: 1) использование неблокирующих
операторов; 2) непрерывные процедурные операторы assign и force; 3) внутриоператорный
контроль; 4) сканирование объектов с помощью системной задачи $monitor.
Листинг 6.6 представляет пример использования автоматической функции factorial,
вычисляющей факториал с возвратом значения в 32-битовом регистре.
Листинг 6.6 Пример автоматической функции

4
module tryfact;
// Декларация функции
function automatic [31:0] factorial;
input [3:0] operand;
reg [3:0] i;
begin
factorial = 1;
for (i = 2; i <= operand; i = i + 1)
factorial = i * factorial;
end
endfunction

//использование функции
integer result;
integer n;
initial
begin
for (n = 0; n <= 7; n = n+1)
begin
result = factorial(n);
$display(“%0d factorial=%0d”, n, result);
end
end
endmodule

// результаты выполнения системной задачи $display


0 factorial=1
1 factorial=1
2 factorial=2
3 factorial=6
4 factorial=24
5 factorial=120
6 factorial=720
7 factorial=5040

6.3. Системные задачи для работы с файлами


6.3.1 Запись информации в файл.
Verilog предлагает возможность работы с файлами с помощью системных задач.
Открыть и закрыть файл можно командами $fopen и $fclose. Открытый файл используется
для задач вывода результатов и мониторинга: $fdisplay и $fmonitor, соответственно.
Синтаксис:
integer $fopen("filename");
$fclose(multi_channel_descriptor);

file_output_task_names(multi_channel_decriptor, list_of_arguments);
file_output_task_name ::=
$fdisplay | $fdisplayb | $fdisplayh | $fdisplayo |
$fwrite | $ fwriteb | $fwriteh | $fwriteo |
$fstrobe | $fstrobeb | $fstrobeh | $fstrobeo|
$fmonitor | $fmonitorb | $fmonitorh | $fmonitoro
Функция $fopen возвращает 32-битовый дескриптор файла multi_channel_descriptor,
имеющий целочисленный тип. Если файл невозможно открыть для записи, то функции
$fopen возвращает 0.
Для тестирования сумматора ha_1 разработан testbench (листинг 6.5). Результат
моделирования записывается в файл ha_f_rslt.txt. Файл открывается и связывается с
именем “info”. Позже задача $fmonitor записывает значения описанных переменных в
открытый файл. При завершении моделирования файл закрывается автоматически.
Можно закрыть файл автоматически через процедуру $close(info)
Листинг 6.5. Пример вывода результатов мониторинга в файл
`timescale 1ns/1ps
module ha_1

5
(input a,b,
output s, ca);
xor #(1,2) (s, a, b);
and #(3,4) (ca, a, b);
endmodule

//test-bench
module tstha_f();
integer info;
reg a,b;
wire s,ca;

ha_1 hh(.s(s), .ca(ca), .a(a), .b(b));


initial begin
a = 0; b = 0;
info = $fopen("ha_f_rslt.txt");
end
always begin
#5 a = 1; b = 0;
#5 a = 0; b = 1;
#5 a = 1; b = 1;
#5 a = 0; b = 0;
end
initial
$fmonitor(info, $time, "a = %b, b = %b, out carry = %b,
outsum = %b", a, b, ca, s);
initial begin
#30 $display(info);
#0 $stop;
end
endmodule

Результаты мониторинга, полученные при моделировании полусумматора (см. листинг


1) и записанные в файл ha_f_rslt.txt.
0a = 0, b = 0, out carry = x, outsum = x
2a = 0, b = 0, out carry = x, outsum = 0
4a = 0, b = 0, out carry = 0, outsum = 0
5a = 1, b = 0, out carry = 0, outsum = 0
6a = 1, b = 0, out carry = 0, outsum = 1
10a = 0, b = 1, out carry = 0, outsum = 1
15a = 1, b = 1, out carry = 0, outsum = 1
17a = 1, b = 1, out carry = 0, outsum = 0
18a = 1, b = 1, out carry = 1, outsum = 0
20a = 0, b = 0, out carry = 1, outsum = 0
24a = 0, b = 0, out carry = 0, outsum = 0
25a = 1, b = 0, out carry = 0, outsum = 0
26a = 1, b = 0, out carry = 0, outsum = 1
6.3.2. Ввод информации в память
Единственной формой ввода информации из файла, поддерживаемой IEEE 1364
Verilog HDL, являются системные задачи $readmemb и $reademh, считывающие
информацию прямо в память. Задачи $readmemb и $reademh работают с двоичным и
шестнадцатеричным форматом представления данных, соответственно. Синтаксис
$readmemh("filename", memname, [,start_addr, end_addr]]);

$readmemb("filename", memname, [,start_addr, end_addr]]);

где filename – строковое имя файла; memname – описывает Verilog-идентификатор


памяти для загрузки данных; start_addr необязательный начальный адрес для записи
данных. Если не указан, то используется левый адрес памяти. Если указан end_addr, то
занесение данных в память заканчивается в этой позиции.

6
Формат данных в файле может быть двоичными или шестнадцатеричным. Длина и
система счисления не указывается. Числа разделяются пробелами. Разрешается
использовать Verilog- комментарии.
Листинг 6.6 представляет типичный пример использования функции $readmemh для
считывания тестовых последовательностей из файла mem.dat для верификации
полусумматора ha_1.
Листинг 6.6. Пример чтения данных из файла с помощью $readmemh
module test_ha_1;
reg [1:0] mem [1:16];
integer i;
reg a, b;
wire s,ca;

ha_1 hh(.s(s), .ca(ca), .a(a), .b(b));

initial
$readmemh("mem.dat", mem);

initial begin
$monitor("time=%d a = %b, b = %b, out carry = %b,
outsum = %b", $time, a, b, ca, s);
for(i=1; i<=16; i = i+1)
#5 {a, b} = mem[i];
end
endmodule

Начальный адрес для записи информации в память может быть также указан в файле в
формате "@hhh...h". Данные загружаются последовательно, начиная с указанного адреса.
Используется только шестнадцатеричный формат для представления адреса. Файл может
содержать несколько начальных адресов, что позволяет загружать в память несколько
подблоков.

6.4. Контрольные вопросы и задания


6.1. Создать функцию, вычисляющую факториал 4-битового числа. Результат
32-битовое число. Написать тестовый модуль для вызова функции и проверки результата ее
работы.
6.2. Создать функцию, умножающую два 4-битовых числа и формирующую 32-битовый
результат. Написать тестовый модуль для вызова функции и проверки результата ее
работы.
6.3. Создать функцию, моделирующую восьмиоперационное АЛУ. Функция принимает
3-битовый код операции, два 4-разрядных операнда, а возвращает 5-битовый результат.
Написать тестовый модуль для вызова функции и проверки результата ее работы.

7
6.4. Разработать функцию, вычисляющую скалярное произведение двух векторов A и B

по формуле . Например,
A[3:1] = (1, 2, 3); B[3:1] = (4, 5, 6);

Вызовфункции должен иметь вид DOT(A,B), где A и B – массивы жлементов типа


integer. Результат вычисления, возвращаемой функцией, также имеет тип integer.
6.5. Для функции из задания 6.4 разработать testbench, в котором определены два
массива A и B, используемые в качестве тестовых последовательностей. Массивы
инициализируютя значениями из файла “input_data.dat”, информация в котором
представлена в шестнадцатеричной форме.
6.6. Создать задачу, вычисляющую факториал 4-битового числа. Результат 32-битовое
число назначается после задержки в 10 единиц времени. Написать тестовый модуль для
вызова задачи и проверки результата работы.
6.7. Создать задачу, выполняющую проверку на четность 16-битового числа. Результат
– 31-битовое число. На него поступает значение после трех положительных фронтов
синхронизации. Использовать оператор repeat.
6.8. а) Написать задачу, вычисляющую сумму квадратов входных значений A и B.
Входные значения имеют тип real. Задача возвращает значение через вещественную
переменную sum_of_squares.
б) Разработать testbench для тестирования работы задачи из п. а). Тестовые значения
формировать с помощьюсистемной функции $random. Результаты моделирования
выводить в текстовый файл в формате:
A = значение B = значение sum_of_squares = значение.
Например,
A = 3 B = 2 sum_of_squares = 13.

6.9. Написать задачу, генерирующую импульсы синхросигнала syn_clk длиной 1 ед.


времени с периодом в 20 ед. времени. Когда сигнал reset = 1, то задача переключает syn_clk
в 0 и возвращает управление.

Вам также может понравиться