МАРСОХОД

Open Source Hardware Project

Язык описания аппаратуры Verilog HDL

Счетчик в коде Грея

Давайте подумаем, как на языке Verilog можно описать счетчик в коде Грея (Gray code). Такой счетчик может нам понадобиться для реализации асинхронного FIFO. В кодах Грея соседние значения меняются только в одном бите. Это делает возможным безопасную передачу последовательно меняющихся значений кодов из одного клокового домена в другой. Напомню, что в асинхронном FIFO указатели на голову/хвост очереди пересекают clock domain именно в кодах Грея.

Поскольку последовательность чисел в коде Грея нам известна (0,1,3,2,6,7,5...), то первое, что приходит на ум – это написать вот такой код:
module gray_cnt_v1(
   input wire clk,
   input wire nreset,
   output reg [3:0]q
   );

always @(posedge clk or negedge nreset)
  if(~nreset)
    q <= 4'b0000;
  else
    case
(q)
     4'b0000:  q <= 4'b0001;
     4'b0001:  q <= 4'b0011;
     4'b0011:  q <= 4'b0010;
     4'b0010:  q <= 4'b0110;
     4'b0110:  q <= 4'b0111;
     4'b0111:  q <= 4'b0101;
     4'b0101:  q <= 4'b0100;
     4'b0100:  q <= 4'b1100;

     4'b1100:  q <= 4'b1101;
     4'b1101:  q <= 4'b1111;
     4'b1111:  q <= 4'b1110;
     4'b1110:  q <= 4'b1010;
     4'b1010:  q <= 4'b1011;
     4'b1011:  q <= 4'b1001;
     4'b1001:  q <= 4'b1000;
     4'b1000:  q <= 4'b0000;
    endcase
endmodule
Это описание счетчика Грея вполне работоспособно, но оно далеко не самое удачное. Недостаток такого решения – его трудно написать и легко ошибиться при наборе этих двоичных чисел. Второе – код не параметризован. Что если придется писать, скажем, 64-х битный или 128-ми битный счетчик?

Мы знаем, что код Грея можно преобразовать в двоичный код с помощью «Исключающего ИЛИ». Так же и наоборот, двоичный код можно преобразовать в код Грея с использованием этой же операции и операции сдвига. Значит можно написать второй вариант «программы». Попробуем сделать обычный двоичный счетчик, и его выходные значения будем преобразовывать в код Грея:

module gray_cnt_v2(
   input wire clk,
   input wire nreset,
   output wire [3:0]q
   );

reg [3:0]bc; //bin counter
always @(posedge clk or negedge nreset)
  if(~nreset)
    bc <= 4'b0000;
  else
    bc <= bc + 1'b1;

//convert bin to gray
assign q = { bc[3], ^bc[3:2], ^bc[2:1], ^bc[1:0] };
 
 endmodule


Код стал компактнее, но стал ли он от этого лучше? Я бы сказал, что этот вариант даже хуже. В первом варианте gray_cnt_v1 на выходе нашего счетчика стоит регистр, фиксирующий выходное значение по фронту тактовой частоты. В этом легко убедиться, откомпилировав модуль в cреде Altera Quartus II и посмотрев результат в RTLViewer:

Счетчик Грея в Altera Quartus II RTLViewer
Имея на выходе модуля регистр мы гарантируем, что каждое новое выходное значение будет строго соответствовать правилу Грея - число всегда меняется только в одном бите.

Во втором варианте gray_cnt_v2 выходы – это комбинационная логика, что очень плохо именно для счетчика Грея:

Счетчик Грея в Altera Quartus II RTLViewer (BAD)

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

При функциональной симуляции обоих модулей gray_cnt_v1 и gray_cnt_v2 мы увидим абсолютно одинаковый результат. Но это не значит, что все хорошо. Лучше отказаться от такого  gray_cnt_v2 по указанной выше причине.

Следующий «правильный» метод может выглядеть не очень компактно, зато здесь все правильно:


module gray_cnt_v3(
   input wire clk,
   input wire nreset,
   output wire [SIZE-1:0]q
   );
