We have detected your current browser version is not the latest one. Xilinx.com uses the latest web technologies to bring you the best online experience possible. Please upgrade to a Xilinx.com supported browser:Chrome, Firefox, Internet Explorer 11, Safari. Thank you!

cancel
Showing results for
Did you mean:
Highlighted jmcclusk
Scholar
1,776 Views
Registered: ‎02-24-2014

How to do rational division of a clock to generate a clock enable. TECH TIP #1

This post has no questions, only answers.  It is purely expository.

I have seen so many ways to bungle the job of creating a clock enable at a lower frequency of the core clock, so I decided to write a VHDL component to do just this.    I've added the flexibility to specify the desired output frequency in multiple ways... as a frequency in MHz, or as a pulse interval in nanoseconds,  or even as just raw numerator and divider numbers.

The advantages of rational division are many,  if you want a 15 MHz output from a 100 MHz core clock, it's super easy..  just multiply by 3 and divide by 20.      Just like this:

dut: entity xil_defaultlib.rational_divider
Generic map (
G_input_clock_mhz  => 100.0,   -- input clock frequency in MHz
G_output_clock_mhz => 15.0    -- Output clock frequency in MHz (must be less than half input clock)
)
Port map (
clk      => clk,
rst_sync => rst,
tick_out => out_clk
);

There is some tricky code inside though..  It also will create dividers from DSP48E1 primitives, which have the nice advantage of not using any fabric, and also running at 500+ MHz, depending on your speed grade.

The DSP48E1 has some limitations, however.  It is capable of ternary add/subtract operations, but there are severe speed limitations if the OPMODE register is not enabled, and this consequently makes it unsuitable for a rational divider that implements conditional ternary operations at high speed.   BUT!!!  and this is the saving grace,  48 bits is quite a lot of precision, and a good approximation to a rational number can be made with a numerator fixed at 2**48 and a denominator of 48 bits.   In this case, the DSP48E1 operates as just a binary accumulator, effectively approximating the desired ratio.   But not precisely.   If a precise ratio is needed, then a fabric ternary accumulator is superior.

Here is the code.    Note the central function that searches the Stern-Brocot tree  (for an explanation of Stern-Brocot, see Wikipedia).   Although I didn't realize it at the time,  Vivado should produce at least double precision results from type REAL, so using DSP48E1 primitives will give 48 bit precision, and theoretically, up to the 54 bit precision for fabric accumulators.

-- This component does rational division of a clock, with an integer numerator and integer divider.  The output clock has jitter, but a precise average frequency.
-- The output can be specified either as a frequency in MHz, or as an output interval in ns.   Alternatively, the numerator and denominator can be specified as unsigned 48 bit fields
-- It can also use a DSP48E1 element as an accumulator, where the numerator is fixed at 2**48, and the denominator approximates the desired ratio.  The advantage
-- of the DSP48E1 is raw speed, typically 500 MHz, but because the numerator is fixed at 2**48, many ratios can only be approximated, typically with 26 to 30 bit precision.
-- The computation of the 48 bit accumulator increment actually needs double precision accuracy for type REAL, but most synthesis tools only support single precision for type REAL.
-- THere is another flag for "one shot" operation, producing a single output pulse for every reset pulse on the "rst_sync" input.  In this mode, if the output
-- is fed back to the rst_sync input, it will oscillate with exactly the same period forever, since the accumulator is reset on each cycle.
--
-- A word of warning about the parameter G_max_accum_bits.   If this parameter is raised higher than 24 bits, the algorithm for converting a REAL ratio to a rational pair of integers
-- may produce a very large pair of integers that approximate the desired ratio, because of roundoff error in the floating point.
--
-- Author:  John.McCluskey at gmail
-- Released April 4, 2018
--
Library UNISIM;
use UNISIM.vcomponents.all;

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.MATH_REAL.ALL;

entity rational_divider is
Generic (
G_input_clock_mhz       : real := 100.0;     -- input clock frequency in MHz
G_output_clock_mhz      : real := 0.0;       -- Output clock frequency in MHz (must be less than or equal to half the input clock frequency)
G_output_interval_ns    : real := 0.0;       -- Alternate parameter for output tick interval in ns.  G_output_clock_mhz must be 0.0 if this is used!
G_max_accum_bits        : natural := 24;     -- If set higher then 24 bits, best to simulate to verify that the numerator/denominator pair are satisfactory.
G_one_shot              : boolean := false;  -- If true, only one output pulse is produced after each reset.
G_use_dsp48             : boolean := false;  -- Will use G_max_accum_bits fabric accumulator (or shorter) if false, 48 bit DSP48E1 if true
G_alt_multiply          : unsigned(47 downto 0) := (others => '0');   -- Alternate parameter to directly specify the multiplier, only used if non-zero
G_alt_divide            : unsigned(47 downto 0) := (others => '0')    -- Alternate parameter to directly specify the divider, only used if non-zero (both must be used!)
);
Port (
clk         : in std_logic;         -- global input clock from a clock buffer.   Connect it to a local clock at your own peril.
clk_en      : in std_logic := '1';  -- pro-forma clock enable
rst_sync    : in std_logic := '0';  -- synchronous reset with priority over the clock enable.
tick_out    : out std_logic := '0'  -- clock tick signal usable in the input clock domain.  This is a single cycle pulse at the desired AVERAGE frequency.  There will be JITTER.
);                         -- the output SHOULD NOT be connected to a clock buffer, unless you know how to use a BUFGCE and multicycle constraints.
end rational_divider;        -- The output is intended as a clock enable for sub-circuits that need to run at a fractional rate of the global clock.
-- it can also be used as a timer tick with a precise AVERAGE interval.  There will be jitter, of course.

