Подпрограммы
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_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
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
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
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;
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;
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". Данные загружаются последовательно, начиная с указанного адреса.
Используется только шестнадцатеричный формат для представления адреса. Файл может
содержать несколько начальных адресов, что позволяет загружать в память несколько
подблоков.
7
6.4. Разработать функцию, вычисляющую скалярное произведение двух векторов A и B
по формуле . Например,
A[3:1] = (1, 2, 3); B[3:1] = (4, 5, 6);