nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Счётчик в кодах Грея - продолжение

Начало здесь.

Пора наконец-то представить "широкой общественности" универсальный, параметризуемый модуль счётчика в кодах Грея на верилоге!

Чтобы сообразить, как он работает, сначала вспомним, как работает самый обычный двоичный счётчик:

0000
0001
0010
0011
0100
0101
0110
0111
1000
...


Самая простая разновидность - это "счётчик пульсаций", он же "асинхронный счётчик", он же ripple counter. Самый младший бит переключается по фронту тактовой частоты, а каждый следующий бит переключается ровно тогда, когда ПРЕДЫДУЩИЙ БИТ переключается с единички на нолик. Если взглянуть на ряд чисел выше, увидим ровно такую закономерность.

Но на ПЛИС делать такие счётчики крайне не рекомендуется, там используют СИНХРОННЫЙ счётчик. Смысл в том, что каждому биту "заранее сообщается", должен ли он переключиться по ближайшему фронту тактовой частоты.

Младшему биту всегда надо переключаться. Следующему за ним - только когда в младшем бите единичка. Это значит, по фронту произойдёт перенос, поэтому и нам надо переключиться. Предпоследнему биту (из нашего примера) надо переключиться, если два младших бита единичных, только тогда произойдёт перенос в наш бит, и так далее. Если использовать D-триггеры, получим такую вот схему:



В младшем бите каждый такт текущее значение меняется на противоположное.

В следующем стоит элемент "исключающее ИЛИ", XOR, я его рисую в российских традициях, "=1". То бишь, он выдаёт "1" на выходе, если сумма входов равна именно 1, в противном случае: "0". Если в младшем разряде нолик, то по фронту тактового импульса будет занесено то же самое значение, что и лежало раньше, ничего не изменится. Если же единичка - произойдёт переключение.

И тут же мы объединяем по "И" выходы Q[0] и Q[1], чтобы управлять переключением следующего разряда, а потом туда прибавляем и Q[2], чтобы управлять переключением Q[3]. И "по инерции" поставив ещё один "И", получаем выход переноса cout (carry out), на котором появляется "1", если у нас на выходе все единицы, "1111". Его можно использовать как признак, что мы досчитали до конца, и следующим тактом снова сбросимся в ноль.

Как ни странно, такая схема очень хорошо ложится на логические элементы ПЛИС, вот рисуночек из ТО на 5576ХС4Т, по сути это перевод даташита на Altera Flex10k:


Логические элементы специально для этого (а также для сумматоров) объединены быстродействующими цепочками переноса. Генератор функций (ГФ, он же LookUp Table, LUT) на 4 входа "расщепляется" на два отдельных, по 3 входа. То есть на двух выходах можно задать произвольную функцию от 3 входов. Вот как раз верхний используется для управления триггером, он реализует наше "исключающее ИЛИ", а нижний - для формирования переноса на следующий разряд, он реализует "И".

Получается возможным ввести дополнительные входы:

- вход разрешения счёта cnt_en / разрешения работы clk_en (data1). Если скоммутировать его на вход ENA триггера, это станет clk_en, то есть триггер и не подумает изменить своё состояние, пока clk_en=0, т.е в этот момент нельзя будет ни "прибавить единичку", ни сбросить, ни загрузить какое-то значение. А если поставить ENA=1, зато правильно задать верхний генератор функций, можно сделать cnt_en: пока cnt_en=0, единичка прибавляться не будет, а вот синхронный сброс или загрузка - пожалуйста!
- вход синхронного сброса (data2).
- вход данных (data3) и разрешения синхронной загрузки (data4).

И всё это великолепие по сути на N логических элементах (LE) для N-битного счётчика!

А теперь проанализируем методом "пристального взора" счёт в кодах Грея, только справа добавим дополнительный, "фиктивный" (dummy) разряд, который попросту переключается на каждом такте, причём стартует с единицы:

0000 1
0001 0
0011 1
0010 0
0110 1
0111 0
0101 1
0100 0
1100 1
1101 0
1111 1
1110 0
1010 1
1011 0
1001 1
1000 0


Наблюдаем следующую логику. Младший разряд кодов Грея переключается только когда "фиктивный" бит равен единице.