architecture Behavioral of rational_divider is

function ulogb2( i : unsigned ) return natural is  -- calculate log2 of unsigned number.. could be more than 32!
variable temp    : unsigned(i'range) := i;
variable ret_val : natural := 1;
begin
while temp /= 0 loop
ret_val := ret_val + 1;
temp    := shift_right(temp,1);
end loop;
assert FALSE report "rational_divider: Accumulator length is " & integer'image(ret_val) severity NOTE;
return ret_val;
end function;

type ratio_vector is array(natural range <>) of unsigned(63 downto 0);

-- search the Stern-Brocot tree for the best rational approximation
-- The ratio of Fout/Fin should be in the range of (0.0,0.5] but not equal to 0
function rat_approx( Fin, Fout, Tint : real; u,v : unsigned(47 downto 0); flag : boolean ) return ratio_vector is
constant mt : unsigned(63 downto 0) := (G_max_accum_bits-1 => '1', others => '0');
constant md : unsigned(63 downto 0) := mt - 1;
variable f, f2  : real;
variable h  : ratio_vector(0 to 2) := ( (others => '0'), (0=>'1',others=>'0'), (others =>'0'));
variable k  : ratio_vector(0 to 2) := ( (0=>'1',others => '0'), (others=>'0'), (others =>'0'));
variable a, x, d, n  : unsigned(md'range) := (0=>'1', others => '0');
variable ret_val : ratio_vector(0 to 1);
variable i : integer := 0;
begin

Here is a simulation of what you get with a 100 MHz input clock, and requested 15 MHz output.   Note that this is NOT a perfect clock like you get from an MMCM or PLL.    It has a basic jitter of 10 ns, which is the input clock.   But on average, the frequency is perfect, exactly 15.0000 MHz. Don't forget to close a thread when possible by accepting a post as a solution.
Tags (2)
6 Replies markcurry
Scholar
1,726 Views
Registered: ‎09-16-2009

Re: How to do rational division of a clock to generate a clock enable. TECH TIP #1

Well done, and thanks for posting the complete code.  I now have a new method to learn - Stern-Brocot, and code that implements it.

It's post like these that make this forum an excellent resource.  I've learned something new here today.

Thanks again.

Mark brimdavis
Scholar
1,680 Views
Registered: ‎04-26-2012

Re: How to do rational division of a clock to generate a clock enable. TECH TIP #1

@jmcclusk  "This is also a prime example of code that could benefit from double precision implementation of type REAL.   (ENHANCEMENT REQUEST)"

IIRC, the LRM specifies a minimum real precision of 6 decimal digits for VHDL-93, and IEEE double precision (64 bit) for VHDL-2008.

The following test code:

constant real_F1 : real := 1234567890.1234567890;
constant real_F2 : real := real'high;
constant real_F3 : real := real'low;

-- 15 digit subtraction test
constant real_T1 : real := 123456789012345.0;
constant real_T2 : real := 123456789012344.0;
...
assert FALSE report "real_F1: " & real'image(real_F1) severity NOTE;
assert FALSE report "real_F2: " & real'image(real_F2) severity NOTE;
assert FALSE report "real_F2_log10: " & real'image(log10(real_F2)) severity NOTE;
assert FALSE report "real_F3: " & real'image(real_F3) severity NOTE;

assert FALSE report "real_T1-real_T2: " & real'image(real_T1-real_T2) severity NOTE;

Synthesized with 2017.4 as "VHDL" (aka VHDL-93):

INFO: [Synth 8-63] RTL assertion: "real_F1: 1234567936.000000" [C:/Projects/vivado_tests/real_comparison/compare_reals.vhd:36]
INFO: [Synth 8-63] RTL assertion: "real_F2: inf" [C:/Projects/vivado_tests/real_comparison/compare_reals.vhd:37]
INFO: [Synth 8-63] RTL assertion: "real_F2_log10: 308.000000" [C:/Projects/vivado_tests/real_comparison/compare_reals.vhd:38]
INFO: [Synth 8-63] RTL assertion: "real_F3: -inf" [C:/Projects/vivado_tests/real_comparison/compare_reals.vhd:39]
INFO: [Synth 8-63] RTL assertion: "real_T1-real_T2: 1.000000" [C:/Projects/vivado_tests/real_comparison/compare_reals.vhd:41]

Synthesized with 2017.4 as "VHDL-2008" :

INFO: [Synth 8-63] RTL assertion: "real_F1: 1234567936.000000" [C:/Projects/vivado_tests/real_comparison/compare_reals.vhd:36]
INFO: [Synth 8-63] RTL assertion: "real_F2: inf" [C:/Projects/vivado_tests/real_comparison/compare_reals.vhd:37]
INFO: [Synth 8-63] RTL assertion: "real_F2_log10: 308.000000" [C:/Projects/vivado_tests/real_comparison/compare_reals.vhd:38]
INFO: [Synth 8-63] RTL assertion: "real_F3: -inf" [C:/Projects/vivado_tests/real_comparison/compare_reals.vhd:39]
INFO: [Synth 8-63] RTL assertion: "real_T1-real_T2: 1.000000" [C:/Projects/vivado_tests/real_comparison/compare_reals.vhd:41]

Conclusions:

The exponent of 308 suggests double precision IEEE, as does the 15-digit subtraction test result.

But the number of significant digits printed by real'image for real_f1 looks like single precision, and appears to internally use a fixed width printf format.

Which suggests that although the real'image string routines are implemented using a single precision sprintf, reals are actually being stored using double precision.

I haven't tried checking the various real library routines for argument/return value ranges to test whether they are using single or double precision.

-Brian richardhead
Scholar
1,641 Views
Registered: ‎08-01-2012

Re: How to do rational division of a clock to generate a clock enable. TECH TIP #1

Actually, the real type is still open to be however many bits the implemetor wants, but it is the annomous type universal_real that is required to be 64 bits:

"An implementation shall choose a representation for all floating-point types except for universal_real that
conforms either to IEEE Std 754-1985 or to IEEE Std 854-1987; in either case, a minimum representation
size of 64 bits is required for this chosen representation."

Then, the real type (like the integer type), is declared like this:

type real is universal_real range LOW to HIGH;

Where Low and High are set by the implementor.

I also wonder if they've updated their textio code, as 'image refers to the textio.write for real, with DIGITS parameter set to 0. Have you also tried the to_string function? brimdavis
Scholar
1,633 Views
Registered: ‎04-26-2012

Re: How to do rational division of a clock to generate a clock enable. TECH TIP #1

@richardhead  "Actually, the real type is still open to be however many bits the implemetor wants, but it is the annomous type universal_real that is required to be 64 bits"

In the sentence you quoted, I would note that universal real is part of the "except" clause - so I would read this sentence as requiring real to use at least IEEE 64 bit:

"An implementation shall choose a representation for all floating-point types except for universal_real that
conforms either to IEEE Std 754-1985 or to IEEE Std 854-1987; in either case, a minimum representation
size of 64 bits is required for this chosen representation."

I don't have an LRM at hand, but my copy of "VHDL-2008 Just the New Stuff" also says real uses IEEE 64 bit double precision (pages 186-187).

> Have you also tried the to_string function?

No, I haven't experimented with that (or other real library functions) yet.

I noticed just recently that the internal Vivado Synthesis representation appeared to be double precision while prototyping a synthesizable string=>real parser to work around IPI generic limitations; I had to write a parser because real'value isn't synthesizable in Vivado for constant initialization.

-Brian

EDIT: to_string(real{,format}) doesn't appear to be supported for Synthesis assertions (VHDL-2008) in 2017.4

Either of these:
assert FALSE report "real_F2: " & to_string(real_F2) severity NOTE;
assert FALSE report "real_F2: " & to_string(real_F2,"%e") severity NOTE;

Produce:
ERROR: [Synth 8-5883] invalid operator type for expression [C:/Projects/vivado_tests/real_comparison/compare_reals.vhd:41] hgleamon1
Mentor
1,603 Views
Registered: ‎11-14-2011

Re: How to do rational division of a clock to generate a clock enable. TECH TIP #1

This is quite an impressive implementation and I can see advantages for awkward division ratios.

Can you give an idea of implementation size / resource usage from this?

(apologies but I'm a little lazy to just figure it out myself :) )

----------
"That which we must learn to do, we learn by doing." - Aristotle jmcclusk
Scholar
1,580 Views
Registered: ‎02-24-2014

Re: How to do rational division of a clock to generate a clock enable. TECH TIP #1

Thank you so much, Brian!!   I had seen some synthesis results that suggested Vivado was using double precision, but this proves it.   I will be rewriting other code (plus tweaking this code) to take advantage of the double precision in type REAL..

Don't forget to close a thread when possible by accepting a post as a solution.