diff --git a/dsp/freq_count.gtkw b/dsp/freq_count.gtkw index 26659afbdc..6a16144bfb 100644 --- a/dsp/freq_count.gtkw +++ b/dsp/freq_count.gtkw @@ -3,11 +3,12 @@ [pos] -1 -1 *-6.533834 3870 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 [treeopen] freq_count_tb. -[sst_width] 210 +[sst_width] 213 [signals_width] 174 [sst_expanded] 1 [sst_vpaned_height] 154 @28 +freq_count_tb.clk freq_count_tb.f_in @22 freq_count_tb.diff_stream[15:0] @@ -15,12 +16,14 @@ freq_count_tb.diff_stream[15:0] freq_count_tb.diff_stream_strobe @24 freq_count_tb.frequency[27:0] +@28 +freq_count_tb.mut.freq_strobe @22 freq_count_tb.mut.work.gray1[3:0] freq_count_tb.mut.work.gray3[3:0] @24 freq_count_tb.mut.work.diff1[3:0] -freq_count_tb.mut.work.refcnt[5:0] +freq_count_tb.mut.refcnt[5:0] freq_count_tb.mut.work.accum[27:0] [pattern_trace] 1 [pattern_trace] 0 diff --git a/dsp/freq_count.v b/dsp/freq_count.v index d07a5bd619..919bb3c32c 100644 --- a/dsp/freq_count.v +++ b/dsp/freq_count.v @@ -4,7 +4,7 @@ `timescale 1ns / 1ns module freq_count #( - // Default configuration useful for input frequencies < 96 MHz + // Default configuration useful for input frequencies < 2*sysclk parameter glitch_thresh=2, parameter refcnt_width=24, parameter freq_width=28, @@ -15,26 +15,65 @@ module freq_count #( input f_in, // unknown input // outputs in sysclk domain - output [freq_width-1:0] frequency, + output reg [freq_width-1:0] frequency, output freq_strobe, - output [15:0] diff_stream, - output diff_stream_strobe, + output reg [15:0] diff_stream, // stream of last 4 4-bit counts of f_in + output reg diff_stream_strobe, // strobe at f_sysclk/4 // glitch_catcher can be routed to a physical pin to trigger // a 'scope; see glitch_thresh parameter above - output glitch_catcher + output reg glitch_catcher ); +initial begin + frequency=initv; + diff_stream=0; + diff_stream_strobe=0; + glitch_catcher=0; +end + +// Reference counter +// may or may not be synchronized between instances +reg [refcnt_width-1:0] refcnt=0; +reg ref_strobe=0, stream_strobe=0; +always @(posedge sysclk) begin + {ref_strobe, refcnt} <= refcnt + 1; + stream_strobe <= refcnt[1:0] == 0; +end + +wire [3:0] xcount; // per-sysclk count of f_in edges +wire [freq_width-1:0] frequency_w; freq_gcount #( - .glitch_thresh(glitch_thresh), - .refcnt_width(refcnt_width), + .gw(4), .freq_width(freq_width), .initv(initv) ) work ( .sysclk(sysclk), .f_in(f_in), .g_in(1'b1), // this is the whole point! - .frequency(frequency), .freq_strobe(freq_strobe), - .diff_stream(diff_stream), .diff_stream_strobe(diff_stream_strobe), - .glitch_catcher(glitch_catcher) + .ref_strobe(ref_strobe), + .frequency(frequency_w), .freq_strobe(freq_strobe), + .xcount(xcount) ); +// Nobody except some ancient USB debugging ever used this. +// It's harmless; if you don't attach to the diff_stream, +// diff_stream_strobe, or glitch_catcher ports, it will all +// just disappear in synthesis. +// +// Make xcount available to stream to host at 24 MByte/sec, which was +// especially interesting when reprogramming a AD9512 clock divider +// on a LLRF4 board. +// It might also be possible to histogram xcount. +reg [15:0] stream=0; +always @(posedge sysclk) begin + if (xcount > glitch_thresh) glitch_catcher <= ~glitch_catcher; + stream <= {stream[11:0], xcount}; // assumes freq_gcount gw=4 +end + +// Latch/pipeline one more time to perimeter of this module +always @(posedge sysclk) begin + diff_stream <= stream; + diff_stream_strobe <= stream_strobe; + frequency <= frequency_w; +end + endmodule diff --git a/dsp/freq_count_tb.v b/dsp/freq_count_tb.v index 1830deb0f7..3cf0fad999 100644 --- a/dsp/freq_count_tb.v +++ b/dsp/freq_count_tb.v @@ -16,7 +16,8 @@ initial begin end // Simulated accumulation interval is 20*64 = 1280 ns // Should catch an average of 1280/6 = 213.33 f_in edges in that time - if (frequency>212 && frequency < 215) begin + $display("frequency = %d", frequency); + if (frequency > 212 && frequency < 215) begin $display("PASS"); $finish(0); end else begin diff --git a/dsp/freq_gcount.v b/dsp/freq_gcount.v index cf9b1b29ce..183130d9ec 100644 --- a/dsp/freq_gcount.v +++ b/dsp/freq_gcount.v @@ -1,40 +1,39 @@ // Copied from the old freq_count, but with new g_in (gate) input added // Read the new name as frequency (gated) count. +// +// This version delegates reference counter function to the caller, +// to allow synchronization and resource-sharing across multiple +// freq_gcount instances. Note that if you want a _lot_ of frequency +// counter instances, maybe you should look into multi_counter.v. +// +// It no longer includes the obscure glitch diagnostics. Those are +// still available in freq_count, just in case anyone wants them. `timescale 1ns / 1ns module freq_gcount #( - // Default configuration useful for input frequencies < 96 MHz - parameter glitch_thresh=2, - parameter refcnt_width=24, + parameter gw=4, // Gray code counter width parameter freq_width=28, - parameter initv=0 + parameter initv=0 // output value for frequency at start-up ) ( // input clocks input sysclk, // timespec 8.0 ns input f_in, // unknown input - input g_in, // gate (f_in clock domain) + + // control input in f_in domain + input g_in, // gate (wire to 1 to get a simple frequency counter) + + // control input in sysclk domain + input ref_strobe, // typically one pulse every 2^24 sysclk cycles // outputs in sysclk domain - output reg [freq_width-1:0] frequency, + output [freq_width-1:0] frequency, output freq_strobe, - output reg [15:0] diff_stream, - output reg diff_stream_strobe, - // glitch_catcher can be routed to a physical pin to trigger - // a 'scope; see glitch_thresh parameter above - output reg glitch_catcher + output [gw-1:0] xcount // cycle-by-cycle gated count of f_in ); -initial begin - frequency=initv; - diff_stream=0; - diff_stream_strobe=0; - glitch_catcher=0; -end - -// four-bit Gray code counter on the input signal +// four-bit (nominal) Gray code counter on the input signal // https://en.wikipedia.org/wiki/Gray_code -localparam gw=4; reg [gw-1:0] gray1=0; // The following three expressions compute the next Gray code based on @@ -57,7 +56,7 @@ end // verilator lint_save // verilator lint_off UNOPTFLAT -wire [gw-1:0] bin3 = gray3 ^ {1'b0, bin3[gw-1:1]}; // convert Gray to binary +wire [gw-1:0] bin3 = gray3 ^ {1'b0, bin3[gw-1:1]}; // Gray to binary // verilator lint_restore reg [gw-1:0] bin4=0, bin5=0, diff1=0; @@ -65,37 +64,19 @@ always @(posedge sysclk) begin bin4 <= bin3; bin5 <= bin4; diff1 <= bin4-bin5; - if (diff1 > glitch_thresh) glitch_catcher <= ~glitch_catcher; end -// It might also be possible to histogram diff1, -// but for now just accumulate it to get a traditional frequency counter. -// Also make it available to stream to host at 24 MByte/sec, might be -// especially interesting when reprogramming a clock divider like -// the AD9512 on a LLRF4 board. -// In that case a 48 MHz sysclk / 2^24 = 2.861 Hz update +// Accumulate diff1 to get a traditional frequency counter reg [freq_width-1:0] accum=0, result=initv; -reg [refcnt_width-1:0] refcnt=0; -reg ref_carry=0; -reg [15:0] stream=0; -reg stream_strobe=0; -always @(posedge sysclk) begin - {ref_carry, refcnt} <= refcnt + 1; - if (ref_carry) result <= accum; - accum <= (ref_carry ? 28'b0 : accum) + diff1; - stream <= {stream[11:0],diff1}; - stream_strobe <= refcnt[1:0] == 0; -end - -// Latch/pipeline one more time to perimeter of this module -// to make routing easier reg freq_strobe_r=0; always @(posedge sysclk) begin - frequency <= result; - freq_strobe_r <= ref_carry; - diff_stream <= stream; - diff_stream_strobe <= stream_strobe; + accum <= (ref_strobe ? {freq_width{1'b0}} : accum) + diff1; + if (ref_strobe) result <= accum; + freq_strobe_r <= ref_strobe; // high when new data is valid end + +assign frequency = result; // Don't over-register assign freq_strobe = freq_strobe_r; +assign xcount = diff1; endmodule