How to calculate the frequency of an input signal using Verilog

system-verilogverilog

I'm trying to count the frequency of the feedback signal from a DC motor to find out the speed of the motor (in revolution/sec), or at least the rate of its spinning. The feedback signal from the motor is a square wave, and 192 positive edges are equivalent to one revolution.

I'm using 50 mega-Herz as my input clock signal. I was trying to design a module that takes 50M-Hz clock and feedback from the motor as my inputs, and outputs the rate/speed of the motor. I've been struggling for a while because Verilog does not allow me to use one variable in multiple always block. Please help me out with this project. Thank you!

Best Answer

The first step is to decide whether to measure the frequency or the period of the signal. As Hida pointed out, measuring the period of the signal is possible by counting 50MHz pulses between each rising edge of the input. You would then have to divide a constant by this period to obtain RPM or RPS.

But RPM is linearly related to the frequency of the pulse signal, not the period. So it is easier to directly measure this by counting the number of pulses in a fixed period.

Next you have to decide how fast you need to measure the system. If you count the number of pulses for a whole second, you will get the RPS * 192, which is good precision but only gives an output once a second and may be too slow. If you count for 1/192 of a second you get RPS directly, but may loose precision. The example below measures for 1/32 of a second which gives 6*RPS or RPM/10. You should select a measurement period to suit the requirements and adjust the bit widths and constants to fit. By picking the right sample period you can even get the output directly in the desired units or at least a convenient multiple of them.

  1. Divide the input clock from 50MHz down to whatever sample rate (period) you need. In this example create a 32Hz period by using a counter from 1 to 1562450 (which is 50000000 / 32). Or use a down counter from 1562449 to 0.

  2. Keep track of the input signal to find it's rising edge. If it is low on one 50Mhz clock and high on the next one, then it is a rising edge of a pulse. Since the input signal is so slow with respect to the 50MHz clock, it is treated as data and just sampled asynchronously. Note: In a real design, you probably need to debounce and register the input signal since it is asynchronous to the 50MHz clock. Otherwise any noise or bounces sampled will add false pulses to the counter. This example assumes the signal conditioning is already done.

  3. Each time the counter reaches zero, one period has ended. Register the current edge count so that it does not change during the next period. Then reset the edge count back to zero and start counting edges again for the next period.

As an aside, you can see there are places in the test bench and module that use a signal in multiple always blocks (for example "counter"). You can play with this at http://www.iverilog.com or with a Verilog simulator.

CAVEAT: This is just a very quick example. Production code would be explicit about all bit widths, would assign a wire to the end-of-period signal vs. duplicating the logic. The clock counter is pretty wide and should probably be pre-scaled, and many other details. But this runs in the simulator and should synthesize and work fine in an FPGA @50Mhz too.


module rpm_counter(
    input clk, // 50Mhz
    input signal, // 0 to 1Mhz signal
    output [15:0]rpmout // RPM/10 e.g. 300 for 3000RPM
);

// counter == 0 @ 32 Hz
reg [20:0]counter = 0;
always @(posedge clk) begin
    if(counter == 0)
        counter <= 1562500 - 1; // 3-2-1-0 is period of 4, not 3
    else
        counter <= counter - 1;
end

// simple flip/flip to store last value of signal
reg last = 0;
always @(posedge clk) begin
    last <= signal;
end

// when counter == 0, register current count and reset edge counter
// otherwise increment edge counter for each rising edge of the pulse
reg [15:0]edges = 0;
reg [15:0]outreg = 0;
always @(posedge clk) begin
    if(counter == 0) begin
        outreg <= edges;
        edges <= 0;
    end
    // detect edge, was low and now high
    else if(~last & signal)
        edges <= edges + 1;
end

assign rpmout = outreg;

endmodule

module cheesy_testbench;

wire [15:0]out;
initial
  begin
    #100000000;
    $display("0x%x is %d RPM", out, out*10);
    $finish;
  end

reg clk50 = 1;
always
  #10 clk50 = ~clk50; // 10+10 = 20ns = 50Mhz

reg sig = 1;
always
  #45208 sig = ~sig; // 45208ns*2 period @3456RPM

rpm_counter UUT (
  .clk(clk50),
  .signal(sig),
  .rpmout(out)
);

endmodule
Related Topic