Декодер

Декодер - устройство, преобразующее двоичный код в позиционный код.

Рассмотрим полный декодер. Если входных линий декодера 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) в других статьях.

 


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