veriloghdlvga

Writing a counter to approximate a fraction with minimal error


I'm writing a VGA controller in Verilog. I have a 100 MHz clock and I want to enable a signal, VPIXEL, 480 times in a span of 16670 ms.

Obviously, I can't enable VPIXEL every 16670ms/480 ~= 34729.166ms or 34729.166... counts of the 100MHz clock.

What is the best way to approach the problem, so that the error is minimal in the long run?

I don't want to use a new clock.


Solution

  • This is a very old problem, the first instance of this problem that I a m aware of is the design of the Gregorian calendar.

    If you are asking to reduce the error in the long run, that is probably because you tried a counter using the integer division threshold, i.e. one pulse every 34729 clocks, and then you notice that the pulse will start to come too early.

    Since the denominator is constant you can write a counter you can keep track of the value without any division circuit updating only the numerator of the fraction.

    // Code just typed here to illustrate the idea, not tested
    // may have even syntax errors
    module fractional_counter #(
      parameter N=16670*1000*100,
      parameter D=480
    ) (
      input logic clk,
      input logic rst,
      output logic pls
    );
    
    localparam NB=$clog2(N+D);
    logic [NB-1:0] current_num;
    
    always @(posedge clk) begin
      if(rst) begin
        current_num = 0;
      end
      else begin
        
        if(current_num >= N - D) begin
          // (current_num+D)/D >= N/D
          // a cycle is complete
          current_num <= (current_num + D - N);
          pls <= 1;
        end
        else begin
          // Increase the counter
          // current_num / D + 1 = (current_num + D) / D
          current_num <= current_num + D;
          pls <= 0;
        end
      end
    end
    endmodule
    

    You can reduce a few bits there, by simplifying the factor, e.g. dividing both N and D by 160, also, you 16670 is very suspicious of being just another approximation of 50000/3 in that case maybe you have to put that as a fraction as well.