1i7 (1i7) wrote,
1i7
1i7

Categories:

Лабораторная работа 4, часть 1: простые конечные автоматы (1)

На прошлой лабораторной работе мы научились делать ячейки памяти и вспомнили, как на первой лабораторной работе делали генератор тактовых сигналов. Теперь пришло время посмотреть, как эти инструменты можно использовать на ПЛИС+Verilog и сделать с ними что-нибудь полезное - например, конечный автомат.

Для начала научимся использовать генетатор тактового сигнала - clock. На первой лабораторной работе для его создания мы использовали конденсатор и цепочку из нескольких логических элементов. На плате ПЛИС ничего подобного конструировать не требуется, т.к. в плату уже встроен нужный генератор, который доступен для модулей Verilog в виде обычного внешнего интерфейса ввода (в точности, как кнопка, рычажок или порт ввода-вывода общего назначения PIO) - в частности в плату Digilent Basys 2 встроен генератор сигнала 25МГц.

Пробросить этот сигнал в модуль на Verilog очень просто:

Нужно подключить сигнал в ucf-файле (для Digilent Basys2):
# Pin assignment for clock
NET "clk" LOC = "b8";

и можно сразу использовать его внутри топ-модуля как любой другой входной однобитный сигнал:
module module_top(input clk);
    //...
endmodule

По идее мы могли бы уже здесь назначить входной сигнал тактового генератора на выходной сигнал светодиода, чтобы он мигал на каждый такт как в первой лабораторной работе, но т.к. при частоте 25МГц лампочка будет включаться и выключаться 25 миллионов раз в секунду (1Гц - 1 действие в секунду, 1МГц - миллион действий в секунду), большинство людей подобное мигание скорее всего не заметит (для них диод будет казаться все время горящим, но не в полную силу), поэтому в качестве первого упражнения научимся использовать тактовый сигнал для управлениями действиями на более привычных человеческой физиологии отрезках времени, например соизмеримых с секундой.



Упражнение 1: разделение частоты тактового сигнала

Разделим тактовый сигнал 25МГц так, чтобы он генерировал участки HIGH/LOW/HIGH/... по 1й секунде.



Делаем простой модуль - на входе получает 1бит сигнал clock 25МГц, на выходе выдает один бит сигнал 1Гц (точнее с учетом одного упрощения мы сделаем не 1Гц, а 0.745Гц, но их тоже видно - см ниже).







module clock_divider(
    input clk,
    output one_second
    );

Для реализации заявленного функционала логично использовать счетчик, который будет отсчитывать каждый такт оригинального 25тимегагерцового сигнала и в нужный момент сообщать, что отсчитана секунда.

Для этого счетчика мы можем взять переменную область памяти, в начальный момент времени записать в нее 0 и далее увеличивать значение на 1 на каждый такт генератора пока не досчитаем до 25ти миллионов.

И сразу понимаем, что для создания такой переменной, нам вообще говоря нужен механизм для реализации этой самой памяти, которая будет сохранять каждое новое значение между тактами сигнала. Переменные wire из 2й лабораторной работы хранить значения не позволяют - они могут только соединять через себя внешние входные и выходные сигналы, но к счастью в Verilog есть специальный механизм для работы с памятью - это регистры.

Синтаксис объявления переменной-регистра точно такой же, как синтаксис объявления переменной wire, только вместо ключевого слово wire используется ключевое слово reg:

Однобитный регистр:
reg counter;

Многобитный регистр (bit_num - количество бит в регистре):
reg [bit_num: 0] counter;

Замечание: переменные-регистры из Verilog еще больше похожи на переменные из какого-нибудь языка программирования типа С/С++, чем переменные-wire из Verilog же, т.к. он как и программистские переменные могут сохранять значения. Однако ключевая разница в том (если можно вообще корректно сравнивать язык программирования и язык описания аппаратного обеспечения), что объявление переменной в языке программирования обычно объявляет указатель на некоторую область доступной аппаратной памяти, а объявления регистра в Verilog можно сказать объявляет эту самую доступную аппаратную память как таковую.

Очевидно, что однобитный регистр для счетчика нам не подойдет, т.к. с ним можно досчитать только от нуля до одного, а нам нужно досчитать до 25ти миллионов. Значит для счетчика создадим многобитный регистр, который позволит досчитать до 25ти миллионов. Конвертируем десятичные 25 миллионов в двоичный вид:

25'000'000 = b1'0111'1101'0111'1000'0100'0000 (25 бит).

получили 25тибитное значение (не уверен, имеет ли количество двоичных бит прямое отношение к количеству десятичных миллионов - возможно этому совпадению есть математическое объяснение, но в данном случае это разные 25).

