Выбери формат для чтения
Загружаем конспект в формате docx
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Подключение модулей
Проект, как правило, содержит несколько модулей. При этом из отдельного модуля (верхнего уровня) можно обратиться к другому модулю (нижнего уровня). Для этого используется процедура подключения модуля, которая описывает порядок вхождения в модуль нижнего уровня:
<имя модуля> <имя экземпляра модуля> (подключение портов)**;
Для пояснения рассмотрим пример.
Примеры Подкл.мод. 1, 2.
Описать асинхронный двоичный суммирующий счетчик, используя T триггеры. Для этого:
a) создать отдельный модуль, описывающий T триггер
b) создать модуль счетчика, в котором подключить 4 экземпляра T триггера для получения счётчика.
Решение задачи можно разбить на два этапа.
1 этап
Сначала создадим базовый модуль - T триггер, схема которого представлена на рисунке.
Базовая операция Т- триггера:
q = (~q&t)|(q&~t) //формула описания T-триггера
или в дискретном времени:
qn+1=(~ qn &t)|( qn &~t)
Алгоритм работы T-триггера при фиксации значения t=1:
каждое последующее выходное состояние qn+1 определяется значением выходного сигнала в предыдущем состоянии. Срабатывание триггера происходит по положительному фронту тактового сигнала clk, при этом триггер опрокидывается в противоположное состояние.
Программное описание триггера .
module T_trigger (t, clk, q); // T_trigger t – имя Т-триггера.
input clk, t;
output reg q;
initial q = 0;
always @ (posedge clk) //срабатывает по каждому положительному фронту сигнала clk
begin
q = (~q&t)|(q&~t); // базовая операция Т- триггера: при появлении “1” на входе clk происходит опрокидывание триггера в противоположное состояние.
end
endmodule
2 этап.
Для создания четырехразрядного счетчика базовый модуль будем вызывать 4 раза в верхний модуль. Логика работы счетчика поясняется рисунком.
Все триггеры соединены последовательно. При этом инвертирующий выход отдельного (предыдущего) Т-триггера подключается к входу следующего триггера. Одновременно состояние выхода i-го триггера является значением i-го разряда счетчика.
В результате можно описать процедуру работы или программу описания модуля счётчика.
module counter (clk, x); // объявление модуля верхнего уровня - счетчика
input clk;
output [3:0] x;
T_trigger reg1 (1`b1, clk, x[0]); // позиционное подключение модуля нижнего уровня повторяющееся 4-е раза
T_trigger reg2 (1`b1, ~x[0], x[1]);
T_trigger reg3 (1`b1, ~x[1], x[2]);
T_trigger reg4 (1`b1, ~x[2], x[3]);
endmodule
Подключение портов может быть 2х типов:
1. позиционное;
2. именное.
В рассмотренном примере представлено позиционное подключение. При этом переменная в круглых скобках используется (вычисляется) также как переменная, находящаяся на той же позиции в базовом блоке.
В тексте модуля верхнего уровня мы вызываем модуль нижнего уровня (4 раза). Сначала мы пишем имя базового модуля T_trigger, а потом идёт указание на <имя экземпляра модуля> reg1 или reg2, reg3,... Фактически это указание на значение i-го разряда. Затем идёт позиционное подключение – определяется выражением в круглых скобках, где на позициях имен формальных переменных, соответствующих базовому модулю T_trigger, мы называем имена фактические переменных, соответствующих некоторому разряду счетчика reg3. В результате имеем запись T_trigger reg2 (1`b1, ~x[0], x[1]);
Входная переменная t модуля reg2 имеет постоянное значение 1`b1, в качестве второй входной переменной указывается инвертированное значение нулевого разряда предыдущего триггера счетчика ~x[0] , который в соответствии с функциональной схемой подается на вход модуля reg2. Выходом модуля reg2 является значение первого разряда счетчика x[1].
Таким образом, на позиции формальных переменных записываются имена фактических переменных, используемых в модуле верхнего уровня, т.е. в счетчике.
Поясним случай именного подключения.
При именном подключении в круглых скобках сначала ставится точка, потом формальная переменная базового модуля (модуля нижнего уровня) и в круглых скобках фактическое имя переменной, используемой таким же образом в модуле верхнего уровня – счетчике. В итоге имеем.
module counter (clk, x); // объявление модуля верхнего уровня - счетчика
input clk;
output [3:0] x;
T_trigger reg1 (.t(1`b1), .clk(clk), .q(x[0]))
T_trigger reg2 (.t(1`b1), .clk(~x[0]), .q(x[1]));
T_trigger reg3 (.t(1`b1), .clk(~x[1]), .q(x[2]));
T_trigger reg4 (.t(1`b1), .clk(~x[2]), .q(x[3]));
endmodule
Часто бывает удобнее пользоваться позиционным подключением.
Операторы Функция и Задача
Основное значение - это использовать их как подпрограммы. Реально это подпрограммы, которые используется только внутри модуля для вычисления многократно повторяющихся процедур.
Функции
Функция содержит один или несколько входов и не имеет выходов.
Все функции выполняются в нулевой момент модельного времени.
Разница между функциями и задачами – функции не могут запускать задачи, в то время как задачи могут запускать и функции, и сами себя и другие задачи.
function <разрядность> <название функции> (описание входов функции);
/* либо иной вариант этой строчки:
function <разрядность> <название функции>;
<описание входов функции>; */
begin //организация тела фукнции
<переменная> = <результат вычисления функции>
end
endfunction
Определение входных портов
1. После названия функции следует точка с запятой, после чего идёт перечисление входных портов. Например
function [7:0] getbyte ;
input [15: 0] address;
input parit;
begin
getbyte = #parity address;
end
endfunction
Вычисление функции в тексте программы производится тогда, когда встречается имя этой функции. Допустим, был какой-то внешний модуль
module orient
…
m = getbyte;
function [7:0] …..
begin
getbyte =
end
endfunction
Присутствуют только входы, выходов нет.
Задачи
Задачи могут содержать входы и выходы и вызывать другие задачи.
task <имя задачи>;
input <> ,<>// описание входов и выходов
output <> ,<>
begin //тело задачи
c = #5 a;
d = a + b;
end
endtask
При вызове нужно просто написать имя функции (или задачи).
Если задача, то нужно указать входные параметры (значения) и задача решается - Sum (2`b10, 2`b11)
Несколько примеров:
Пример Функции 8.
Создать модуль, вычисляющий выражение z = (a + b)*c
a, b, c – входы, z – выход. Входы имеют разрядность 8, выход – 16.
module zd8 (a, b, c, z);
output [15:0] z;
input [7:0] a, b, c;
function [8:0]sum (input [7:0] a, input [7:0] b)
begin
sum = a+ b;
end
endfunction
assingn z = sum (a, b)*c // оператор непрерывного присваивания. Вычисляется постоянно, при каждом изменении в правой части.
endmodule
должно совпадать имя функции и имя переменной, которая вычисляется!
Пример Задачи 9.
module zd8_2 (en, xout)
input en
output reg [4:0] xout;
task gen_xout;
begin
xout = 0;
repeat (1) @ (posedge en)
xout [4] = 1;
#30 xout [4] = 0;
xout [3] = 1
#30 xout [3] = 0;
xout [2] = 1
#30 xout [2] = 0;
xout [1] = 1
#30 xout [1] = 0;
end
endtask
initial
begin
gen_xout;
end
endmodule
Операторы системные задачи. Вывод информации на консоль.
Этот раздел изучить на следующем занятии
В языке Verilog есть особый класс конструкций или операторов — это системные задачи (System tasks). Они предназначены для управления процессом моделирования, вывода информации-результатов вычислений и комментариев к ним на экран, вывода служебных сообщений, загрузки данных из файлов в память компьютера и наоборот, пересылки данных между областями памяти компьютера и устройствами ввода-вывода. Характерным их признаком является то, что они начинаются с символа $, например $display. Основными среди них являются системные задачи, предназначенные для вывода данных на консоль -$display. Консоль – это область внизу экрана - окно Transcript, где появляется информация, характеризующая выполняемые процедуры и где пользователь может разместить желаемую информацию, в частности комментарии и результаты выполнения программы. и
Вывод информации на консоль.
Основной оператор вывода данных на консоль $display имеет синтаксис:
$display (''<строка> '', <имя_сигнала1>, <имя_сигнала2> ….);
В <строке> указывается текст и символы, которые будут выведены на экран в качестве комментариев. Кроме того в строке могут присутствовать «признаки формата». Признак формата состоит из символа % и буквы, обозначающей формат отображаемого значения. Каждому признаку формата в строке сопоставляется сигнал из списка следующего после самой строки. Первый признак формата сопоставляется с первым сигналом в списке, второй — со вторым и т.д. Допустимые признаки формата:
%d or %D - вывод на монитор переменной или значения сигнала в десятичном формате.
%b or %B и %h or %H для вывода данных в двоичном, шестнадцатиричном формате соответственно.
Кроме того при указании признака %m or %M на консоль будет выведено имя используемого при этом модуля.
Для вывода на данных дисплей можно использовать еще ряд операторов-системных задач.
Системная задача $write. Если после выполнения $display производится перевод строки, то после аналогичного оператора $write — нет.
Системная задача $monitor выводит значения указанных переменных (сигналов) при их изменении. Синтаксис оператора аналогичен $display, т.е. также могут использоваться признаки формата. В отличие от $display задача $monitor указывается в коде единожды, но исполняется при каждом изменении указанных сигналов.
Кроме того задача $strobe выполняется только после того как будут выполнены все присваивания намеченные на данный момент модельного времени.
Сказанное иллюстрируется нижеприведенным коротким примером.
module sum_3(summa);
reg [1:0] a,b;
output reg [2:0] summa;
always @ (*)
summa=a+b;
initial
begin
a=2;
b=1;
#100
a=0;
b=0;
#100
a=2;
b=2;
#200;
end
always @ (a or b)
begin
#1 if (summa==(a+b)) $display ("TRUE %d + %d = %d",a,b,summa) ;
end
endmodule
Блок initial задает трижды изменяющееся входное воздействие. Первый блок always @ (*) вычисляет сумму при каждом изменении входных воздействий. Второй блок always @ (*) при изменении или осуществляет сравнение правильности вычислений и выдает на консоль результат с комментарием:
# TRUE 2 + 1 = 3
# TRUE 0 + 0 = 0
# TRUE 2 + 2 = 4
Файловый ввод/вывод (пересылка данных)
Характерными признаками операторов файлового обмена являются два последовательных символа $f, с которых начинаются эти операторы.
Режим открытия файлов.
Для работы с файлом, т.е. при пересылке данных в нем содержащихся в моделируемый модуль и наоборот, необходимо сначала его «открыть», а по окончании работы, т.е. после пересылки данных, - файл необходимо закрыть.
Для открытия используется оператор $fopen. Его синтаксис:
<идентификатор файла> = $fopen ("<имя файла>", "<мод файла>");
Переменная "<мод файла>") задает направления обмена. Используемая в качестве "<мода файла>") переменная "r" задает режим чтения, а "w" задает режим записи.
Имя name "<имя файла>" указывает весь путь, как попасть в файл в памяти компьютера, откуда следует считать данные при чтении, или куда их надо записать при записи. Причем, это должен быть текстовый файл с расширением .txt.
Переменная <идентификатор файла> должна быть задана как целочисленная в начале модуля. Например, в качестве ее можно использовать имя fp1:
integer fp1;
Таким образом, запись
fp1=$fopen(“C:\\tasks\\inputA1.txt”,’r”);
означает открытие текстового файла inputA1, расположенного на диске C в папке tasks, и предназначенного для считывания предварительно записанного в нем численного значения.
Переменная fp1 может иметь произвольное значение, однако после выполнения операции открытия файла необходимо убедиться, что она не равна нулю, ибо это означает ”пустой” файл, из которого нельзя ничего считать. Рекомендуется после открытия файлов всегда проверять значение этой переменной на неравенство нулю с помощью условных операторов.
Режим чтения.
Чтение из файла производится с помощью задач $fgetc, $fgets, $fsacnf.
Далее будем использовать как основную задачу $fscanf. Оператор $fscanf будет читать строку из файла и хранить ее в указанной форме.
Синтаксис использования системной задачи $fsacnf:
I=$fscanf (<идентификатор файла>, <формат>, <имя1>, <имя2>,…);
Вместо I можно использовать другую переменную, важно лишь описать ее в начале модуля как целочисленную: integer I. Ее значение указывает число прочитанных данных. Специально это не используется, это просто полезное свойство синтаксиса оператора. Если в течение операции чтения произойдет ошибка, то это число будет нулем.
Формат задается, как и ранее признаками формата, например %b или %D. Задание формата указывает, в каком формате будут считаны данные. Желательно, чтобы до считывания они хранились также в этом формате.
<имя> указывает имя переменной, которой будет присвоено считываемое из памяти численное значение, или иначе адрес регистра назначения пересылки. Можно считывать несколько значений для нескольких переменных назначения.
Пример записи оператора:
i=$fscanf(fp1, “%b %h”, a, b);
Команда $fgets будет читать всю строку текста из файла и хранить эту информацию как строку. Команда $fgetc будет читать символ из файла и возвращать его как 8-битовую строку. Эти две команды имеют особенности применения, в том числе и в синтаксисе, и далее они не будут использоваться.
Режим записи данных в файл.
Для записи данных в файл могут использоваться задачи $fdisplay, $fstrobe, $fwrite, различающиеся между собой также как и соответствующие им и рассмотренные ранее команды вывода на консоль: $display, $strobe, $write. Синтаксис проиллюстрируем на примере оператора $fdisplay:
$fdisplay (<идентификатор файла >, “<строка>”, <имя1>, <имя2>,…); Пример записи:
$fdisplay (fp2, “%b”, a);
В приведенном случае по адресу, который указан при открытии файла с помощью fp2 в двоичном формате будет записано численное значение переменной a, определенной в этот момент в нашем модуле.
Закрытие файлов.
После выполнения открытых операторов пересылки их все надо закрыть с помощью системной задачи $fclose:
$fclose (<идентификатор файла >);
Например:
$fclose (fp1);
$fclose (fp2);
Пример использования системных задач:
В модуле объявить 32-х разрядную переменную типа reg. Создать на диске D в папке system_tasks_2 текстовый файл, открыть его задачей $fopen, и используя задачу $fscanf считать значение переменной из файла. Записать значение переменной в файл десятичном, двоичном и шестнадцатиричном виде, используя команды $fdisplay и $fwrite. Закрыть файл.
module laba_5;
reg [31:0] a;
integer fp1;
integer fp2;
integer i;
initial
begin
fp1=$fopen("D:\\system_tasks_2\\input1.txt", "r");
fp2=$fopen("D:\\system_tasks_2\\output1.txt ","w");
if (fp1==0 || fp2==0)
begin
$display("File Error\n");
$finish;
end
i=$fscanf(fp1,"%x",a);
$display("Displaying in %m");
$fdisplay(fp2,"In hex %x, dec %d, bin %b\n",a,a,a);
$fclose(fp1);
$fclose(fp2);
end
endmodule