Categories:

События счет любят

Даже не знаю, кому будет интересно. Математикам банально, программистам неактуально, электронщикам и так известно, остальным пофиг. Поэтому ...

Допустим, что нам для некоторого эксперимента нужно считать, сколько раз случилось каждое из многих десятков или сотен тысяч событий. Для этого мы разрабатываем специализированные чипы, каким-то умным образом анализирующие сырые данные эксперимента и получающие внутри себя много сигналов "случилось такое-то событие".
События могут происходить в произвольных сочетаниях - хоть все сотни тысяч одновременно, и - по сравнению с тактовыми частотами современных процессоров, - весьма часто. Это значит, что к схеме тех специализированных чипов придется добавить много блоков, которые только и умели бы, что по сигналу "произошло событие" как можно быстрее прибавлять 1 к счетчику (ну и по управляющему сигналу позволяли бы сбрасывать счетчик в ноль, одновременно выталкивая его текущее содержимое в порт - надо же нам как-то узнавать, до чего они там досчитали, но эту деталь функциональности мы пока для простоты опустим).

Как будет выглядеть такой блок? Он будет состоять из двух частей: регистра, где хранится текущее значение счетчика, и сложителя, прибавляющего 1 к счетчику. У регистра есть два входа - сигнал и новое значение. В момент прихода сигнала содержимое регистра заменяется новым значением. В сложитель же входит текущее значение регистра, а выходят из него провода, несущие "новое значение":
reg [63:0] counter;
wire [63:0] new_value = counter + 1; // Сложение происходит "непрерывно"
always @(signal) begin // Каждый раз, когда приходит сигнал (верилоговцы меня простят)
    counter = new_value;
end

Таким образом, если события будут приходить с интервалом не меньшим, чем время, необходимое сложителю для прибавления единицы к, например, 64-битному числу, то наш регистратор будет работать.

Посмотрим теперь, как сложитель устроен внутри. Понятно, что из четного числа получается нечетное, и наоборот: new_value[0] = !counter[0];

Каждый следующий бит изменяется на противоположный, если все биты правее него были равны единице, или, что то же самое, если логическое И от всех этих битов равно единице:
for (int i = 1; i < 64; i++) // Декларативный цикл, описывающий подключение отдельных битов new_value
    new_value[i] = counter[i] ^ (counter[i-1:0] == (1 << i) - 1);

Это довольно быстро становится или дорого с точки зрения количества необходимых логических элементов - если мы будем вычислять все частичные логические И параллельно, их потребуются сотни, или недопустимо медленно - если мы будем экономить и вычислять их по цепочке:
wire [63:0] ands;
assign ands[0] = counter[0];
for (int i = 1; i < 64; i++) begin
    new_value[i] = counter[i] ^ ands[i-1];
    if (i != 63) ands[i] = ands[i-1] & counter[i];
end

Да и то понадобится по два элемента (XOR и AND) почти на каждый бит счетчика.

Как же быть? Как сделать счетчик событий так, чтобы он, кроме собственно регистра, использовал самый минимум логических элементов?