Следующий за ним разряд переключается только когда справа от него возникает комбинация "10".

Предпоследний разряд переключается только когда справа от него комбинация "100".

И наконец, старший разряд переключается по комбинации "1000", но если мы реализуем ровно такую логику, мы, перебрав все 16 позиций, не вернёмся снова в нулевую, т.к на самой нижней строке справа вовсе не "1000", а переключить старший разряд с 1 на 0 надо! Так что, если мы хотим кольцевой счёт (как это обычно и бывает), нужна дополнительная логика, которая обнаружит "завершение" и позволит старшему разряду переключиться назад. Но для моей задачи, как ни странно, это и не обязательно, интереснее даже посмотреть, что будет без этой логики.

Нарисуем схему, которая могла бы осуществлять такой счёт:


Схема переноса ничуть не усложнилась, только если раньше там стоял элемент "И", то теперь: "ИЛИ", таким способом мы ищем "все нули". Но теперь на каждый триггер приходится тащить ещё выход с предыдущего разряда, что немного усложняет схему, так что теперь так много "дополнительных плюшек" навесить на неё не получится!

Кстати, cout, возле которого я поставил знак вопроса, даёт практически что нам надо: он практически всегда равен "1", но аккурат на последнем отсчёте здесь возникает "0". И ещё становится понятнее, как нам переделать последний разряд, чтобы он с финальной позиции перешёл на самую первую. Надо просто проигнорировать вход с предыдущего разряда. То мы всё проверяли, чтобы он был единичный, а на самом деле нам и ноль подойдёт. В общем, на верхний вход элемента "И" подаём "1" всегда, и он превращается в "НЕ" для нижнего входа.

Если теперь сопоставить такую схему с режимом "сбрасываемого счётчика" для логических элементов ПЛИС, мы видим: подходит. С переносом всё элементарно, а на верхний ГФ надо подавать вход переноса (как и раньше), выход текущего триггера (как и раньше) и ВЫХОД ПРЕДЫДУЩЕГО ТРИГГЕРА. Тем самым, о "разрешении счёта" (cnt_en) можно забыть, он сюда "не влезет". Зато "разрешение работы" (clk_en) можно сохранить, равно как и синхронную загрузку данных.

Теперь самое время повторно открыть Application Brief 135, и всё написанное там внезапно обретает смысл :)

Только вместо D-триггеров они пишут о T-триггерах (Toggle, "переключение"), ну D-триггер с навешенным спереди XOR - это оно и получается. Я просто хотел понять, как оно "внутри ПЛИС" ложиться будет.

И бит dummy у них инвертирован относительно того, что у нас, тоже логично, все регистры в этих ПЛИС инициализируются нулями!

И вместо "ИЛИ" в цепях переноса используется "И", причём все выходы триггеров кроме dummy предварительно инвертируются. Да, пожалуй, так у нас cout=1 именно на самом последнем отсчёте, прежде чем вернётся во все "нули".

Давайте попробуем всё это дело описать на верилоге!

Начнём с простейшего варианта, без дополнительных пряников:
module GrayCodeCounter (input clk, output reg [Width-1:0] Q = 1'b0,
			output Ddummy);
parameter Width = 4;

reg dummy = 1'b0;	//вводим дополнительный бит справа, "контрольная сумма", такая, что сумма всех битов всегда должна быть нечётной
assign Ddummy = dummy;


Ни сброса, ни разрешения работы, ни синхронной загрузки, ничего :) Выход Ddummy - отладочный (Debug), когда всё нормально заработает, уберу его.

Ввели параметр Width, дополнительный 1-битный регистр dummy, и всё-таки его тоже инициализировали нулём.

Чтобы двигаться дальше, немножко перерисуем схему для младшего разряда кодов Грея:


Слева как было, справа как это можно изобразить, ради единообразия.

Вводим цепь "переноса", вот с ней сложнее всего:

wire [Width-1:0] carryChain;

assign carryChain[0] = ~dummy;

genvar i;
generate
  for (i=1; i<Width; i=i+1) begin	:take_your_stupid_name
    assign carryChain[i] = carryChain[i-1] | Q[i-1];
  end
endgenerate


