Давайте подумаем, как на языке 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:
Имея на выходе модуля регистр мы гарантируем, что каждое новое выходное значение будет строго соответствовать правилу Грея - число всегда меняется только в одном бите.
Во втором варианте gray_cnt_v2 выходы – это комбинационная логика, что очень плохо именно для счетчика Грея:
Мы не должны допускать формирование разных бит "счетчика" Грея в разное время, но именно это и может происходить в случае комбинационной логики на выходе нашего модуля, ведь путь формирования сигналов каждого из битов может оказаться разный.
При функциональной симуляции обоих модулей 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 этапа:
- Преобразуем в двоичное число из кода Грея.
- Добавляем единицу.
- Преобразуем двоичное число обратно в код Грея.
Откомпилируем теперь этот модуль в Altera Quartus II и посмотрим как выглядит все это в схематике 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 и смотрим как считает наш счетчик:
Похоже все работает правильно.
Подробнее...