Декодер - устройство, преобразующее двоичный код в позиционный код.
Рассмотрим полный декодер. Если входных линий декодера N, то выходных линий будет 2^N. например, декодер с тремя входными линиями имеет 2^3=8 выходов. Только на одном из выходов декодера будет единица, на том, номер которого подается на шину входных сигналов. На остальных выходах декодера будет ноль.
Есть несколько способов описать декодер на языке Verilog HDL. Самый очевидный способ - с помощью конструкции языка case-endcase. Вот как можно это сделать:
module my_decoder1(
input wire [2:0]addr,
output reg [7:0]selector
);
always @*
begin
case(addr)
3'd0: selector=8'b00000001;
3'd1: selector=8'b00000010;
3'd2: selector=8'b00000100;
3'd3: selector=8'b00001000;
3'd4: selector=8'b00010000;
3'd5: selector=8'b00100000;
3'd6: selector=8'b01000000;
3'd7: selector=8'b10000000;
endcase
end
endmodule
Если откомпилировать такой код с помощью Altera Quartus II и потом посмотреть netlist с помощью утилиты RTLViewer из комплекта Quartis II, то мы увидим, что в схеме получился... декодер!
Есть еще один, менее очевидный, но более простой способ описания полного декодера. Вас может удивить его реализация:
module my_decoder2(
input wire [2:0]addr,
output reg [7:0]selector
);
always @*
selector = 8'b00000001 << addr;
endmodule
Этот способ удобен, когда используется большая разрядность входных/выходных сигналов. При этом не нужно делать многострочных записей для case-endcase и вероятность ошибиться при наборе текста при этом меньше. Здесь используется сдвиговый регистр. Регистр selector с начальным значением единица сдвигается влево на столько бит, сколько значение входного сигнала addr.
В RTLViewer результат компиляции будет выглядеть вот так:
Казалось бы, причем тут декодер, когда здесь сдвиговый регистр? Я покажу, что эти устройства работают абсолютно одинаково. Для этого напишем тестбенч на языке Verilog:
`timescale 1ns / 1ns
module test();
//моделируем тактовую частоту
reg clk;
initial clk=0;
always
#5 clk=~clk;
//сделаем 3х битный счетчик
reg [2:0]cnt;
initial cnt=0;
always @(posedge clk)
cnt = cnt+1;
//моделируем провода подключенные к выходу декодера
//выполненного с помощью case-endcase
wire sel_a0,sel_a1,sel_a2,sel_a3,sel_a4,sel_a5,sel_a6,sel_a7;
//моделируем провода подключенные к выходу декодера
//выполненного на сдвиговом регистре
wire sel_b0,sel_b1,sel_b2,sel_b3,sel_b4,sel_b5,sel_b6,sel_b7;
//вставляем в тестбенч оба экземпляра декодеров:
my_decoder1 decod1(
.addr(cnt),
.selector( {sel_a0,sel_a1,sel_a2,sel_a3,sel_a4,sel_a5,sel_a6,sel_a7} )
);
my_decoder2 decod2(
.addr(cnt),
.selector( {sel_b0,sel_b1,sel_b2,sel_b3,sel_b4,sel_b5,sel_b6,sel_b7} )
);
initial
begin
//объявляем генерируемый Waveform файл
$dumpfile("out.vcd");
$dumpvars(0,test);
#200 $finish;
end
endmodule
Симулируем "проект" из трех файлов с помощью icarus verilog:
c:\Altera\marsohod\test_decoder>iverilog -o qqq test_dec.v my_decoder1.v my_decoder2.v
c:\Altera\marsohod\test_decoder>vvp qqq
VCD info: dumpfile out.vcd opened for output.
c:\Altera\marsohod\test_decoder>gtkwave out.vcd
Вот какие временные диаграммы показывает нам GtkWave:
Видно, что сигналы sel_b и sel_a меняются синхронно, значит и устройства абсолютно одинаковые. Скажу даже больше, скорее всего и проект будет откомпилирован для ПЛИС абсолютно одинаково и для случая с case-endcase и для случая со сдвиговым регистром.
Есть еще одна тонкая тонкость при реализации декодеров. Мы рассмотрели случай полного декодера, то есть когда входных сигналов N, а выходных 2^N. Но иногда полный декодер вроде бы и не требуется. Например, нас могут не интересовать некоторые биты выходного селектора, скажем 6й и 7й биты. Так вот, было бы совсем не правильно написать что-то вроде этого:
module my_decoder3(
input wire [2:0]addr,
output reg [7:0]selector
);
always @*
begin
case(addr)
3'd0: selector=8'b00000001;
3'd1: selector=8'b00000010;
3'd2: selector=8'b00000100;
3'd3: selector=8'b00001000;
3'd4: selector=8'b00010000;
3'd5: selector=8'b00100000;
/*
3'd6: selector=8'b01000000;
3'd7: selector=8'b10000000;
*/
endcase
end
endmodule
Я закоментировал две строки из case-endcase, но такая реализация ущербна. При такой реализации в проекте появятся элементы памяти в виде защелок (latches). В этом описании получается что при входных значениях 6 или 7 на выходе должно получиться не какое-то новое число, а то, что было раньше. То есть предыдущее значение где-то нужно будет запомнить, вот компилятор и добавит latches. Бойтесь и избегайте их - это зло. Лучше опишите все возможные выходные сигналы для case-endcase, но не используйте их. Не думайте, что сэкономите логические элементы таким способом, скорее получится наоборот.
Я еще напишу про защелки (latch) в других статьях.
Подробнее...