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

Давайте подумаем, как на языке 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 и смотрим как считает наш счетчик:

Счетчик Грея

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

 


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