Часть 0 - покупаем, паяем, ставим драйвера и софт Часть 1 - что это вообще за зверь? Часть 2 - наша первая схема! Часть 3 - кнопочки и лампочки Часть 4 - делитель частоты Часть 5 - подавление дребезга кнопкиЧасть 6 - заканчиваем кнопочки и лампочки
Часть 7 - счетчики и жаба Часть 8 - передатчик UART Часть 9 - Hello, wolf! Часть 'hA - приёмник UART Часть 'hB - UART и жаба Часть 'hC - полудуплексный UART. Часть 'hD - МКО (МКИО, Mil-Std 1553) для бедных, введение.Часть 'hE - приёмопередатчик МКО "из подручных материалов" (в процессе)
Часть 'hF - модуль передатчика МКО Часть 'h10 - передатчик сообщений МКО Часть 'h20 - работа с АЦП ADC124s051 Часть 'h21 - преобразование двоичного кода в двоично-десятичный (BCD) Часть 'h22 - Bin2Bcd с последовательной выдачей данных Часть 'h23 - перемножитель беззнаковых чисел с округлением Часть 'h24 - перемножитель беззнаковых чисел, реализация Часть 'h25 - передаём показания АЦП на компьютер Часть 'h26 - работа над ошибками (быстрый UART) Часть 'h27 - PNG и коды коррекции ошибок CRC32 Часть 'h28 - передатчик изображения PNG Часть 'h29 - принимаем с ПЛИС изображение PNG Часть 'h2A - ZLIB и коды коррекции ошибок Adler32 Часть 'h2B - ускоряем Adler32 Часть 'h2C - формирователь потока Zlib Часть 'h2D - передаём сгенерированное PNG-изображение Часть 'h2E - делим отрезок на равные части Часть 'h2F - знаковые умножители, тысячи их! Часть 'h30 - вычислитель множества Мандельброта Часть 'h31 - ускоренные сумматоры Часть 'h32 - ускоренные счётчики (делаем часы) Часть 'h33 - ускоряем ВСЁ Часть 'h34 - ускоренные перемножители Часть 'h35 - умножители совсем просто Часть 'h36 - уравновешенный четверичный умножитель Немножко поиграемся с макросами и локальными параметрами, чтобы каждый раз руками не вводить ширину шин и не волноваться, что новые параметры сделают модуль неработоспособным.
А также реализуем другую схему подавления дребезга, которая выдаёт единичный импульс по нажатию кнопки, а потом, если держать кнопку нажатой - импульсы продолжатся с заданным интервалом, например, 2 раза в секунду. Так ведут себя многие кнопки на часах.
Сперва должок. Когда мы написали делитель частоты, формирующий импульсы раз в миллисекунду и 2 раза в секунду, мы ручками задали количество бит, отведённое на каждый из счетчиков: 17 бит на "быстрый" счетчик (чтобы поделить частоту в 80 000 раз - как многие наверняка помнят, с помощью 16 бит можно поделить частоту лишь в 65536 раз) и 9 бит на "медленный", для деления в 500 раз.
Это чревато: к примеру, назначим Ffast = 400 Гц (тогда, если у нас 4 семисегментных индикатора, один период развёртки составит 100 Гц, что глаз ещё не раздражает) - и всё, схема накроется медным тазом, пока мы не влезем внутрь и ручками не переправим. И даже шаг в обратную сторону может нам всё порушить: выставив Ffast = 5 кГц, мы выведем из строя "медленный" счетчик, поскольку он попытается сосчитать до 2500, но это число попросту не влезет в 9-битный регистр.
В стандарте IEEE Std 1800-2005 в язык verilog была добавлена функция $clog2, сокращённо от ceil(log2(x)), т.е двоичный логарифм от числа округляется вверх, что и указывает количество бит, необходимых, чтобы представить такое количество значений (само число может и не влезть, например, $clog2(8) = 3, но с помощью 3 бит мы представляем числа от 0 до 7).
К сожалению, Quartus II 9.0sp2, к которому мы "привязаны" (более свежие версии уже не поддерживают flex10k и наш 5576ХС4Т), испытывает странные отношения к $clog2: он определённо знает, что это такое, но использовать при синтезе схем не позволяет, выдавая ошибку "system function "$clog2" is not supported for synthesis"!
Приходится идти в обход. На
форуме intel'а (бывший форум Altera) предлагается в каждом модуле, где необходимо посчитать эту функцию, написать такую функцию самостоятельно:
module GenHID(input clk, output ce_fast, output ce_slow);
parameter Fclk = 80000000; //80 MHz
parameter Ffast = 8000000; //8 MHz for simulation
parameter FastCntWidth = CLogB2(Fclk / Ffast);
reg [FastCntWidth - 1 : 0] fast_ct = 1'b0;
...
...
function integer CLogB2;
input Depth;
integer i;
begin
i = Depth;
for(CLogB2 = 0; i > 0; CLogB2 = CLogB2 + 1)
i = i >> 1;
end
endfunction
endmodule
Заметим: эта функция "существует" исключительно на этапе компиляции, ведь ей передаётся константа! Такое вот метапрограммирование :)
Увы, решение громоздкое: синтаксис Verilog позволяет использовать только "локальные" функции, которые сидят внутри какого-то модуля. Создать отдельный файл с такими функциями, и вызывать их отовсюду нельзя. Такая возможность появляется в System Verilog. Может, и стоит в итоге на него перейти...
Если же мы хотим оставаться в рамках Verilog'а, то придётся воспользоваться макросами (препроцессором). Функцию CLog2 мы можем написать так:
`define CLOG2(x) \
(x <= 2) ? 1 : \
(x <= 4) ? 2 : \
(x <= 8) ? 3 : \
(x <= 16) ? 4 : \
(x <= 32) ? 5 : \
(x <= 64) ? 6 : \
(x <= 128)? 7 : \
(x <= 256)? 8 : \
(x <= 512)? 9 : \
(x <= 1024)? 10 : \
(x <= 2048)? 11 : \
(x <= 4096)? 12 : \
(x <= 8192)? 13 : \
(x <= 16384)? 14 : \
(x <= 32768)? 15 : \
(x <= 65536)? 16 : \
(x <= 'h20000)? 17 : \
(x <= 'h40000)? 18 : \
(x <= 'h80000)? 19 : \
(x <= 'h100000)? 20 : \
(x <= 'h200000)? 21 : \
(x <= 'h400000)? 22 : \
(x <= 'h800000)? 23 : \
(x <= 'h1000000)? 24 : \
(x <= 'h2000000)? 25 : \
(x <= 'h4000000)? 26 : \
(x <= 'h8000000)? 27 : \
(x <= 'h10000000)? 28 : \
(x <= 'h20000000)? 29 : \
(x <= 'h40000000)? 30 : \
(x <= 'h80000000)? 31 : \
(x <= 'h0FFFFFFFF)? 32 : \
-1
Надо заметить, что параметры и константы в Verilog'e, по крайней мере в исполнении Quartus II 9.0sp2 - это 32-битные целые, так что значения свыше 32 нам тут в любом случае не нужны. Мало того, в последнем условии мы вместо правильного значения 232 сравниваем с 232 - 1 - по этой же причине.
Синтаксис весьма укуренный. Все команды препроцессора начинаются с символа `, который сидит на клавише слева сверху, вместе с тильдой и буквой Ё. Не путать с одинарной кавычкой ', которая используется для записи литералов.
Обратный слеш в конце строки означает, что строка продолжается и далее, ну это понятно.
Наконец, в прошлых частях мы вводили бинарные слова заданной ширины, например 8'b11101011. Здесь мы вводим 16-ричные значения, причем ширину не используем - по умолчанию устанавливается 32 бита. Если же мы напишем 4'hFF00, это будет означать - ширина 4 БИТА. Затем нас предупредят, что значение литерала было обрезано. То есть, число перед одинарной кавычкой - это всегда количество БИТ, независимо от того, в какой системе счисления мы вводим этот литерал.
Данный макрос мы можем сохранить отдельным файлом, например, math.v. Затем, в тех модулях, где нам нужен этот макрос, перед объявлением модуля мы пишем:
`include "math.v"
На всякий случай в файле math.v можно даже написать классическую "обёртку", как в языке C:
`ifndef _math_
`define _math_
...
`endif
хотя, поскольку в Quartus'е все используемые модули попросту перечисляются в проекте, и друг на друга они не ссылаются, ситуации с повторным объявлением макросов и так происходить не должно.
И ещё один полезный элемент синтаксиса - localparam. Если обычный параметр (parameter) попадает в табличку на принципиальной схеме, где его можно изменить, то localparam "наружу" не выходит, и изменить его никто не может.
Сейчас покажем, как всё это можно применить. Вот доработанный модуль GenHID:
`include "math.v"
module GenHID (input clk,
output ce_fast,
output ce_slow,
output reg ce_reg_fast,
output reg ce_reg_slow);
parameter Fclk = 80000000 ; //80 MHz
parameter Ffast = 8000000 ; //8 MHz for simulation
parameter Fslow = 1000000; //1 MHz for simulation
localparam FastWidth = `CLOG2(Fclk / Ffast);
localparam SlowWidth = `CLOG2(Ffast / Fslow);
reg[FastWidth - 1 : 0] ct_fast = 0;
reg[SlowWidth - 1 : 0] ct_slow = 0;
assign ce_fast = (ct_fast == (Fclk/Ffast) - 1) ;
assign ce_slow = ce_fast & (ct_slow == (Ffast/Fslow) - 1) ;
always @(posedge clk) begin
ct_fast <= ce_fast? 1'b0 : ct_fast + 1'b1 ;
ct_slow <= ce_slow? 1'b0 : ce_fast? ct_slow + 1'b1 : ct_slow ;
ce_reg_fast <= ce_fast;
ce_reg_slow <= ce_slow;
end
endmodule
Использование localparam позволяет сохранять результат действий над обычными параметрами, и не позволяет их изменить "силой".
Теперь про новый формирователь импульсов от кнопки. Вот его код на verilog:
`include "math.v"
module DebounceMultiplePulses( input clk,
input ce,
input D,
output Q);
parameter PulsePeriod = 10;
localparam DividerWidth = `CLOG2(PulsePeriod);
reg [DividerWidth - 1 : 0] Divider = 0;
reg R1, R2;
wire firstPulse = (~R1) & R2; //первый импульс
wire TC = (Divider == PulsePeriod - 1); //Terminal Count - досчитал-таки!
assign Q = firstPulse | (TC & ce);
always @(posedge clk) begin
R1 <= ce? D : R1;
R2 <= R1;
Divider <= ce? ((R2 | TC)? 1'b0 : Divider + 1'b1) : Divider;
end
endmodule
Итак, входы и выходы здесь в точности те же, можно легко выкинуть из схемы старый модуль и вставить на её место новый - всё заработает.
Определён параметр PulsePeriod - промежуток времени между импульсами, выдаваемых на выход. Меряется в количестве пришедших ce. Т.е, если PulsePeriod = 1000, то через каждые 1000 импульсов ce будет выдаваться один выходной импульс Q, и так до тех пор, пока нажата кнопка!
Если на собственном счётчике для формирования ce мы сэкономили, то здесь сэкономить и воспользоваться, к примеру, 2-герцовым выходом GenHID очень проблематично... Ведь если мы нажмём кнопку как раз перед приходом очередного импульса ce_slow, то интервал не будет выдержан - вслед за первым импульсом (ответ на нажатие кнопки) сразу же проскочит и второй.
Разве что можно было бы считать не миллисекунды, а более крупными дискретами, где-нибудь по 10 мс, а можно и 50 мс. Благодаря макросам мы теперь можем довольно легко с этим играться.
Опишем, что здесь происходит.
Регистры R1 и R2 работают в точности так же, как в прошлый раз - первый "защёлкивает" состояние кнопки через каждую миллисекунду (или чему там равен импульс ce), второй - задерживает это состояние ровно на один такт.
Выражение (~R1) & R2 сохранилось, но теперь оно поступает не напрямую на выход, а в промежуточную "переменную" firstPulse.
Введён регистр для счетчика, причем мы гарантируем, что он сможет досчитать до величины PulsePeriod - 1.
Как видно, значение счетчика обновляется только во время поступления импульса ce, скажем, раз в миллисекунду. И обновляется оно следующим образом: если кнопка отпущена (R2 равно единице), счётчик держится на нуле. Когда мы понимаем, что кнопка нажата, и даже сформировали первый импульс (т.е уже и R2 стало нулём), счётчик наконец-то начинает свою работу - прибавляет единичку при каждом импульсе ce, и так пока кнопка остаётся нажатой (R2 нулевое), либо пока он не досчитает до PulsePeriod - 1. В этот момент TC примет значение 1, и уже к следующему такту счётчик обнулится и пойдёт по-новой.
Наконец, изучаем, как формируются выходные импульсы:
assign Q = firstPulse | (TC & ce);
самый первый импульс проходит без проблем. А кроме него, импульсы поступают и в моменты, когда счётчик дошёл до конца. Но поскольку TC держится на единичном уровне в течение целой миллисекунды (промежуток между ce), мы вырезаем из него всего один такт, объединив с ce.
Заменив старый ButtonDebounce на эту штуковину в общей схеме, и запустив симуляцию всё с тем же самым Vector Wave Form, мы получим такую картину:
Всё верно. Можно заметить интересную особенность - на отпускание кнопки модуль реагирует не сразу, успевая выдать ещё один импульс на выход. Учитывая, что дребезг длится порядка миллисекунды, это не страшно, "в пределах погрешности".
Если теперь переправить назад все параметры:
Ffast = 1000
PulsePeriod = 1000
и прошить данный проект в ПЛИС, то при нажатии и удержании кнопки, светодиод начнёт мигать, пока мы кнопку не отпустим.
Для разнообразия сделаем "бегущий" огонёк слева направо, но бежит он только пока нажата кнопка.
Создаём ещё один небольшой модуль:
module CounterTo8(input clk, input ce, output reg [2:0] Q = 0);
always @(posedge clk) if (ce)
Q <= Q + 1'b1;
endmodule
Считать до степени двойки - это вообще халява! Кстати, обратим внимание: когда Q доходит до 7 (3'b111), по следующему счетному импульсу он "перевернётся" в ноль, то есть все операторы сложения и вычитания в verilog работают "без насыщения". И вообще, здесь все числа по умолчанию считаются целыми беззнаковыми.
И опять обращаем внимание на гибкий синтаксис - если мы объявили один из выходов с ключевым словом reg, то инициализировать его можно прямо здесь же.
Соединяем все модули между собой:
Запускаем моделирование:
Всё чётко: когда нажимаешь кнопку, светодиоды начинают переключаться один за другим.
Наконец, повторно переправив параметры и откомпилировав, мы можем прошить ПЛИС.
Видеоролики мигающих лампочек могу выложить ближе к ночи.
Про что дальше написать - про 7-сегментник или про UART? Или по кватернионам соскучились?