Таким образом, когда значение переменной счетчика примет указанное выше значение, это будет обозначать, что мы досчитали до 25ти миллионов и прошла ровно одна секунда - в этот момент в общем нужно зафиксировать успех и отправить сигнал наружу, т.е. похоже, что нам нужен 25тибитный регистр.. Но т.к. нам такая точность в общем не нужна, немного упростим себе жизнь - успех будем фиксировать в том момент, когда 25тибитный счетчик будет переполнен, т.е. 26й старший бит переменной примет значение "1". Такой подход позволит нам следить за значением всего одного 26го бита, хотя мы потеряем точность - вместо 1Гц на выходе получим 0.745Гц, но при текущих условиях это допустимо - для реализации этого подхода увеличиваем регистр на 1 бит и объявляем для счетчика 26тибитный регистр:

reg [25, 0] counter;

Регистр объявили, теперь нужно превратить его в счетчик, который будет увеличиваться на 1 на каждый такт сигнала clk. Специально для таких задач в Verilog существует конструкция "always @":

always @(posedge clk)
    counter <= counter + 1;

Которая состоит из 2х частей:
1. Внутри скобок находится "список чувствительности" (sensitivity list), в котором перечислены условия, при осуществлении которых будет отработан блок действия (следующий пункт).
2. Блок действия - все, что находится внутри конструкции always - в случае однострочного блока - это следующая строка за always, если нужно поместить несколько строк, можно отметить блок маркерами begin/end (аналог фигурный скобок {} в C/C++).

В качестве условий из первого пункта можно перечислять сигналы (внутренние переменные и входы модуля), а триггером к их выполнению является изменение значения одного из сигналов из списка чувствительности. В данном случае блок always будет отрабатывать каждый раз, когда значение сигнала clk будет меняться с 0 на 1, т.е. на положительном ребре сигнала (25 млн раз в секунду) - дополнительное условие положительности накладывается ключевым словом posedge (positive edge) - как такое можно делать в железе вспоминаем D-триггер из предыдущей лабы.



Блок действия - "counter <= counter + 1" - увеличивает значение counter на 1 по сравнению с предыдущим значением, оператор '<=' используется для присвоения значений внутри блока always. Левый операнд оператора (counter) должен быть обязательно объявлен как регистр reg.

Итого получили, что значение регистра counter будет увеличиваться на 1 25 миллионов раз в секунду, т.е. на каждый такт периодического сигнала.

Теперь просто подключаем к 26му старшему биту регистра counter выходной сигнал one_second модуля - теперь выход one_second будет иметь значение 1, когда 26й бит регистра counter будет равен 1 и будет иметь значение 0, когда 26й бит регистра counter будет равен 0 - модуль закончен:
assign one_second = counter[25];

Напоследок рассмотрим поведение интересующего нас старшего 26го бита.

В момент первого отсчета от 0я к 25ти миллионам значение 26го бита будет равно нулю до тех пор, пока до него не добегут младшие единички - промежуточные значения будут выглядеть примерно так:
0'00101...1101

в последний момент перед превращением 26го бита из 0 в 1 все младшие биты будут равны 1:
0'11111...1111

До этого момента счетчик насчитает более 25ти млн. тактов, т.е. пройдет чуть больше секунды, т.е. 26й бит регистра будет держать значение 0 примерно одну секунду.

а на следующий такт все станет наоборот - старший 26й бит превратится в 1, а все остальные биты обнулятся:
1'00000...0000

Таким образом, мы получим почти исходное состояние, отличие будет только в 26м бите, который теперь имеет значение 1 вместо 0. Единички снова побегут наверх через 25 бит и как уже выше стало понятно, до 26го бита они доберутся опять же примерно через секунду, т.е. старший 26й бит теперь будет держать значение 1 опять же примерно одну секунду.

В последний момент, когда весь регистр будет забит единицами и произойдет следующий такт, операция сложения приведет к переполнению регистра - старшая единица отправится в 27й бит, которого не существует, а остальные 26битов заполнятся нулями, т.е. мы получим исходное нулевое состояние и круг замкнулся.

Таким образом, 26й старший бит регистра (а вслед за ним и выход модуля one_second) будет держать значение '1' 1 одну секунду (примерно - с учетом допущения выше) и будет держать значение 0 одну секунду же - это именно то, что мы хотели. В этом можно убедиться, подключив к нему в модуле верхнего уровня лампочку.

Весь код:
clock_divider.v
/**
 * Divides 25MHz clock to one second.
 */

module clock_divider(
    input clk,
    output one_second
    );

    reg [25:0] counter;

    // count up on each positive clock
    always @(posedge clk)
            counter <= counter + 1;

    // one second signal would be emitted when
    // 26th bit of the counter would become "1"
    assign one_second = counter[25];
endmodule

clock_divider_top.v
/**
 * Top module to test clock divider -
 * blink lamp with 1 second period.
 */

module clock_divider_top(
    input clk,
    output [0:0] ld
    );

    clock_divider count(.clk(clk), .one_second(ld[0]));
endmodule

basys2_clock_divider.ucf
# Pin assignment for LEDs
NET "ld<0>" LOC = "m5" ;

# Pin assignment for clock
NET "clk" LOC = "b8";

Код в действии:










Продолжение "Упражнение 2: знакомство с конечными автоматами - светофор" следует >>
Tags: verilog, плис, цифровая электроника для программистов
Subscribe

  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 3 comments