Счетчики.

В этой статье я постараюсь рассказать про счетчики, про их описание на Verilog и их схемотехническое представление в RTLViever.

Счетчики широко применяются везде, где нужно посчитать число некоторых событий, да и не только для этого Smile

Двоичный счетчик.

Вот это просто двоичный счетчик. На входе данных группы триггеров стоит сумматор. Одно из слагаемых для сумматора - это предыдущее значение счетчика, а второе слагаемое - константа "единица".


reg [3:0]counter;
always @(posedge clk)
  counter <= counter + 1'd1;


Вот представление этого счетчика в RTLViewer:

схема двоичного счетчика

К сожалению симулировать такой счетчик не получится, так как симулятору не известно начальное значение регистров counter[3:0], значит он не сможет вычислить и все последующие значения счетчика. Чтобы провести симуляцию нам нужен двоичный счетчик с ассинхронным сбросом.

Двоичный счетчик с асинхронным сбросом.


reg [3:0]counter;
always @(posedge clk or posedge reset)
  if(reset)
    counter <= 4'd0;
  else
    counter <= counter + 1'd1;


Вот его схемотехническое представление в RTLViewer:

схема двоичного счетчика с ассинхронным сбросом

И вот его временная диаграмма:

временная диаграмма схемы двоичного счетчика

Видно, что по фронту тактовой частоты в регистры счетчика будет записываться очередное значение, на единицу большее, чем предыдущее. Если в коде Verilog использовать минус, вместо плюса, то  счетчик будет считать в обратную сторону.

Еще раз подчеркну, что ассинхронный сброс или установка как правило используются только в самом начале работы устройства.

Двоичный счетчик с синхронным сбросом.

Здесь обнуление регистров счетчика происходит по фронту тактовой частоты. В схеме появляется мультиплексор, который выбирает для записи в регистры счетчика либо следующее значение, либо ноль.


always @(posedge clk)
  if(reset)
    counter <= 4'd0;
  else
    counter <= counter + 1'd1;


Вот представление в RTL:

схема двоичного счетчика с синхронным сбросом

И его временная диаграмма:

временная диаграмма счетчика с синхронным сбросом

Счетчик с асинхронным сбросом и входом разрешения и сигналом загрузки.
Чтобы как-то разнообразить описание счетчиков я решил в этом примере дать более "осмысленные" имена сигналам. Представим себе, что мы разрабатываем свой процессор:

  • В процессоре есть указатель на исполняемую инструкцию instr_ptr[15:0].
  • После сброса системы по сигналу reset этот указатель instr_ptr устанавливается на инструкцию по адресу ноль.
  •  С каждым тактом исполнение программы движется вперед - значение instr_ptr увеличивается и каждый раз выбирается следующая команда.
  • Предположим, что некоторые инструкции исполняются дольше других. Это могут быть какие нибудь сложные команды типа умножения или деления. В этом случае АЛУ процессора (арифметико-логическое устройство) выдает нам сигнал cpu_wait вставляя такты ожидания. В эти такты ожидания instr_ptr не изменяется.
  • Если же в какой-то момент времени дешифратор команд увидит команду безусловного перехода (jmp) или условного перехода (jz, jnz, jc и т.д.) и нужно перейти на другой адрес, то нам приходит сигнал branch_cond и в счетчик команд загружается адрес перехода branch_addr.

Вот так это все может быть записано на Verilog:


always @(posedge clk or posedge reset)
begin
 if
(reset)
   instr_ptr <= 16'd0;
 else
 if
(~cpu_wait)
 begin
  if
(branch_cond)
   instr_ptr <= branch_addr;
  else
   instr_ptr <= instr_ptr + 1'd1;
 end
end


Давайте рассмотрим, что представляет из себя этот код в RTLViewer:

Счетчик с загрузкой начального значения счета

А вот временная диаграмма:

Счетчик команд процессора

На этой диаграмме, как пример,  видно такт ожидания на инструкции процессора по адресу 3 и переход с адреса 6 на адрес 35.

Еще счетчики могут быть не только двоичными. Вот, например:

Счетчик по модулю 10.

В этом примере максимальное число в регистре счетчика - это девять. Сигнал cout активен в этот последний такт счета. Следующий такт записывает в регистр ноль. Всего тактов, включая нулевой, получается десять. Отсюда и название - по модулю 10.

Понятно, что аналогично можно строить счетчики по любому модулю.


wire cout;
reg [3:0]counter;

always @(posedge clk or posedge reset)
begin
 if
(reset)
  counter <= 4'd0;
 else
 if
(counter==4'd9)
  counter <= 4'b0;
 else
  counter <= counter + 1'd1;
end

assign cout = (counter==4'd9);


В представлении RTL видно, что кроме собственно регистра у нас есть еще сумматор и компаратор. Сумматор подготавливает для записи следующее значение счетчика, то есть counter+1. Компаратор сравнивает текущее значение счетчика с последним в последовательности (у нас это число 9).

десятичный счетчик

Считает эта схема вот так:

временная диаграмма для десятичного счетчика

Сигнал cout удобно использовать, например, для каскадного соединения счетчиков как сигнал разрешения счета для старших десятичных разрядов.

В следующей статье я хотел рассказать про сдвиговые регистры.

 

 


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