0
\$\begingroup\$

For example, I have two inputs, whose value can be either St1 or St0. The changes of states are synchronous to the same clock, say 1MHz of frequency. I want to design a Verilog module that counts how many St1 have appeared in a certain amount of time.

For instance, within 5ms,

input A is 1 1 0 1 1

input B is 1 0 0 0 1

My problem is that when both signals are 1, how to make sure that the counter +2.

My first thought is just using a case:

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

But if we have 16 or maybe 32 inputs, is there a method better than writing a very long case?

An additional question is that if these lines are synchronous to clocks with different frequencies, how should we handle the case where multiple lines want to increment 1 to the counter at the same time?

\$\endgroup\$
0
\$\begingroup\$

Use a for loop to add up within a cycle. Depending on the clock frequency and number of inputs, you may be able to directly increment the counter or you may have to store the intermediate sum in a register and pipeline adding that to the accumulator on the next cycle.

Handling multiple clocks is a different issue. What you could do is count in each clock domain separately, then dump the counters and synchronize the value every n (say 128) clock cycles into one clock domain where you accumulate all of these counts as they arrive.

I have a little PRBS checker module that does exactly this to count up errors at like 400 MHz, I'll have to dig it up.

Edit: Here we go:

// 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...

Where this was used, WIDTH was set to 16, so it's doing a PRBS check and error count accumulation of a 64 bit input. And it was running at around 20 Gbps, so output_clk was around 300 MHz. However, it passed timing analysis with a clock constraint of closer to 400 MHz (assuming a data rate of 25 Gbps). The internal clock clk was 125 MHz. The target was a Virtex Ultrascale FPGA. Readout and clearing of the final accumulators is not shown, but the counters are set up to saturate at 0xffffffff if they do not get read out and cleared before overflowing. Clock domain crossing is a simple one-way pulse synchronizer (no two-way handshake) with a copy of the count being directly read from the internal clock domain. This might not be the most robust way to do this, but is simple and it worked fine for the application.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.