Первое впечатление от ПЛИС осталось неоднозначным, с одной стороны это интересные проекты и возможности в недалеком будущем, но это также и другой подход к решению проблем, следовательно нужно учиться абсолютно с нуля. Чтобы совсем не потеряться в поисках информации, будем последовательно решать задачи.
При изучении всякой новой технологии общепринято начинать с программ hello world. Некоторый уникумы хватаются сразу за серьезные проекты и бросают навсегда, из-за того что не осилить. Мы же этого делать не будем, медленно медленно спустимся и … 🙂
Я всегда начинаю с того, чтобы как то начать взаимодействовать с железякой — визуализировать информацию, в прошлый раз это было с помощью кнопки. Но согласитесь это не интересно, подобную схему можно реализовать имея резистор и кнопку, без всяких микроконтроллеров и ПЛИС. Кроме того особый интерес представляют всякого рода временные задержки, поэтому куда интереснее сделать светодиодную мигалку.
Задача — помигать светодиодом. Оказалось сделать это не так и просто, сформулировать решение просто — включил ножку, подождал, выключил (инвертировал), подождал. С включением и выключением проблем нет, можно просто инвертировать состояние например так a = !a;
Основной проблемой стали задержки. Как сделать так, чтобы логические элементы срабатывали не моментально, а с некой выдержкой. На борту у ПЛИС ничего вроде таймера нет, да и вообще ничего колеблющегося нет — только логика, поэтому очевидное решение — нужно как то использовать кварц на плате. Если у вас нет кварца, то ничего не выйдет, хотя есть мнение, что можно использовать внутренний генератор, но так глубоко я пока не копал.
Теперь представим ситуацию, как имея только логические элементы и 100МГц на борту, нужно сделать всего 0,1-1Гц чтобы помигать светодиодом. Решение в лоб нагуглилось легко — нужно поделить частоту до требуемых значений, возможно вам это известно, но все же попробуем разобраться.
Вначале нужно разобраться что такое D-триггер, взглянем на рисунок — имеется несколько пинов, основные которые нас интересуют D и Q, clk. Работает он так: чтобы мы не подавали на вход D, на выходе Q ничего не будет происходить пока на ножку clk не подаются импульсы.
Но как только на ножку clk пойдет импульс, то состояние ножки D будет «скопировано» на ножку Q, т.е. если на входе была логическая единица, то и на выходе установится логическая единица, если ноль, то соответственно и на выходе 0. Обновляться состояние выхода будет с частотой подаваемой на ножку clk. Если ничего не подается, то на выходе будет сохраняться состояние, полученное с последним импульсом. Такой триггер называют еще Flip-flop.
Чем это удобно? Допустим, нужно управлять 24 светодиодами, для этого вам понадобится 24 ножки микроконтроллера. Вместо этого, юзаем три регистра у каждого 8 таких триггеров, т.е. 8 входов и 8 выходов. Так как на выходе данные не появятся пока не будет подан импульс, то все ножки данных можно объединить и управлять по очереди. Итого понадобится 8 ножек данных и 3 ножки для подачи импульсов на каждый из триггеров, т.е. 11 ножек вместо 24. Подобный подход заюзан в схеме светодиодного куба.
Как же это поможет в мигании светодиодом? Данные в D-триггерах обновляются один раз за импульс, либо по нарастанию сигнала, либо по спаду. Теперь небольшая извращенская хитрость у триггера имеется инверсный выход, который соединяется со входом.
В первый момент включения, на входе D — 0, на выходе Q тоже ноль, следовательно на инверсном выходе появится логическая единица, ибо это «не» Q. Эта единица пойдет на вход D и при подаче тактового импульса, единица пойдет на выход Q, соответственно на инверсном выходе Q окажется ноль. В следующий такт, все снова поменяется местами. Таким образом, для одного периода на инверсном Q, потребуется два такта сигнала подаваемого на Clk. Вот таким образом и получается делитель частоты. Если не понятно — рекомендую собрать и проверить в том же протеусе или мультисиме.
Пробуем реализовать делитель на Verilog. Приноровиться к новому языку оказалось довольно не просто. Имеется модуль sim1 — название программы, есть некий output led0 — выход, которым мы в последствии будем дрыгать, чтобы мигать светодиодом. Вход для тактовой частоты clk. Регистр counter, на манер языка си это переменная, куда будем писать данные.
module sim1 ( output led0, input clk, output reg counter = 1'b0 ); assign led0 = counter; always @(posedge clk) begin counter = !counter; end endmodule |
Как работает эта программа? Регистр counter является однобитным, изначально запишем в него ноль output reg counter = 1’b0. Строкой assign led0 = counter; мы присваеваем значение регистра counter нашему светодиоду. Независимо от остальной программы, это условие будет всегда выполняться само по себе.
Теперь перейдем к процедурному блоку, он начинается строкой always @(posedge clk) читать эту строку можно так: всегда выполнять код от begin до end, при нарастающем фронте ножки clk. Внутри этого блока мы каждый период тактовой частоты инвертируем состояние регистра(переменной) counter. Конечно велик соблазн написать так:
begin led0 = !led0; end |
т.е. просто инвертировать ножку со светодиодом, но Verilog не дает так сделать, присвоение внутри процедурного блока можно делать только для регистров, проводам нельзя ничего присваивать.
Если скомпилировать данную программу и посмотреть Tools-Netlist Viewers — RTL Viewer, то угадайте что мы увидим? Да это же наш D триггер.
Но теперь прикинем, это мы поделили 100МГц пополам, т.е. 50МГц — светодиод будет светиться постоянно на такой частоте. А если серьезно, то это же сколько делителей нужно, чтобы поделить 100 000 000 до 0,1Гц. Тут я задумался о каком нибудь симуляторе, ибо язык непривычный, чтобы проверить все догадки, придется не один раз прошить и есть шанс ничего так и не понять.
Первое что нагуглилось Modelsim Altera Starter Edition. Не знаю на сколько хорош этот симулятор, но мне он помог освоиться. По сути всю программу можно писать и отлаживать в нем, а уже готовое решение компилить в квартусе и прошивать. Можно их как то скрестить, но как то с ходу не получилось, потом буду разбираться. Сейчас просто забили на квартус — отлаживаемся в моделсиме.
Создаем новый проект: File-New Project. Указываем имя проекта, директорию и имя библиотеки (лучше оставить work).
Дальше создаем новый файл, указываем его имя и задаем язык Verilog
Пишем исходник, компилим и нажимаем Simulate — Start Simulation. Выбираем Library-Work, если в начале указывали work.
Переключаемся на вкладку Wave внизу, должно быть как то так.
Перетаскиваем clk и counter, на Wave. Для clk указываем clock. Запускаем симуляцию Run или F9.
Все выполняется с указанным шагом. Работает как задумано, на каждые два такта тактовой частоты, приходится один период counter.
Теперь в наших руках мощное оружие отладки 🙂 пора бы уже допилить эту мигалку, первое о чем подумалось — сделать некую переменную, которая бы отсчитывала временные периоды. Но даже так, за одну секунду у нас 100 000 000 тиков, сложно представить себе размер переменной, которая бы хранила такие числа. Для справки: регистрам можно задавать их размерность, т.е. количество хранимых бит, например 8 битный регистр:
reg[7:0] counter = 0’b00000000
В общем я сделал так: запускаю первый 12 битный счетчик, он считает до 4096, инвертирует некий промежуточный бит on, таким образом on будет работать на частоте 100 000 000/ (4096*2) ~ 12кГц,
дальше прицепляюсь к нарастанию фронта on и делю его еще одним 12 битным счетчиком 12 207/(4096*2) получилось примерно 1,5Гц. В результате получился такой код:
module sim1 ( input wire key0, output led0, input clk ); reg[12:0] counter = 1'b0; reg on = 1'b0; reg [12:0] counter2 = 1'b0; reg on2 = 1'b0; assign led0 = on2; always @(posedge clk) begin counter = counter + 1'b1; if(counter == 4095) begin on = !on; end end always @(posedge on) begin counter2 = counter2 + 1'b1; if(counter2 == 4095) begin on2 = !on2; end end endmodule |
Вполне очевидно, что можно это реализовать проще, например повесить нормальный часовой кварц и нормально поделить на степень двойки, но я ориентировался на то что было 🙂 По поводу кварца, в симуляции все работало замечательно, а на железке нет, дело в том что кварц посажен на ножку 75 и в Pin planner нельзя забывать указать это для clk, иначе работать не будет.
Получилась немного сумбурно, но главный восторг — все работает, я гарантирую.
Проект квартуса
» Вместо этого,
юзаем три триггера у каждого по 8
входов и 8 выходов, так как на выходе
защелки данные не появятся пока не
будет подан импульс, то все ножки
данных можно объединить и управлять
по очереди.»
Может имелось в виду — 3 регистра по 8 триггеров? 🙂 И защелкой, кажись, называеться D-триггер, работающий по уровню.
Тип инпут и аутпут — разве это провода? Ими ж как-бы описываються входы и выходы модуля. Для проводов свой тип должен быть, аля node или как-то так. Или не? 🙂
А вообще — статья классная 🙂 Знакомлюсь с верилогом. Еще один вариант упрощения кода — просто один длиинный-длинный счетчик-делитель частоты 🙂
К стати, 12-разрядный счетчик сам сбрасываеться на 4096 импульсе, По-этому вместо on можна попробовать использовать 12 разряд первого счетчика. По крайней мере — в AHDL можна было, может и тут можна 🙂
Пока еще сложно врубиться сходу в Verilog — выдержка с сайта марсохода «Модули имеют входы и выходы, которые ведут себя как сигналы wire», я интерпретировал что провода, это и есть входы/выходы. Возможно это не так, пока не понятно. С триггерами поправил 🙂 12 разряд простирается от 2 048 до 4095, так что его заюзать не получится
Согласен, нам на то, чтоб врубиться в AHDL на лабах тоже время достаточно ушло 🙂 Правда — нас основательно спасала книга, в которой он описан. Может и вам поискать какую-нибудь даже он-лайн книгу по верилогу? Может поможет 🙂
А с счетчиком — согласен, затупил. Но тогда триггер on смело можно заменить 13 разрядом в counter 🙂 Счетчик досчитал до 4095 — 13й разряд установился, остальные — сбросились. Еще раз досчитал — 13й и все остальные — сбросились)
Да, может это и необязательно, но можна указать, что D-триггер, у которого инверсный выход подключен на вход даных — это Т-триггер? 🙂
Насколько я понимаю у плисины тоже есть своя память, так вот что лучше заюзать 13 битную переменную или 12 битную + однобитную? Думаю второй вариант менее затратный. Опять же, я не вдавался в подробности, но вроде бы как в квартусе ограничение, он обрезает все переменные до 12бит. По поводу триггеров, я их в своей практике обычно не юзаю, буду потихоньку изучать — добавлю.
ЗЫ: особого гемора с верилогом нет, просто скорее непривычно.
На счет того, что меньше расходует память — не знаю. Как я понимаю, там, дэ-факто, для каждого внутреннего проводника отдельная ячейка памяти, и как бы мы чего не описывали, мы просто будем замыкать те или другие «проводки», т.е. разници не будет никакой. Но точно не скажу, это только догадки 🙂
Видел проект на верилоге с 27-разрядным регистром 🙂
Могу посоветовать более удобный и мощный симулятор — Active HDL, Он куда очевиднее ModelSim и позволяет делать больше интересных и полезных штук — рисовать FSM(конечные автоматы) и генерировать их них код, вычислять покрытие тестов и визуализировать эти данные, исполнять Tcl скрипты и ещё куча-куча всего. Ну и SystemVerilog конечно же, но он нужен для отладки очень больших проектов.
Про непривычно да, вы верно отметили))) Я наоборот начинал с верилога, а потом пересел на микроконтроллеры и долго не мог осознать, что в Cи нету аналога always, Зато после того как свой таймер написал на верилоге стало как-то проще его прогать в готовой железке.
А по поводу кода я бы сделал так. Добавил бы асинхронный сброс и привязал бы его к ножке (если это конечно возможно)
module sim1
(
input wire key0,
output led0,
input clk
);
reg[12:0] counter = 1’b0;
reg on = 1’b0;
reg [12:0] counter2 = 1’b0;
reg on2 = 1’b0;
assign led0 = on2;
always @(posedge clk щк negedge reset)
begin
if(!reset)
counter = counter + 1’b1;
if(counter == 4095)
begin
on = !on;
end
end
always @(posedge on)
begin
counter2 = counter2 + 1’b1;
if(counter2 == 4095)
begin
on2 = !on2;
end
end
endmodule
Случайно отправил коммент раньше времени
module sim1
(
input reset,
input wire key0,
output led0,
input clk
);
reg[12:0] counter;
reg on;
reg [12:0] counter2;
reg on2;
assign led0 = on2;
always @(posedge clk or negedge reset)
if(!reset)
begin
counter<=12'h0;
on<=1'b0;
else
begin
if(counter == 4095)
on <= !on;
else
counter <= counter + 1′b1;
end
always @(posedge on or negedge reset)
if(!reset)
begin
counter<=12'h0;
on2<=1'b0;
end
else
if(counter2 == 4095)
on2 <= !on2;
else
counter2 <= counter2 + 1′b1;
endmodule
<= — это неблокирующее присваивание. От блокирующего оно отличается тем, что выполныется не сразу по коду, а только после окончания всего always блока. Для синхронных схем правильнее использовать неблокирующее присваивание, чем блокирующее потому что синтезатор правильнее интерпретирует его и использует D-триггеры, а не что-то ещё. Не знаю как в ПЛИС, при разработке ASIC это важно. Рискну предположить что большой проект может просто не поместиться в кристалл, если синтезатор напихает туда не то, что там должно быть. Добавление сброса это скорее для успокоения души — опять же с точки зрения синтезатора это важно. И вам спокойнее — сбросили схему один раз, она инициализировалась как надо и всё хорошо.
Константин, спасибо, на выходных гляну.
Константин on 21.05.2014.
Попробовал код в xilinx не проходит …. Точно в квартусе проходит ? :))
Дмитрий on 01.07.2014 в 23:57
Если добавить пару begin-end-ов, которые я по глупости забыл то всё работает=)
подскажите а как задавать определённые входные сигналы на входы а не только клок в modelsim
сейчас не вспомню, ибо моделсим не стоит, но там было очень просто, в определенный момент времени на диаграмме просто выбираешь ноль или единица.