Use un bucle for para sumar dentro de un ciclo. Dependiendo de la frecuencia de reloj y el número de entradas, puede incrementar directamente el contador o puede tener que almacenar la suma intermedia en un registro y una tubería agregando eso al acumulador en el próximo ciclo.
El manejo de múltiples relojes es un problema diferente. Lo que podría hacer es contar en cada dominio de reloj por separado, luego volcar los contadores y sincronizar el valor cada n (por ejemplo, 128) ciclos de reloj en un dominio de reloj en el que acumula todos estos conteos a medida que llegan.
Tengo un pequeño módulo de comprobador de PRBS que hace exactamente esto para contar errores a unos 400 MHz, tendré que desenterrarlo.
Editar: Aquí vamos:
// snip...
reg prbs_invert_reg = 1'b0, prbs_invert_next;
reg prbs_gate_en_reg = 1'b0, prbs_gate_en_next;
reg gate_sync_reg_1 = 1'b0, gate_sync_reg_2 = 1'b0, gate_sync_reg_3 = 1'b0;
always @(posedge output_clk) begin
gate_sync_reg_1 <= gate;
gate_sync_reg_2 <= gate_sync_reg_1;
gate_sync_reg_3 <= gate_sync_reg_2;
end
wire [WIDTH*4-1:0] data_err;
// lfsr_prbs_check #(
// .LFSR_WIDTH(7),
// .LFSR_POLY(7'h41),
// .LFSR_INIT(7'h7f),
// .LFSR_CONFIG("FIBONACCI_FF"),
// .REVERSE(1),
// .INVERT(0),
// .DATA_WIDTH(WIDTH*4),
// .STYLE("AUTO")
// )
// lfsr_prbs_check_inst (
// .clk(output_clk),
// .rst(1'b0),
// .data_in(output_q),
// .data_in_valid(1'b1),
// .data_out(data_err)
// );
lfsr_prbs_check_sel #(
.REVERSE(1),
.DATA_WIDTH(WIDTH*4),
.STYLE("AUTO")
)
lfsr_prbs_check_inst (
.clk(output_clk),
.rst(1'b0),
.select(prbs_select_reg),
.invert(prbs_invert_reg),
.data_in(output_q),
.data_in_valid(1'b1),
.data_out(data_err)
);
// sum errors
// reg [$clog2(WIDTH*4)+1-1:0] cycle_error_count_reg = 0;
// reg [$clog2(WIDTH*4)+1-1:0] cycle_error_count_temp = 0;
// // probably will need to pipeline this
// integer i;
// always @* begin
// cycle_error_count_temp = 0;
// for (i = 0; i < WIDTH*4; i = i + 1) begin
// cycle_error_count_temp = cycle_error_count_temp + data_err[i];
// end
// end
// always @(posedge output_clk) begin
// cycle_error_count_reg <= cycle_error_count_temp;
// end
reg [$clog2(WIDTH*4)+1-1:0] cycle_error_count_reg = 0;
reg [$clog2(WIDTH*2)+1-1:0] cycle_error_count_1_reg = 0;
reg [$clog2(WIDTH*2)+1-1:0] cycle_error_count_2_reg = 0;
reg [$clog2(WIDTH*2)+1-1:0] cycle_error_count_1_temp = 0;
reg [$clog2(WIDTH*2)+1-1:0] cycle_error_count_2_temp = 0;
// probably will need to pipeline this
integer i;
always @* begin
cycle_error_count_1_temp = 0;
cycle_error_count_2_temp = 0;
for (i = 0; i < WIDTH*2; i = i + 1) begin
cycle_error_count_1_temp = cycle_error_count_1_temp + data_err[i];
cycle_error_count_2_temp = cycle_error_count_2_temp + data_err[i+WIDTH*2];
end
end
always @(posedge output_clk) begin
cycle_error_count_1_reg <= cycle_error_count_1_temp;
cycle_error_count_2_reg <= cycle_error_count_2_temp;
cycle_error_count_reg <= cycle_error_count_1_reg + cycle_error_count_2_reg;
end
// accumulate errors, dump every 256 cycles
reg [7:0] count_reg = 8'd0;
reg [8:0] word_count_acc_reg = 0;
reg [8:0] word_count_reg = 0;
reg [8+$clog2(WIDTH*4)+1:0] error_count_acc_reg = 0;
reg [8+$clog2(WIDTH*4)+1:0] error_count_reg = 0;
reg error_count_flag_reg = 0;
always @(posedge output_clk) begin
if (count_reg == 8'd255) begin
count_reg <= 8'd0;
word_count_acc_reg <= 0;
error_count_acc_reg <= 0;
if (!prbs_gate_en_reg || gate_sync_reg_3) begin
word_count_acc_reg <= 1;
error_count_acc_reg <= cycle_error_count_reg;
end
word_count_reg <= word_count_acc_reg;
error_count_reg <= error_count_acc_reg;
error_count_flag_reg <= ~error_count_flag_reg;
end else begin
count_reg <= count_reg + 1;
if (!prbs_gate_en_reg || gate_sync_reg_3) begin
word_count_acc_reg <= word_count_acc_reg + 1;
error_count_acc_reg <= error_count_acc_reg + cycle_error_count_reg;
end
end
end
// synchronize dumped counts to control clock domain
reg flag_sync_reg_1 = 1'b0;
reg flag_sync_reg_2 = 1'b0;
reg flag_sync_reg_3 = 1'b0;
always @(posedge clk) begin
flag_sync_reg_1 <= error_count_flag_reg;
flag_sync_reg_2 <= flag_sync_reg_1;
flag_sync_reg_3 <= flag_sync_reg_2;
end
// snip...
always @* begin
// snip...
cycle_count_next = cycle_count_reg + 1;
if (cycle_count_reg[31] && ~cycle_count_next[31]) begin
cycle_count_next = 32'hffffffff;
end
update_count_next = update_count_reg;
prbs_word_count_next = prbs_word_count_reg;
prbs_error_count_next = prbs_error_count_reg;
if (flag_sync_reg_2 ^ flag_sync_reg_3) begin
update_count_next = update_count_reg + 1;
prbs_word_count_next = word_count_reg + prbs_word_count_reg;
prbs_error_count_next = error_count_reg + prbs_error_count_reg;
// saturate
if (update_count_reg[31] && ~update_count_next[31]) begin
update_count_next = 32'hffffffff;
end
if (prbs_word_count_reg[31] && ~prbs_word_count_next[31]) begin
prbs_word_count_next = 32'hffffffff;
end
if (prbs_error_count_reg[31] && ~prbs_error_count_next[31]) begin
prbs_error_count_next = 32'hffffffff;
end
end
// snip...
end
// snip...
always @(posedge clk) begin
if (rst) begin
// snip...
cycle_count_reg <= 32'd0;
update_count_reg <= 32'd0;
prbs_word_count_reg <= 32'd0;
// snip...
prbs_error_count_reg <= 32'd0;
prbs_select_reg <= 2'b00;
prbs_invert_reg <= 1'b0;
prbs_gate_en_reg <= 1'b0;
// snip...
end else begin
// snip...
cycle_count_reg <= cycle_count_next;
update_count_reg <= update_count_next;
prbs_word_count_reg <= prbs_word_count_next;
// snip...
prbs_error_count_reg <= prbs_error_count_next;
prbs_select_reg <= prbs_select_next;
prbs_invert_reg <= prbs_invert_next;
prbs_gate_en_reg <= prbs_gate_en_next;
// snip...
end
// snip...
end
// snip...
Donde se usó esto, WIDTH se estableció en 16, por lo que está haciendo una comprobación de PRBS y una acumulación de conteo de errores de una entrada de 64 bits. Y funcionaba a unos 20 Gbps, por lo que output_clk
era de unos 300 MHz. Sin embargo, pasó el análisis de tiempo con una restricción de reloj cercana a 400 MHz (suponiendo una velocidad de datos de 25 Gbps). El reloj interno clk
fue de 125 MHz. El objetivo era un Virtex Ultrascale FPGA. La lectura y el borrado de los acumuladores finales no se muestran, pero los contadores se configuran para saturarse a 0xffffffff si no se leen y borran antes de desbordarse. El cruce del dominio del reloj es un simple sincronizador de pulso unidireccional (no un saludo de dos vías) con una copia del conteo que se lee directamente desde el dominio del reloj interno. Puede que esta no sea la forma más robusta de hacer esto, pero es simple y funcionó bien para la aplicación.