Немножко укуренный синтаксис: просто цикл for можно ставить лишь внутри always-блока, для присвоения регистров. А для "непрерывного присвоения" с помощью assign нужно всё это заключать в generate / endgenerate. Что ещё страньше, после for нужно указать "имя блока". Вообще по стандарту это "опционально", но Квартус прямо жить без этого имени не может. Поэтому я там и написал ":take_your_stupid_name".

И теперь ещё сформируем сигнал, поступающий на вход триггеров. Тут, к счастью, никаких циклов не нужно:

assign T_input[0] = ~dummy;
assign T_input[Width-1:1] = Q[Width-2:0] & (~carryChain[Width-2:0]);


Это у нас "входы для T-триггера", т.е когда на каждый из них приходит "1", значит нужно переключиться (с единицы на ноль, или с нуля на единицу) иначе сохранить состояние "как было".

И, наконец, запишем переключение наших триггеров, они же регистры. Я раньше считал, что регистр - это "набор триггеров" с совмещёнными управляющими сигналами, но когда здесь ключевое слово reg, как-то начинаю терять различие между регистром и триггером, и то и другое "нечто, хранящее состояние". Вот:

always @(posedge clk) begin
	dummy <= ~dummy;
	Q <= Q^T_input;
end


По-моему, это всё :) Выпишем весь код целиком:
module GrayCodeCounter (input clk, output reg [Width-1:0] Q = 1'b0,
						output Ddummy);
parameter Width = 4;

reg dummy = 1'b0;	//вводим дополнительный бит справа, "контрольная сумма", такая, что сумма всех битов всегда должна быть нечётной
assign Ddummy = dummy;

wire [Width-1:0] carryChain;

assign carryChain[0] = ~dummy;

genvar i;
generate
  for (i=1; i<Width; i=i+1) begin	:take_your_stupid_name
    assign carryChain[i] = carryChain[i-1] | Q[i-1];
  end
endgenerate

wire [Width-1:0] T_input;

assign T_input[0] = ~dummy;
assign T_input[Width-1:1] = Q[Width-2:0] & (~carryChain[Width-2:0]);

always @(posedge clk) begin
	dummy <= ~dummy;
	Q <= Q^T_input;
end

endmodule


Увы, он синтезируется в 9 ЛЭ, что означает: квартус не смог сообразить, как красиво такая схема запихивается в ЛЭ в режиме счётчика, и сделал всё "в лоб". Посмотрим, как оно работает:




Ну красота же! Видно, что на каждом такте переключается только один выход. Здесь я не стал делать специальный код для старшего бита, в результате дойдя до последнего значения 1000, счётчик "развернулся задом наперёд", став проходить те же 16 позиций в обратную сторону!

Остаётся добавить несколько полезных входов: clk_en, sload и D, а также ввести "обычную" версию кода Грея, когда после 1000 всё-таки наступает 0000, и цикл в точности повторяется.

Изучать AHDL, чтобы вместить счётчик в N бит в N ЛЭ я уж не буду пока. Как-нибудь потом :)
Tags: ПЛИС, работа, странные девайсы
Subscribe

  • Так есть ли толк в ковариационной матрице?

    Задался этим вопросом применительно к своему прибору чуть более 2 недель назад. Рыл носом землю с попеременным успехом ( раз, два, три, четыре),…

  • Big Data, чтоб их ... (4)

    Наконец-то стряхнул пыль с компьютерной модели сближения, добавил в неё код, чтобы мы могли определить интересующие нас точки, и выписать…

  • Потёмкинская деревня - 2

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

  • Великая Октябрьская резня бензопилой

    Сегодня прокатился прочистить Абрамцевскую просеку. Как обычно, с приключениями. Выезжал на велосипеде, а вернулся на самокате. Первый раз по этим…

  • Очередная несуразность в единицах измерения

    Когда-то я написал программу PhysUnitCalc - калькулятор, умеющий работать с размерностями. Мне казалось, что я наступил уже на все грабли, которые…

  • Big Data, чтоб их... (3)

    "В предыдущих сериях": мой прибор выдаёт 6 значений: 3 координаты и 3 угла, т.е все 6 степеней свободы твёрдого тела. Причём ошибки измерения этих 6…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments