Циклы в Verilog
В предыдущей части мы на простом примере познакомились с принципом модульной конструкции в Verilog и создали RS-триггеры с помощью концепции модуля-экземпляра.
Но что нам делать, если потребуется создать много (например, 50) D-триггеров, которые должны быть соединены между собой в соответствии со схемой делителя частоты? Мы уже знаем, как сделать общий модуль для D-триггера, поэтому нужно сделать 50 экземпляров этого модуля и соединить входы тактового сигнала каждого модуля с выходом предыдущего, также подвести сигнал Сброс (Reset) ко всем модулям и вывод D через инверсию соединить с выходом Q. В общем, нужно сделать всё то, что показано на рисунке:

Вручную писать столько экземпляров (50 штук по 6 строк в каждом – 300 строк!) – не практично. Поэтому в Verilog для генерации большого числа одинаковых модулей мы можем воспользоваться циклом контроллера счетчика, который является чем-то вроде цикла for. Но, чтобы этот цикл работал правильно, мы должны тщательно продумать структуру делителя частоты.
Подключение D-триггеров друг к другу является достаточно простым и шаблонным. Конечно, тактовый вход первого триггера будет соединен со входом нашего модуля верхнего уровня, а выход последнего триггера мы также присоединим к определенному выводу модуля верхнего уровня. С этими двумя триггерами мы будем работать индивидуально. Программа знает, что первому и последнему выводам модуля нужны внешние соединения, и ответственность за их подключение ложится на инженера.
Итак, поскольку каждый триггер (кроме первого) должен иметь вход, зависящий от выхода предыдущего, то мы можем все эти сигналы объединить в одну шину, что равносильно созданию массива в C/C++. Следует также учитывать, что D-вход каждого триггера зависит от выхода этого триггера. Еще не стоит забывать про общий сброс.
Но, во-первых, какой тип шины мы зададим? Поскольку шина просто переносит сигнал от источника к выходу, то это будет wire. Сделаем так:
wire in[49:0] //50-битная шина для входов триггеров
wire out[49:0] //50-битная шина для выходов триггеров
Быть может, вы захотите назвать эти шины Q и D, но не стоит этого делать, ибо нельзя давать имя одной части проекта при уже используемом таком имени другой частью проекта (Q и D являются портами модуля D-триггера).
Мы также можем создать экземпляр для нашего первого триггера:
dff dff0(
.clk(clk),
.rst(rst),
.D(in[0]),
.Q(out[0])
);
Не беспокойтесь об инвертировании сигнала, идущего на D-вход. Мы сделаем это позже. А сейчас сосредоточимся на создании цикла. Хотя он похож на цикл в C/C++, все же у него имеются некоторые отличия. Сначала мы должны создать переменную счетчика. В Verilog такой тип переменной называется «genvar», он используется для объявления переменной, например:
genvar y;
Цикл начинается с ключевого слова «generate» и заканчивается «endgenerate». Следует помнить, что объявление переменных с помощью genvar должно производиться вне тела цикла.
genvar y;
generate
endgenerate
После ключевого слова «generate» начинается область, отвечающая за работу цикла. Самому циклу не нужны никакие фигурные скобки «{}» как в C/C++, но тело цикла должно быть заключено между ключевыми словами «begin» и «end». Также нужно помнить, что Verilog не поддерживает специальные операции вроде инкремента y++, поэтому нужно делать так: y = y + 1. При создании цикла будем иметь ввиду, что мы уже создали первый (точнее нулевой) D-триггер, поэтому диапазон количества новых триггеров будет от 1 до 49 (<50).
genvar y;
generate
for(y = 1; y < 50; y = y + 1 )
begin
end
endgenerate
При создании цикла нам нужно после «begin» указать имя, начинаемого процесса. Это имя нужно для синтезатора цикла и не будет использоваться где-либо еще в нашей программе. При этом после «begin» ставится двоеточие и указывается это имя. Пусть оно будет «dff_generation», тогда получим:
genvar y;
generate
for(y = 1; y < 50; y = y + 1 )
begin : dff_generation
end
endgenerate
Итак, мы дошли до самой сложной части – создания модели экземпляра. Она будет похожа на стандартный экземпляр за исключением того, что в качестве подводимых к модулю сигналов будет использоваться переменная, объявленная как «genvar».
genvar y;
generate
for(y = 1; y < 50; y = y + 1 )
begin : dff_generation
//имя экземпляра не имеет значения в дальнейшем
dff dff_insts (
.clk(out[y-1]), //clk триггера под номером «y» является входом выхода «y-1»
.rst(rst), //общий сброс
.D(in[y]), //D-вход
.Q(out[y]) //выход
);
end
endgenerate
Нам не хватает только одного – инверсии на входе порта «.D». Ее можно применить ко всей шине, а не к отдельным сигналам:
assign in = ~in;
Итак, мы рассмотрели циклы в Verilog и собрали вполне полезную и работоспособную схему.
Перевод © digitrode.ru