Очередная серия про ПЛИС и отладочную плату Френки. Предыдущие серии 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 раз. Новые знания приходят, значит все это не зря!