//
// Copyright (C) 2026  Bit by Bit Signal Processing (bxbsp.com)
//
// This code comes without warranty or guarantees.
//
// Use of this code for any purpose is allowed so long as this
// copyright notice is attached.
//

`undef r
`define r always@(posedge clk_ref)

`undef rm
`define rm always@(posedge clk_measured)

`undef w
`define w assign
   
`timescale 1 ps / 1 ps

//
// This module measures the frequency of an unknown clock, with output
// in Hertz.  Measurements are updated once each second.  The measured
// value is made available in the clk_ref clock domain.
//
module freq_measurement 
  #( 
     parameter   CLK_REF_FREQ_HZ            = 100000000 ,
     parameter   CLK_MEASURED_MAX_FREQ_HZ   = 500000000 ,
     localparam  MEASURED_BITS              = $clog2(CLK_MEASURED_MAX_FREQ_HZ)
   )
   (
     input wire                       clk_ref                 ,
     input wire                       clk_measured            ,
     output wire [MEASURED_BITS-1:0]  measured_frequency_hz   ,
     output wire                      measurement_valid
   );

   localparam REF_BITS = $clog2(CLK_REF_FREQ_HZ/2);

   //
   // Generate a 1Hz square wave with 50% duty cycle.
   // Do this by making a 2Hz counter with unknown duty cycle.
   // Then toggle a register every time it rolls over.
   //
   reg [REF_BITS-1:0] half_second_counter_r     = 0;
   reg                half_second_count_done_r  = 0;
   reg                one_second_square_wave_r  = 0;
   
   `r half_second_count_done_r  <=  (half_second_counter_r == CLK_REF_FREQ_HZ/2-2 );   
   `r half_second_counter_r     <=  half_second_count_done_r ? 0 : half_second_counter_r + 1;
   `r one_second_square_wave_r  <=  one_second_square_wave_r ^ half_second_count_done_r;
   
   //
   // Transition one_second_square_wave_r to the measured clock domain with
   // a synchronizer.  FALSE_PATH_DEST is defined as follows in the Vivado
   // .xdc constraint file:
   //
   //  set_false_path -to [get_cells -hier -filter {(FALSE_PATH_DEST == 1)}]
   //
   // Setting this as a false path prevents Vivado from doing anything funky to
   // align the clocks, which otherwise could cause a problem meeting timing.
   //
   (* ASYNC_REG  = "TRUE" *) (* FALSE_PATH_DEST = 1 *)  reg  one_second_square_wave_rm  = 0;
   (* ASYNC_REG  = "TRUE" *)                            reg  one_second_square_wave_rrm = 0;
   
   `rm one_second_square_wave_rm  <= one_second_square_wave_r;
   `rm one_second_square_wave_rrm <= one_second_square_wave_rm;

   //
   // From the one second square wave, generate a 1 second pulse from
   // the positive edge.
   //
   reg one_second_square_wave_rrrm  = 0;
   reg one_second_pulse_rm          = 0;
   
   `rm one_second_square_wave_rrrm  <=  one_second_square_wave_rrm;
   `rm one_second_pulse_rm          <=  one_second_square_wave_rrm && !one_second_square_wave_rrrm;

   //
   // Count positive edges of the measured clock signal for one second.
   //
   reg [MEASURED_BITS-1:0] measurement_counter_r = 0;
   
   `rm measurement_counter_r  <=  one_second_pulse_rm ? 0 : measurement_counter_r + 1;
   
   //
   // Latch the measurement when it occurs
   //
   reg                      measurement_valid_rm      = 0;
   reg [MEASURED_BITS-1:0]  measured_frequency_hz_rm  = 0;
   
   `rm measurement_valid_rm      <=  one_second_pulse_rm ? 1 : measurement_valid_rm;
   `rm measured_frequency_hz_rm  <=  one_second_pulse_rm ? measurement_counter_r : measured_frequency_hz_rm;
   
   //
   // Use synchronizers to transfer the measurement and valid to the clk_ref domain.
   // See the earlier comment on synchronizers.
   //
   (* ASYNC_REG  = "TRUE" *) (* FALSE_PATH_DEST = 1 *)  reg measurement_valid_r       = 0;
   (* ASYNC_REG  = "TRUE" *)                            reg measurement_valid_rr      = 0;
   (* ASYNC_REG  = "TRUE" *) (* FALSE_PATH_DEST = 1 *)  reg [MEASURED_BITS-1:0] measured_frequency_hz_r   = 0;
   (* ASYNC_REG  = "TRUE" *)                            reg [MEASURED_BITS-1:0] measured_frequency_hz_rr  = 0;

   `r measurement_valid_r       <= measurement_valid_rm;
   `r measurement_valid_rr      <= measurement_valid_r;

   `r measured_frequency_hz_r   <= measured_frequency_hz_rm;
   `r measured_frequency_hz_rr  <= measured_frequency_hz_r;
   
                                
   //
   // Latch the values in on a negative edge pulse of the one second square wave.
   // Since the signals change shortly after the positive edge of a one second
   // square wave, they are certainly stable by the time of the negative edge
   // of the one second square wave.  This enssures that all bits are stable in 
   // the multi-bit measurement.
   //
   // The way this module is designed, the first measurement is only for a
   // half second, not a full second.  So latch the valid twice to only indicate
   // a valid measurement on the second measurement.
   //
   reg                      one_second_square_wave_rr  = 0;
   reg                      one_second_negedge_pulse_r = 0;

   reg                      measurement_valid_l        = 0;
   reg                      measurement_valid_ll       = 0;
   reg [MEASURED_BITS-1:0]  measured_frequency_hz_l    = 0;

   `r one_second_square_wave_rr   <=  one_second_square_wave_r;
   `r one_second_negedge_pulse_r  <=  one_second_square_wave_rr && !one_second_square_wave_r;

   `r measured_frequency_hz_l  <= one_second_negedge_pulse_r  ?  measured_frequency_hz_rr : 
                                  /**/                           measured_frequency_hz_l  ;

   `r measurement_valid_l      <= one_second_negedge_pulse_r  ?  measurement_valid_rr : 
                                  /**/                           measurement_valid_l  ;
   
   `r measurement_valid_ll     <= one_second_negedge_pulse_r  ?  measurement_valid_l  : 
                                  /**/                           measurement_valid_ll  ;

   //
   // Assign the module outputs
   //
   `w measurement_valid      =  measurement_valid_ll;
   `w measured_frequency_hz  =  measured_frequency_hz_l;

