В этой статье я постараюсь рассказать про счетчики, про их описание на Verilog и их схемотехническое представление в RTLViever.
Счетчики широко применяются везде, где нужно посчитать число некоторых событий, да и не только для этого
Двоичный счетчик.
Вот это просто двоичный счетчик. На входе данных группы триггеров стоит сумматор. Одно из слагаемых для сумматора - это предыдущее значение счетчика, а второе слагаемое - константа "единица".
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 удобно использовать, например, для каскадного соединения счетчиков как сигнал разрешения счета для старших десятичных разрядов.
В следующей статье я хотел рассказать про сдвиговые регистры.
Подробнее...