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


  1. Dzhamall
    29.12.2016 15:40
    +2

    Все это дает основания полагать....

    Все это дает основание предполагать, что нормальные люди используют асинхронный ресет триггеров в схеме, по включению питания, а не городят костыли :)

    reg[7:0]
    
    always (posedge async_rst_i, posedge clk) begin
       if(async_rst_i) reg <= 8'b1010_0101;
       else            reg <= ХХ;
    end
    


    1. UA3MQJ
      29.12.2016 16:08

      Ну тогда уж и схему приложите.


    1. jok40
      29.12.2016 21:25
      +1

      У Вас сброс с тактами местами перепутаны. И «уха» между always и скобками нет.


    1. jok40
      29.12.2016 21:40
      +1

      А ещё регистр неправильно объявлен. И, кстати говоря, сигнала сброса в конструкции нет — его тоже нужно предварительно сформировать. В остальном согласен — правильнее делать так, как Вы сказали.


  1. Dzhamall
    29.12.2016 16:44

    Схему чего? Того как синтезируется и разложится в фпга конструкция, которую я привел?
    Поясните пожалуйста, я не понял.


    1. UA3MQJ
      29.12.2016 18:09

      Схему, которая подключена ко входу async_rst_i


      1. jok40
        29.12.2016 22:02

        Сигнал сброса можно сформировать из регистра. Так как инициализация регистров по включению питания в MAX7000 выполняется только в ноль, то сигнал сброса можно сделать так:

        reg pon_reset_reg = 0;
        
        always @( posedge clk )
          pon_reset_reg <= 1'b 1;
        
        
        То есть сброс получился отрицательным. Соответственно в других always в качестве сброса нужно будет использовать negedge pon_reset_reg:

        always @( posedge clk, negedge pon_reset_reg )
        begin
          if( !pon_reset_reg )
          begin
            // Здесь инициализируем регистры в то, что надо.
          end
          else
          begin
            // Здесь выполняем действия по фронту тактов
          end
        end
        


        1. jok40
          29.12.2016 22:25
          +1

          Уже после того, как написал, заметил, что Вы сброс уже сформировали, причём целый счётчик для этого дела замутили :) Думаю, одного такта будет достаточно (как в моём примере). Проверьте.


      1. Dzhamall
        02.01.2017 00:31
        +1

        Господин выше все правильно описал, да.
        Если есть возможноть сделать кнопку, то общесхемный асинхронный ресет триггеров заводят на неё (правда после кнопки конечно нужно будет поставить триггер, подзащелкивающий этот ресет, чтобы он случайно не попал на фронт клока).
        Если нет возможности сделать кнопку, то да, как описано ниже — через initial. Но вообще я бы старался максимально избавляться от initial-ов для триггеров. Это костыль. А для ASIC так вообще несинтезируемая конструкция

        P.S: Почему именно асинхронный ресет? Потому что он уже есть в триггерах, и не придется городить лишнюю логику, тем самым отжирая ресурсы и ухудшая быстродействие.


  1. Dark_Purple
    29.12.2016 19:08

    Тиристоры же, никто не любит шумных гирлянд.


    1. sim2q
      02.01.2017 18:49

      В школе когда-то собрал секвенсор на жёсткой логике подключенный к клавишам «электропианино» релюшками. В чём то даже было прикольное совмещение с ударными:)