parameter SIZE = 4;

integer i;
reg [SIZE-1:0]gray_cnt;
reg [SIZE-1:0]bin;
reg [SIZE-1:0]next_gray_cnt;

always @*
begin
  //convert gray-to-bin
  for (i=0; i<SIZE; i=i+1)
    bin[i] = ^(gray_cnt>>i);
  //increment binary
  bin=bin+1;
  //convert bin-to-gray
  next_gray_cnt = (bin>>1)^bin;
end

always
@(posedge clk or negedge nreset)
  if(~nreset)
   gray_cnt <= 0;
  else
   gray_cnt <= next_gray_cnt;

assign q=gray_cnt;

endmodule


По каждому клоку тактовой частоты записываем в регистр счетчика следующее предвычисленное значение next_gray_cnt. А вычисляем мы его в 3 этапа:

  1. Преобразуем в двоичное число из кода Грея.
  2. Добавляем единицу.
  3. Преобразуем двоичное число обратно в код Грея.

Откомпилируем теперь этот модуль в Altera Quartus II и посмотрим как выглядит все это в схематике RTLViewer:

Счетчик Грея в RTLViewer

Теперь можно просимулировать поведение счетчиков из примера 1 и 3. Напишем вот такой тестбенч на Verilog:


`timescale 1ns/1ns

module test();

reg clk;
initial clk=0;
always
  #10 clk= ~clk;

reg nreset;
wire [3:0]cnt_value;

gray_cnt_v3 my_gray_cnt(
    .clk(clk),
    .nreset(nreset),
    .q(cnt_value)
    );
/*
gray_cnt_v1 my_gray_cnt(
    .clk(clk),
    .nreset(nreset),
    .q(cnt_value)
    );
*/

initial
begin

$dumpfile("out.vcd");
$dumpvars(-1, test);

nreset=0;
@(posedge clk); #0;
nreset=1;

#400;
$finish();
end
endmodule


 

Симулируем с помощью Icarus Verilog и смотрим как считает наш счетчик:

Счетчик Грея

Похоже все работает правильно.

 

Комментарии  

0 #6 k_levin 26.06.2015 11:19
а про стили написания есть одна статейка. называется Synthesizable Finite State Machine Design Techniques
Using the New SystemVerilog 3.0 Enhancements. там подробно описаны стили описания и результирующующ ий нетлист.
0 #5 k_levin 26.06.2015 11:18
Большое спасибо за модуль! Очень искал именно описание счётчика кода грея - борюсь с потреблением в текущем проекте.
0 #4 nckm 26.01.2015 10:30
Цитирую qxov:
Уж извините за спам в комментариях, но странно как-то они добавляются. Вот фрагмент: http://paste.ubuntu.com/9864896/

писать 2 always блока не обязательно, но это скорее вопрос стиля. Я про это писал вот здесь: https://marsohod.org/11-blog/256-bad-advice
0 #3 qxov 25.01.2015 10:58
Уж извините за спам в комментариях, но странно как-то они добавляются. Вот фрагмент: http://paste.ubuntu.com/9864896/
0 #2 qxov 25.01.2015 10:55
Последняя попытка
always @(posedge clk or negedge nreset) begin
if(~nreset) begin
gray_cnt <= 0;
end else begin
gray_cnt <= next_gray_cnt;
for (i=0; i<SIZE; i=i+1)
bin = ^(gray_cnt>& gt;i);
bin=bin+1;
next_gray_cnt = (bin>>1)^ bin;
end
end
0 #1 qxov 25.01.2015 10:52
Зачем делать предварительное вычисление в отдельном always? Чем этот вариант хуже ( я так понимаю, что они в конечном счете будут эквивалентны и здесь вопрос стиля по сути)?

А если теперь вообще избавиться от next_gray_cnt? Получим большую неодновременнос ть смены бит на выходе, поэтому не делаем?

Добавить комментарий


Защитный код
Обновить


GitHub YouTube Twitter
Вы здесь: Начало Verilog Счетчик в коде Грея