Очередная серия про ПЛИС и отладочную плату Френки. Предыдущие серии 1, 2, 3.
Сделать контроллер елочных гирлянд не просто, а очень просто! Hello World на ПЛИС — это помигать светодиодом. А "С новым годом" на ПЛИС — это помигать несколькими светодиодами. Принцип прост, как и в предыдущих статьях: создаем счетчик, который делит частоту тактового генератора, выбираем биты из слова счетчика, для получения нужной скорости. Несколько бит из этого слова дадут нам определенный шаг отображения (в зависимости от количества выбранных бит 1, 2, 4, 8 и т.д. шагов). В зависимости от номера шага задаем значения для N светодиодов.
Для управления реальной гирляндой, можно взять какой-нибудь shield с электромагнитным реле. У меня оказался вот такой, на 8 реле. Схема подключения. Принципиальная схема.
Основа проекта
module epm7064_test(clk, leds);
input wire clk; // вход тактового генератора
output wire [7:0] leds; // 8 выходов на светодиоды/реле
reg [31:0] cnt; // счетчик для определения интервалов и определения шагов отображения
always @(posedge clk) cnt <= cnt + 1'b1; // с каждым тактом увеличиваем счетчик
assign leds = ... // тут будет логика управления светодиодами
endmodule
Обычное чередование
При обычном чередовании лампочек, мы на первом шаге зажигаем четные лампочки, а на втором — нечетные. Шагов 2, поэтому из слова счетчика нам потребуется всего один бит. Номера лампочек на каждом шаге зададим словом — константой.
...
wire [7:0] variant1 = (cnt[24]) ? 8'b01010101 : 8'b10101010;
assign leds = variant1;
...
Бегущий огонек
Огоньку, чтобы пробежать через 8 лампочек, нужно 8 шагов. Для этого из слова счетчика нужно выбрать 3 бита (2^3 = 8). Стоит заметить, что вывод у нас получается инвертированным, так как для включения реле, мы должны выдать 0.
...
wire [7:0] variant2 =
(cnt[24:22] == 3'd0) ? 8'b11111110 :
(cnt[24:22] == 3'd1) ? 8'b11111101 :
(cnt[24:22] == 3'd2) ? 8'b11111011 :
(cnt[24:22] == 3'd3) ? 8'b11110111 :
(cnt[24:22] == 3'd4) ? 8'b11101111 :
(cnt[24:22] == 3'd5) ? 8'b11011111 :
(cnt[24:22] == 3'd6) ? 8'b10111111 :
(cnt[24:22] == 3'd7) ? 8'b01111111 : 8'b11111111;
assign leds = variant2;
...
Обычное чередование v2
То же самое, что и в первом варианте, только объединим лампочки парами.
...
wire [7:0] variant3 = (cnt[24]) ? 8'b00110011 : 8'b11001100;
assign leds = variant3;
...
Двойные бегущие огоньки
Это вариация одиночного бегущего огонька, но тут потребуется в два раза меньше шагов
...
wire [7:0] variant4 =
(cnt[24:23] == 2'd0) ? 8'b11001100 :
(cnt[24:23] == 2'd1) ? 8'b01100110 :
(cnt[24:23] == 2'd2) ? 8'b00110011 :
(cnt[24:23] == 2'd3) ? 8'b10011001 : 8'b11111111;
assign leds = variant4;
...
Случайные огоньки
Это самая интересная часть. Для того, чтобы сгенерировать псевдослучайную посделовательность, я использовал LFSR. Примеры кода можно прямо брать по этой ссылке. Я взял на наименьшее количество бит. Алгоритм уже проверенный, но почему-то он никак не хотел работать! Причина оказалась в том, что в регистре не должно быть нулевого значения, а при включении ПЛИС там получается ноль в каждом бите. Но я же применил секцию Initial!!! В общем, за не любовью читать документацию, истина пришла через опыты. На ПЛИС серии EPM7000 не работают секции initial. Та же проблема, кстати, была и на Lattice 4064. Все это дает основания полагать, что в более крупных ПЛИС класса Cyclone, блоки initial, генерируются в какой-то дополнительный hardware кусок. А для того, чтобы сделать инициализацию в EPM я добавил еще один счетчик для генерации сигнала сброса. При поступлении этого сигнала, регистрау генератора псевдослучайной последовательности присваивается изначальное значение. После этого "генератор" заработал.
Из соображений экономии ресурсов, я не стал создавать по генератору на каждый бит. Скорее всего, не хватило бы ресурсов. Дело в том, что генератор может выдавать следующее значение на кадом такте, а обновлять лампочки нам нужно не на каждом такте, поэтому один генератор используется для заполнения значений всех лампочек.
...
reg [2:0] res_gen_reg; // счетчик генератора сброса
wire reset = ~(res_gen_reg==3'b111); //линия сброса, на ней будет 1, пока счетчик не досчитает до 8
always @(posedge clk) begin
// увеличиваем счетчик генератора сброса, но до тех пор, пока он де достигнет
// максимального значения. после этого, мы оставляем его в том же значении
// чтобы он не переполнился и не начал считать заново. бесконечно повтояющийся
// сброс нам не нужен
res_gen_reg <= (res_gen_reg==3'b111) ? res_gen_reg : res_gen_reg + 1'b1;
end
reg [7:0] variant5; // значение лампочек придется хранить и менять по очереди, поэтому нужен регистр
reg [7:0] d; // регистр для генератора псевдослучайной последовательности
always @(posedge clk) begin
if (reset) begin
d <= 1; // задаем начальное значение
end else begin
if (cnt[20:0]==21'd0) begin // раз в определенный промежуток времени
d <= { d[5:0], d[6] ^ d[5] }; // определяем новое значение генератора
// а текущее заносим в один из битов регистра лампочек
variant5[cnt[23:21]] <= d[5]; // по очереди все 8 получат свое значение
end
end
end
assign leds = variant5;
...
Объединяем все вместе
У нас еще остались ресурсы ПЛИС, поэтому мы можем взять старшие биты счетчика и использовать их для переключения типа бегущих огоньков. Проще, когда типов будет 2, 4, 8, но у нас их пять. Поэтому, я их буду перебирать 4 из них, а в промежутках между ними вставлю пятый вариант (вернее первый). В общем, тут поле для экспериментов. Свободные ресурсы еще есть, потому что потрачено 58 ячеек из 64х.
assign leds =
(cnt[29:27] == 3'd1) ? variant2 :
(cnt[29:27] == 3'd3) ? variant3 :
(cnt[29:27] == 3'd5) ? variant4 :
(cnt[29:27] == 3'd7) ? variant5 : variant1;
module epm7064_test(clk, leds);
input wire clk;
output wire [7:0] leds;
reg [31:0] cnt; initial cnt <= 32'd0;
always @(posedge clk) cnt <= cnt + 1'b1;
wire [7:0] variant1 = (cnt[24]) ? 8'b01010101 : 8'b10101010;
wire [7:0] variant2 =
(cnt[24:22] == 3'd0) ? 8'b11111110 :
(cnt[24:22] == 3'd1) ? 8'b11111101 :
(cnt[24:22] == 3'd2) ? 8'b11111011 :
(cnt[24:22] == 3'd3) ? 8'b11110111 :
(cnt[24:22] == 3'd4) ? 8'b11101111 :
(cnt[24:22] == 3'd5) ? 8'b11011111 :
(cnt[24:22] == 3'd6) ? 8'b10111111 :
(cnt[24:22] == 3'd7) ? 8'b01111111 : 8'b11111111;
wire [7:0] variant3 = (cnt[24]) ? 8'b00110011 : 8'b11001100;
wire [7:0] variant4 =
(cnt[24:23] == 2'd0) ? 8'b11001100 :
(cnt[24:23] == 2'd1) ? 8'b01100110 :
(cnt[24:23] == 2'd2) ? 8'b00110011 :
(cnt[24:23] == 2'd3) ? 8'b10011001 : 8'b11111111;
reg [2:0] res_gen_reg;
wire reset = ~(res_gen_reg==3'b111);
always @(posedge clk) begin
res_gen_reg <= (res_gen_reg==3'b111) ? res_gen_reg : res_gen_reg + 1'b1;
end
reg [7:0] variant5;
reg [7:0] d;
always @(posedge clk) begin
if (reset) begin
d <= 1;
end else begin
if (cnt[20:0]==21'd0) begin
d <= { d[5:0], d[6] ^ d[5] };
variant5[cnt[23:21]] <= d[5];
end
end
end
assign leds =
(cnt[29:27] == 3'd1) ? variant2 :
(cnt[29:27] == 3'd3) ? variant3 :
(cnt[29:27] == 3'd5) ? variant4 :
(cnt[29:27] == 3'd7) ? variant5 : variant1;
endmodule
Выводы
Казалось бы, бессмысленный проект, но позволил узнать про такие нюансы, как неработоспособность initial на этой серии. Раньше я считал, что это в целом особенность производителя (Lattice, Xilinx или Altera). А из комментариев выяснилось, что количество перепрошивки данной ПЛИС всего около 100 раз. Новые знания приходят, значит все это не зря!
Комментарии (11)
Dzhamall
29.12.2016 16:44Схему чего? Того как синтезируется и разложится в фпга конструкция, которую я привел?
Поясните пожалуйста, я не понял.UA3MQJ
29.12.2016 18:09Схему, которая подключена ко входу async_rst_i
jok40
29.12.2016 22:02Сигнал сброса можно сформировать из регистра. Так как инициализация регистров по включению питания в MAX7000 выполняется только в ноль, то сигнал сброса можно сделать так:
То есть сброс получился отрицательным. Соответственно в других always в качестве сброса нужно будет использовать negedge pon_reset_reg:reg pon_reset_reg = 0; always @( posedge clk ) pon_reset_reg <= 1'b 1;
always @( posedge clk, negedge pon_reset_reg ) begin if( !pon_reset_reg ) begin // Здесь инициализируем регистры в то, что надо. end else begin // Здесь выполняем действия по фронту тактов end end
jok40
29.12.2016 22:25+1Уже после того, как написал, заметил, что Вы сброс уже сформировали, причём целый счётчик для этого дела замутили :) Думаю, одного такта будет достаточно (как в моём примере). Проверьте.
Dzhamall
02.01.2017 00:31+1Господин выше все правильно описал, да.
Если есть возможноть сделать кнопку, то общесхемный асинхронный ресет триггеров заводят на неё (правда после кнопки конечно нужно будет поставить триггер, подзащелкивающий этот ресет, чтобы он случайно не попал на фронт клока).
Если нет возможности сделать кнопку, то да, как описано ниже — через initial. Но вообще я бы старался максимально избавляться от initial-ов для триггеров. Это костыль. А для ASIC так вообще несинтезируемая конструкция
P.S: Почему именно асинхронный ресет? Потому что он уже есть в триггерах, и не придется городить лишнюю логику, тем самым отжирая ресурсы и ухудшая быстродействие.
Dark_Purple
29.12.2016 19:08Тиристоры же, никто не любит шумных гирлянд.
sim2q
02.01.2017 18:49В школе когда-то собрал секвенсор на жёсткой логике подключенный к клавишам «электропианино» релюшками. В чём то даже было прикольное совмещение с ударными:)
Dzhamall
Все это дает основание предполагать, что нормальные люди используют асинхронный ресет триггеров в схеме, по включению питания, а не городят костыли :)
UA3MQJ
Ну тогда уж и схему приложите.
jok40
У Вас сброс с тактами местами перепутаны. И «уха» между always и скобками нет.
jok40
А ещё регистр неправильно объявлен. И, кстати говоря, сигнала сброса в конструкции нет — его тоже нужно предварительно сформировать. В остальном согласен — правильнее делать так, как Вы сказали.