Программирование ПЛИС
Выбери формат для чтения
Загружаем конспект в формате docx
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Программирование ПЛИС
ЛЕКЦИЯ 1
Литература:
1. Грушвицкнй Р. И., Мурсаев А. X., Угрюмов Е. П.
Проектирование систем на микросхемах программируемой структурой. —
2-е издание. СПб.: БХВ -Петербург, 2006. — 736 с.
(3.3. Элементы языка Verilog HDL 499-549 с.с.)
Или:
Грушвицкнй Р. И., Мурсаев А. X., Угрюмов Е. П.
Проектирование систем на микросхемах программируемой логики. —
СПб.: БХВ -Петербург, 2002. — 608 с.
(3.3. Элементы языка Verilog HDL 419-467 с.с.)
2. http://marsohod.org (Здесь лекции, пособия, примеры)
Имена и комментарии и строки
Verilog различает строчные и прописные буквы.
1. В именах используются буквы, цифры, символ _ , и символ $. С них могут имена начинаться. Но в именах нельзя использовать пробел.
2. Комментарии. Можно использовать 2 варианта комментария :
a. // комментарий строчный комментарий
/* …. */ на несколько строк.
3. Строки.
Строка – последовательность символов, ограниченная слева и справа “кавычками” .
Обычно в строке пишется комментарий, который используется для вывода на экран.
Вывод на экран производится с помощью дополнительного оператора.
Процедуры, которые удобно применять к строкам при выводе на экран.
\n // печатать текст с новой строки;
\t // табуляция текста.
Описание устройства. Понятие модуля
Основным элементом программного описания устройства является модуль. Различают модули верхнего и нижнего уровня.
Модуль нижнего уровня содержит описание конкретного устройства в виде программы, записанной на одном из языков HDL, например Verilog или VHDL.
Модуль верхнего уровня так же содержит программные конструкции, но может дополнительно содержать еще наименования модулей нижнего уровня с описанием параметров вхождения в них. Модули могут располагаться в произвольном порядке.
Структура (синтаксис) модуля.
module <имя> (<список портов>);
<определение портов>;
<определение переменных>;
<параметры>;
<непрерывное присваивание>;
< последовательное присваивание >;
<подключение модулей>;
<системные задачи и функции>;
endmodule
Пример:
module summa (a, b, sum); // в скобках список всех портов
input [3:0] a, b; // определение входных портов с указанием их разрядности: от 0 до 3
output [4:0] sum; // определение выходных портов с указанием их разрядности от 0 до 4
assign sum = a + b; - оператор вычисления суммы входных величин
endmodule
assign – указание на непрерывное присваивание. При этом величина sum изменяется каждый раз, когда изменяется величина a или b.
Определение портов
<направление> <диапазон разрядности> <имя>;
Пример:
input [7:0] a;
Указание разрядности может отсутствовать, тогда подразумевается, что величина может иметь значения 0 или 1, например
output sum;
В качестве портов как впрочем и описываемых переменных вообще могут использоваться отдельные разряды in2[3] группы разрядов out[2:0]
и объединение в одну группу отдельных разрядов нескольких переменных с помощью операции конкатенации{ in2[3], out[2:0] }.
Определение переменных
Если величина не входная и не выходная, а промежуточная, то мы её должны специально определить, используя следующий синтаксис
<тип переменной><знаковая><сила сигнала><векторная/скалярная><диапазон разрядности><имя>;
Все параметры указывать не обязательно, только те которые необходимы.
Пример:
wire signed ( weak1, supply0) vectored [3:0 ] adr;
Пример:
reg (string1, weak0) scalared adr;
Тип переменной
Основная форма представления информации или данных, это сигналы, кроме которых могут быть ещё и служебные данные. Сигналы представляются в алфавите : {0, 1, x, z}. Возможны 2 типа сигналов (переменных).
1. Цепи (nets). Признак цепи при определении типа переменной:
wire, tri – эти операторы указывают на цепь.
wire - когда цепи подключены к единственному источнику сигнала.
tri – когда имеется несколько источников сигнала.
Основное свойство цепи - она не сохраняет состояния и управляется драйвером, который соответствует оператору параллельного присваивания. Имя драйвера совпадает с именем цепи, и это отражается в названии операторов этого типа – непрерывное присваивание. Continuous.
2. Регистры, или регистровые переменные – элементы, сохраняющие своё состояние. Признаком их задания (описания) является указание reg.
При описании тех и других можно указывать диапазон их разрядности:
[<значение старшего разряда>:<значение младшего разряда>]
Пример:
reg[10:0]continue ;
reg a; // разрядность не указана, то есть 1 разряд.
reg [2:0]b;
a = counter[1];
b = counter[4:2];
Величина может быть определена как вектор vectored или как скаляр scalar . [ …] - диапазон разрядности]. Указывается, если это нужно.
Право доступа к отдельным разрядам определяется следующим образом:
1. Если перед указанием диапазона записано ключевое vectored , то доступ к отдельным разрядам запрещён.
2. Если присутствует ключевое слово scalared а так же по умолчанию (когда ни первого, ни второго слова нету), к каждому разряду или группе разрядов можно обращаться индивидуально.
Как регистр, так и цепь, могут быть объявлены как знаковые.
reg signed [15:0] My16BitReg – регистровая переменная имеет 16 разрядов, и одновременно знаковая.
В примере 1 указывается, что это знаковая переменная (signed), а дальше стоит указатель силы.
Сила сигнала
Сила сигнала (переменной) указывается в случае, если работаем с несколькими сигналами, которые готовы к работе, и можно назначить приоритет, с кем хочешь работать. Задается парой значений уровня сигнала (т.е. его приоритетом), например (weak1, supply0). Значения, заканчивающиеся символом 1, определяют уровень драйвера в состоянии логической единицы, а заканчивающиеся символом 0, – в состоянии логического нуля.
Часто в описании verilog используется название «сила драйвера», которая выбирается из следующей таблицы :
supply1
supply0
strong1
strong0
pull 1
pull 0
weak 1
weak 0
highz 1
highz 0
Значения выбираются из разных столбцов, так как символ 1 определяет силу драйвера в состоянии логической 1, а символ 0 – в состоянии логического 0.
wire a;
reg b, c;
assign (strong0, weak 1) a = b;// мы хотим присвоить величине a либо b либо c, при этом b и c непрерывно меняются.
assign (strong1, weak0) a = c;
initial
begin
c = 1`bz, b = 1;
#10 c = 0, b=0;
#10 c=0, b = 1;
#10 c = 1, b=0;
end
Здесь сила сигнала (драйвера) увеличивается по мере вверх и если силы драйверов совпадают, то устройство принимает значение Z - высокоимпедансное.
#10 – добавление 10 тактов.
Каждый раз в этой программе происходит добавление по 10 тактов.
Для интервала в тактах – (0 – 10) : a = c = 1, так как для с strong 1 более сильный чем b.
Для интервала в тактах – (10 – 20) : a = b, так как теперь для 0 имеем более сильный сигнал для b нежели для c.
Для интервала в тактах – (20 –30) : a = z (неопределённое состояние), поскольку для обоих сигналов c=0 и b=1 силы сигналов совпадают.
Для интервала в тактах – (30 –40) : a = z (неопределённое состояние), поскольку для обоих сигналов c=1 и b=0 силы сигналов совпадают.
Параметры
Параметрами являются константы, которые имеют следующий вид :
parameter <имя> = <значение>;
Вместо значения можно написать выражение.
parameter b = 7;
parameter b = 7, a = 5;
parameter w = a + b;
Достоинством использования параметров является возможность внесения изменений в значения переменных, являющихся параметрами, сразу в программном модуле моделирования, не изменяя исходную программу, и не выполняя промежуточных действий по компиляции и созданию новых модулей, необходимых для моделирования.
После того, как мы убедились, что параметры выбраны правильно, можно убрать слово parameter и прошить ПЛИС.
ЛЕКЦИЯ 2
Памяти
Память описывается как массив регистров.
reg [<разрядность>]<имя памяти>[<объем памяти или число ячеек памяти>]
При определении разрядности сначала указывается большее значение, затем меньшее:
[ большее значение : меньшее значение], например [31:0].
Запись reg [10:0] memory1 [31:0] определяет массив из 32 слов памяти memory1 , где каждое слово имеет разрядность 11 бит.
Двумерная память :
reg[7:0] twoDim [15:0][b3:0];
reg[9:0]treeDim [31;0][31:0][43:0] // трехмерная память.
Как можно обращаться к памяти?
memory[5] = 75;
A_reg = twoDim [3][2]; - если у нас объявлен a_reg
Многомерный массив:
A = threeDim[21][4][40][7:0]; выбор 8-ми битов, начиная с элемента, указанного слева.
Числа
Числа могут представляться
- в двоичном формате (b или B)
- в восьмиричном формате (O или o)
- в десятичном формате (D или d)
- в шестнадцатиричном формате (H или h)
Формат записи :
1. <разрядность>`<основание><значение>
Разрядность всегда указывается в количестве двоичных разрядов. Кроме того, в числах можно использовать символы x и z. X – неопределенное состояние, z – высокоимпедансное значение.
Если операция выполняется над двумя числами различной разрядности, то для числа с меньшей разрядностью позиция старших разрядов заполняется нулями, чтобы оба числа имели одинаковую разрядность.
Если в большем числе присутствует символы x или z, то в другом числе так же заполнение производится этими символами.
В состав числа можно включать позицию символ подчёркивания, чтобы улучшить читаемость поля, однако, этот символ не может быть первым.
Пример правильной записи.
12`b 0x0x_1101_0zx1 - 12 позиций разделили подчёркиванием, для лучшей читабельности кода.
Пример неправильной записи.
8`b_001
Так же возможны ещё варианты представления:
1. <основание><значение числа>
Форма записи является машинозависимой. Обычно понимается разрядность 32.
2. <значение>
193 – десятичная форма записи. По умолчанию разрядность равна 32.
`h 7d2 – десятичное, по умолчанию 32.
Можно указывать знак числа :
1. Положительным
2. Отрицательным
При этом указывается число в дополнительном коде.
S – для чисел со знаком, и число представляется в дополнительном коде.
3` sd 7 – это число, имеющее разрядность 3 бита, то есть децимальное число 7 представлено 3мя битами, как 111 в дополнительном коде. Следовательно, значение такого числа равно -1.
Как это так вышло?
111 =>(инвертируем) 100 (и к младшему разряду добавляем 1) => 101 и того, это равно -1. Старший разряд -это знаковый разряд.
Вместо символа z можно использовать символ ?.
Например :
12`d? = 12`dz
Вещественные числа могут записываться в десятичном или в научном формате.
Число в десятичном формате должно иметь, по крайней мере, хотя бы одну цифру после десятичной точки.
Пример :
1.8
3. // неверно!
Пример научного формата :
8e10 // 8^10
Язык verilog преобразует вещественные числа в целые, округляя их до ближайшего целого.
Целые числа и время
integer a, b;
integer c [100 : 1];
time q, r;
time s [31:0]; -целочисленная величина , указывается дополнительно.
Операторы
Арифметические :
1. * умножение
2. / деление
3. + сложение
4. – вычитание
Конкатенации и репликации (объединение и повторение)
{a,b} – объединение (конкатенация)
{{4а}} – повторение (репликация)
? – условный оператор
Синтаксис условного оператора
<оператор условия> ? <истина> : <ложь>;
Например, если записано
wire [15:0] busa = drive_busa ? data : 16’bz;
то результат будет следующий: шина busa примет значение (состояние) data, если drive_busa - истина, либо равен единице. В приведенном примере будет drive_busa = 1, а в противном случае шина переключится в состояние z.
== сравнение на равенство
!= … на неравенство.
Операторы идентичности:
=== -идентично
!== - неидентично
= //описание процедуры присваивания с= a + b;
== // используется в ситуации проверки условия равенства переменных a == b; за исключением позиции x.
=== // если мы хотим, чтобы все позиции совпали, то нужно использовать это выражение.
a = 0110x1
b = 0110x1
a === b возвращает true.
Битовые (поразрядные) операторы
1. ~ // побитовая инверсия
2. & // поразрядная двоичная И
3. | // поразрядная двоичная ИЛИ
4. ^ // поразрядная двоичная исключающая ИЛИ
5. ^~ // поразрядная двоичная исключающая ИЛИ – НЕ.
6. ~^ // - || -
Логические операторы
7. && // логическая И
8. || // логическая ИЛИ
9. ! // логическое НЕ (инверсия)
Задержки
Задержка определяет задержку между изменением значения находящегося справа от операнда и назначением, сделанным на левую сторону операнда.
Пример:
module D (out, a, b, c);
output out;
input a, b, c;
wire e; // определение внутренней цепи
assign #(5) e = a + b;// фиксация результата e и out относительно входных сигналов.
assign #(4) out = e & c;
endmodule;
Можно указывать параметр задержки в виде одного значения, а можно указать 2 или 3 значения.
# 5 #(5)
# (5, 7) задержка по переднему фронту и по заднему фронту.
#(5, 7, 10) указывается 3 типа задержки. Первое число – задержка на переключение сигнала из 0 в 1 , иначе – срабатывание по переднему фронту сигнала. Второе число – задержка при переключении или на переключение из 1 в 0, и третье – значение задержки при переключении в Z состояние.
Дополнительно по свойствам задержек можно указать: различают инерционную и транспортную задержки.
Инерционная – когда сигнальный импульс имеет длительность меньше задержки и в результате задержка отработана не будет.
Транспортная – все изменения входного сигнала , какими бы кратковременными они не были, будут отработаны.
Пример:
В дальнейшем будут использоваться только транспортные задержки.
Кроме того различают еще три типа задержек:
1. Минимальная.
2. Типовая.
3. Максимальная.
assign #10 out = in1& in2;
out # (5:7:9, 8:10:12, 15:18:20)(i1,i2,i3)
(минимальное – min) (типовое typ) (максимальное max)
Передний фронт: задний фронт: переход в высокоимпедансное значение.
ЛЕКЦИЯ 3
Присваивания (назначения)
Различают 2 типа назначения или присваивания: непрерывное и последовательное ( иначе процедурное).
“Непрерывное” означает, что назначение осуществляется каждый раз, когда значение в правой части выражения, содержащего знак равенства или какой-то оператор, изменяется. Выражение в правой части не ограничено, то есть это могут быть операторы выбора case, условный оператор if, и т.п. Более того не обязательно, чтобы это было какое то значение, т.е. может быть ещё и ветвление.
Существенными свойствами непрерывного назначения являются :
а) это назначение распространяется на цепи;
б) на определённый бит цепи;
в) на группу бит цепи;
г) на комбинацию любых предыдущих вариантов.
Поскольку мы говорим, что назначение распространяется на цепи и не распространяется на регистры, то определение цепи указывается оператором wire или tri.
wire (strong1, weak0) x=a+b;// обозначение силы сигнала по выводу 0 и 1 для цепи.
Варианты осуществления назначения
1. В Verilog возможно выполнить сетевое назначение при объявлении цепи. Пример записи:
wire (strong1, weak0) x=a+b
tri
2. Назначение с помощью оператора непрерывного присваивания assign.
assign (strong 1, weak 0) x = a+b;
В ряде случаев переменная цепи, например x, может отсутствовать при объявлении цепи, являясь внутренней, или не может быть объявлена в начале. Например, если это конкатенация.
Рассмотрим примеры использования оператора непрерывного присваивания и других операторов рассмотренных ранее.
Пример 1.
Синтезировать сумматор 2-х 4-х битных чисел с входом и выходом сигнала переноса:
Текст программы:
module adder (sum_out, carry_cut, carry_in, ina, inb); // объявление имени модуля и всех его портов
output [3:0] sum_out; // классификация портов с указанием, входные или выходные
intput [3:0] ina, inb; // порты и разрядности, если это необходимо.
output carry_out; // - |…| -
input carry_in; // - |…| -
wire carry_out, carry in; // указание, это цепь или регистр? Тут цепь.
wire [3:0] sum_out, ina, inb; // характеристика типов используемых переменных – в данном случае это цепи так же с указанием разрядности, если это нужно.
assign { carry_out, sum_out} = ina + inb + carry_in; // определение непрерывного присваивания на оператор конкатенации. Сложение входов a+ b и ещё одноразрядная величина, которая указывает код знакопереноса.
endmodule;
Пример 2.
Создать модуль, который осуществляет сдвиг 16 разрядных входных на 5 разрядов влево.
Текст программы:
module zd1_2(din, dout); // объявление входов и выходов
input [15:0] din;
output [15:0] dout;
// выполним сдвиг
assign dout = (din << 5); // assign dout[10:0] = din [15:5];
assign dout[15:11] = din [4:0];
endmodule.
Примечание:
вместо «желтого» оператора можно использовать «зеленый».
Однако, в этом случае может возникнуть неопределённость в значениях высвободившихся правых разрядов. Поэтому часто используют кольцевой
Сдвиг путем добавления розового оператора.
Пример 3.
Разработать модуль, реализующий 8ми разрядный сумматор, имеющий 2 входа A и B и 2 выхода – сума- sum и перенос - cout.
Текст программы:
module zd1_7 (a, b, sum, cout);
input [7:0] a,b;
output [7:0] sum;
output cout; // 9-ти разрядная переменная, хранящая сумму и перенос.
wire [8:0] sum_with_cout; // 9-ти разрядная переменная, хранящая сумму и перенос.
assign sum_with_cout = a + b; // после этого в старшем разряде окажется признак переноса, а у младших 8ми результат //сложения.
assign sum = sum_with_cout [7:0]; // перенесём сумму на выход.
assign cout = sum_with_cout [8]; // перенесём перенос на выход. 8-ой разряд полученной величины.
endmodule.
Пример 4.
Создать модуль, представляющий собой одноразрядный элемент И с программируемой задержкой. Задержка должна задаваться параметром T.
Текст программы:
module zdz_1 (x1, x2, y);
input x1, x2;
output y;
parameter T = 10;
assign #(T) y = x1 & x2;
endmodule.
Пример 5.
Реализовать 8-ми разрядный буфер с 3-им состоянием.
Буфер управляется сигналом oe. При oe = 1 входные данные передаются на выход. При oe = 0, выход устанавливается в высокоимпедансное значение.
Текст программы:
module ad2_7 (din, dout, oe);
intput [7:0] din;
input oe;
output [7:0] dout; // в зависимости от oe зададим выходные сигналы.
assign dout = (oe) ? din : 8`bzzzz_zzzz;// 8ми разрядная переменная, формат бинарный. При реализации, _ не проявляется как разряд.
endmodule
Процедурное (последовательное) назначение (присвоение)
Процедурное назначение применяется для переменных типа reg - регистр, integer - целое, real - вещественное, time – время. Будем в основном использовать reg.
Эти присвоения обладают памятью. То есть, присвоенное значение переменной будет оставаться неизменным, пока не произойдёт новое присвоение.
В качестве процедурных назначений используется 2-а оператора.
аlways и initial. Они определяют процедуру назначения.
Оператор initial:
В этом случае выполнение операции начинается в нулевой момент времени. И этот блок выполняется единожды. Блоков initial может быть несколько. И они могут располагаться в различных местах программы, но при этом они выполняются параллельно (одновременно) и независимо.
Действие оператора initial распространяется только на один последующий оператор. Если его действие нужно распространить на несколько операторов, то нужно их заключить в так называемые скобки begin …. end.
Пример:
module primer; // портов нет поскольку все действия реализуются внутри программы.
reg x, y, a, b, m; // вместо wire указали reg
initial
m = 1`b0;
initial
begin
#5 a = 1`b1;
#25 b = 1`b0;
end
initial
begin // выполнение началось вместе со всеми initial, но в блоке begin .. end выполняется последовательно.
#10 x = 1`b0;
#25 y = 1`b1;
end
initial
#50 $finish; // оператор finish означает остановку инструкций, чтобы избежать неопреденённости.
endmodule
Все 4-е оператора initial выполняются одновременно, начинаясь в нулевой момент времени.
Время задержки исполнения
Выполняется Оператор
m = 1`b0
5
a= 1`b1
10
x = 1`b0
30
b = 1`b0
35
y = 1`b1
50
$finish
Оператор always:
Его действие так же распространяется на 1 последующий оператор или на группу, заключённую в скобки begin … end. Блок always так же начинается в нулевой момент времени, но его выполнение циклически повторяется. Поэтому его удобно использовать для генератора тактовых импульсов.
module clock_gen (output reg clock);
initial
clock = 1`b0; // устанавливает в 0 момент времени, чтобы переменная clock была определена.
always
#10 clock = ~clock; // происходит через 10 тактов задержки. clock инвертируется и эта операция многократно повторяется.
initial
#1000 $finish; // остановка через 1000 тактов.
endmodule
Цикл выполнения блока always начинается сразу по окончанию предыдущего цикла.
Возможно другое использование оператора always по событийному выражению.
always @ (<событийное выражение 1>or<соб. выр. 2>… <>). В этом случае блок always срабатывает каждый раз, когда изменяется событийное выражение.
Событийное выражение - любая переменная величина. Если она изменяется, выполняется всё выражение:
always @ (clock) q = d;// присвоение q= d изменяется при изменении сигнала clock.
Могут быть разновидности:
always @ (pasedge clock) q = d; срабатывание по положительному фронту сигнала
always @ (pasedge clock) q = d; срабатывание по отрицательному фронту сигнала
always @ (a or b)
X = a + b; // при изменении любой переменной a или b происходит изменение суммы.
always @(*)
X = a + b; //* - означает срабатывание блока операторов, при изменении любой переменной блока.
ЛЕКЦИЯ 4
Блокирующие и не блокирующие присваивания
Блокирующее присваивание (обозначается обычным знаком равенства =) запрещает исполнение других присваиваний до своего завершения. То есть гарантируется последовательное исполнение операторов в блоке.
Если присвоение содержит задержку, то изменение будет выполнено через число тактов, соответствующих значению задержки. (В дальнейшем мы будем использовать этот тип).
Неблокирующее присвоение (обозначается двойным знаком <=) разрешает исполнение последующих операторов до собственного завершения.
Пример.
module block_nonblock;
reg a, b, c , d, e, f;
initial // фрагмент блокирующего присвоения
begin
a = #10 1; //a – на 10 такте
b = #2 0; // b – на 12 такте
c = #4 1; // c – на 16 такте
end
initial // фрагмент неблокирующего присвоения
begin
d <= #10 1; // d – на 10 такте
e <= #2 0; // e – на 2ом такте
f <= #4 1; //f – на 4ом такте
end
endmodule
Операторы ветвления (условные) if … else …
Варианты:
- if (выражение/условие) <оператор, выполняемый в случае, когда условие истинно> else <оператор, выполняемый в случае, когда условие ложно>;
- if (выражение) <оператор> // если выражение истинно, выполняется оператор. В противном случае оператор пропускается.
- if (…) <… > else if (…) else if (…)…//вложенные условия
Пример.
if (instruction == ad)
begin
carryin = 0;
complement_arg = 0;
end
else if (instrucrion == sub)
begin
carry_in = 1;
complement_arg = 1;
end
else
illegal = 1;
Условный оператор case
Состоит из ключевого слова case, после которого записывается выражение в круглых скобках. Кроме того, в конструкцию входит одно или более значений для case, после которого ставится 2 точки и приводится оператор для выполнения. Конструкция заканчивается endcase.
Оператор работает следующим образом. Происходит сравнение выражения в круглых скобках с приводимыми далее значениями case. При совпадении с каким либо из них, выполняется оператор или присвоение, указанное для этого значения после двоеточия. Проверка осуществляется последовательно, и при первом же совпадении процедура останавливается.
Возможно, что никакое значение не совпадет, тогда можно определить выбор и назначение некоторого результата, указав в качестве значения оператор default.
Пример:
default : next_state = IDLE;
case (state)
IDLE : begin
if (state)
next_state = STEP;
else
next_state = IDLE;// IDLE - неопределённое состояние
end
STEP1
next_sate = STEP2;
STEP2 :
next_state = IDLE;
default : next_state = IDLE;
endmodule;
Пример 6.
Описать мультиплексор 4 x 1 с использованием последовательного присваивания и условных операторов.
module zds_1 (din1, din2, din3, din4, dout, select); // перечисление всех портов
input din1, din2, din3, din4;
intput [1:0] select;
output reg dout;
always @(*) // срабатывает каждый раз при изменении переменной блока
begin
if(select == 2`b00) dout = din1;
else if(select == 2`b01) dout = din2;
else if(select == 2`b10) dout = din3;
else if(select == 2`b11) dout = din4;
end
endmodule;
Пример 7.
Описать 3х разрядный дешифратор с использованием последовательного присваивания и оператора case.
module zd3_2 (din, dout);
intput [2:0] din;
output reg [7:0] dout
always @(*)
begin
case(din)
3`d0: dout = 8`b 0000_0001;
3`d1: dout = 8`b 0000_0010;
3`d2: dout = 8`b 0000_0100;
3`d3: dout = 8`b 0000_1000;
3`d4: dout = 8`b 0001_0000;
3`d5: dout = 8`b 0010_0000;
3`d6: dout = 8`b 0100_0000;
3`d7: dout = 8`b 1000_0000;
endcase
end
endmodule
Возможны еще две разновидности оператора case.
- casex –При сравнении не учитываются значения разрядов замещенных символом x. Например
101x01 , в позиции x может быть 1 или 0;
- casez – не учитывает значения разрядов замещенных символом z.
Примитивы
Примитив - это модуль, имеющий один одноразрядный выход.
Бывают примитивы:
- предопределённые;
- определяемые пользователем.
Предопределённые содержатся в библиотеке САПРа. Например or, and, …
Как правило, пользуются операторами | или &.
Структура примитива, определяемого пользователем:
primitive <имя> (<список портов>)
<определение портов>
table
.
.
.
endtable
endprimitive
В списке портов первым указывается выходной.
Пример
primitive test (z, a, b, c)
input a, b, c;
output z;
table
// a, b, c, z - указываем возможные величинв a,b,c
0 0 1 :1
1 0 1 :0
? 1 1: 1
…………….
endtable
endprimitive
Символ ? – означает нечувствительность результата к значению сигнала в этой позиции.
Приведённый пример соответствует комбинационному типу примитива.
Возможен последовательный тип примитива, когда выходной сигнал зависит ещё от предыдущего значения. В этом случае дополнительно указывается, что Z это регистр: output reg z;
Пример
output reg z;
table
// a b c zn zn+1
0 0 1 : 1 : 0
1 0 1 : 1 : 1
? 0 1 : 0 : 1
endtable
ЛЕКЦИЯ 5
Операторы цикла
Эти операторы могут использоваться только в последовательных блоках. Возможны следующие 4-е варианта их реализации.
1. for (index = min; index (неравенство < (или >)) max (или min); index = index+ (или -) step) <оператор>; // пошагово значение index достигает max или min значения c шагом step.
Например, for (I = 0; I < 4; i= I +1) <оператор> ; // начальное значение, как и размер шага переменной может быть любым. Эти процедуры применимы к целочисленным значениям. i принимает только целые значения, поэтому длжно быть указание: integer i или reg i.
2. while (count < 128 )
begin
count = count +1;
end
Если пишешь выражение, состоящее из нескольких операторов, то нужно его заключать в скобки begin … end;
Для оператора while допускается использование их в блоках always, но в этом случае в операторе always в качестве событийного выражения следует указывать фронт сигнала.
Неправильная запись:
always
while (x < a)
x = x + z;
Правильная запись:
always
begin @ (posege clk)
while (x < y)
begin
x = x + z;
end
end
3. forever < оператор> // оператор, который будет выполняться бесконечное количество раз. Такой оператор применяется для создания тактовых генераторов (тактовых сигналов).
Пример.
module clk_sign;
reg clk;
initial
begin
clk = 1`b0; // регистр или регистровая переменная.
forever #10 clk = ~clk;
end
initial // последовательный оператор, для начала процедуры в нулевой момент времени программы.
#100 $finish; // параллельно будет выполняться подсчёт 100 тактов задержки, после чего будет выполнен оператор finish. Команда finish просто останавливает действие программы и выходит из неё.
4. repeade (n) <>; повторение; // n – число повторений оператора в скобках.
Пример 8.
Создать 8-ми разрядный двоичный сумматор, имеющий вход и выход переноса. При этом не использовать операцию сложения, а использовать оператор цикла.
module cikl_1 (a, b, dout, cin, cout)
input [7: 0] a,b; // a, b име.т разрядность 8
input cin;
output reg [7:0] dout; // выходная переменная
reg [3:0] I;
output reg cout;
always @(*) //процедура
begin
cout = cin;
for (I = 0; I < = 7; I = I + 1)
begin
dout [i] = a[i]^b[i]^cout; // операция сложения выполняется с помощью логическх операторов ^ исключающее или. count – добавляется знак переноса из предыдущего блока.
cout = (a[i]&b[i])|(a[i]&cout)|(b[i]&cout); // сравниваем (может возникнуть переполнение, если сложили 2 единицы). Значит, нужно делать двоичный сдвиг.
end
end
endmodule
dout [i] = a[i]^b[i]^cout; - поэтапное логическое сложение значений одноименных разрядов (И) входных переменных a и b, и учёт для I = 0 входного сигнала переноса.
Для 0 < I < 7 сигналы переноса – переполнения от выполнения вычислений по предыдущему разряду.
Пример 9.
Создать модуль, вычисляющий бит четности для 8-разрядного операнда. Использовать операторы цикла, операцию свертки не использовать.
module cikl_2 (din, dout);
input [7:0] din;
output reg dout;
reg [3:0] i; // переменная цикла
always @(*)
begin
dout=0;
for (i=0;i<=7;i=i+1)
dout=dout+din[i];
end
endmodule