¿Cómo diseñar un módulo que cuente los 1 lógicos en varias entradas en Verilog?

0

Por ejemplo, tengo dos entradas, cuyo valor puede ser St1 o St0. Los cambios de estado son sincrónicos al mismo reloj, digamos 1MHz de frecuencia. Quiero diseñar un módulo Verilog que cuente la cantidad de St1 que ha aparecido en un período de tiempo determinado.

Por ejemplo, dentro de 5 ms,

la entrada A es 1 1 0 1 1

la entrada B es 1 0 0 0 1

Mi problema es que cuando ambas señales son 1, cómo asegurarse de que el contador +2.

Mi primer pensamiento es simplemente usar un caso:

reg counter;
wire [1:0] inputs;
assign inputs = {inputA, inputB};
always @(posedge clk)
begin
    if(reset)
        counter <= 0;
    else
    begin
        case(inputs)
            2'b00: counter <= counter;
            2'b01, 2'b10: counter <= counter + 1;
            2'b11: counter <= counter + 2;
        endcase
    end
end

Pero si tenemos 16 o quizás 32 entradas, ¿hay algún método mejor que escribir un caso muy largo?

Una pregunta adicional es que si estas líneas son sincrónicas a los relojes con diferentes frecuencias, ¿cómo deberíamos manejar el caso en el que varias líneas desean incrementar 1 al contador al mismo tiempo?

    
pregunta Qiao Huang

1 respuesta

0

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.

    
respondido por el alex.forencich

Lea otras preguntas en las etiquetas