endmodule // freq_measurement


//
// This test module can be run as follows with Icarus Verilog:
//
//   iverilog -g 2012 -s top -DDEBUG -DTEST_MODULE freq_measurement.sv
//   ./a.out
//
// Output for UNKNOWN_FREQUENCY_HZ equal to 777 is this:
//
//   VCD info: dumpfile freq_measurement_test.vcd opened for output.
//   Unknown frequency measured to be 776Hz.
//   freq_measurement.sv:201: $finish called at 3111885 (1ps)
//
// The value measured always seems to be 1Hz low in simulation.
// It's possible that this is a simulation artifact, or that
// it's real.
//
`ifdef TEST_MODULE

module top ();
   
   localparam                REF_FREQUENCY_HZ      = 10000;
   localparam                UNKNOWN_FREQUENCY_HZ  = 777;
   localparam                MAX_FREQUENCY_HZ      = 700 * 1000 * 1000;
   localparam                MEASURED_BITS         = $clog2(MAX_FREQUENCY_HZ);

   localparam                FREQ_PRODUCT               = 2 * REF_FREQUENCY_HZ * UNKNOWN_FREQUENCY_HZ;
   localparam                REF_CLOCK_HALF_PERIOD      = UNKNOWN_FREQUENCY_HZ;  // / FREQ_PRODUCT
   localparam                UNKNOWN_CLOCK_HALF_PERIOD  = REF_FREQUENCY_HZ;      // / FREQ_PRODUCT

   reg                       clk_ref       = 0;
   reg                       clk_measured  = 0;

   wire [MEASURED_BITS-1:0]  measured_frequency_hz;
   wire                      measurement_valid;

   initial begin
      for(;;)
        begin
           #REF_CLOCK_HALF_PERIOD;
           clk_ref = !clk_ref;
        end
   end

   initial begin
      for(;;)
        begin
           #UNKNOWN_CLOCK_HALF_PERIOD;
           clk_measured = !clk_measured;
        end
   end

   freq_measurement #( .CLK_REF_FREQ_HZ           (  REF_FREQUENCY_HZ      ),
                       .CLK_MEASURED_MAX_FREQ_HZ  (  MAX_FREQUENCY_HZ      ))
   measurer          ( .clk_ref                   (  clk_ref               ),
                       .clk_measured              (  clk_measured          ),
                       .measured_frequency_hz     (  measured_frequency_hz ),
                       .measurement_valid         (  measurement_valid     ));
   
   `r if(measurement_valid)
     begin
        $display("Unknown frequency measured to be %0dHz.", measured_frequency_hz);
        $finish;
     end

 `ifdef DEBUG
   initial
     begin
        $dumpfile("freq_measurement_test.vcd");
        $dumpvars(0, top);
     end
 `endif

endmodule

`endif //  `ifdef TEST_MODULE
