-- (c)EMARD -- License=BSD -- parametric ECP5 PLL generator in VHDL -- to see actual frequencies and phase shifts -- trellis log/stdout : search for "MHz", "Derived", "frequency" -- diamond log/*.mrp : search for "MHz", "Frequency", "Phase", "Desired" library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity ecp5pll is generic ( in_hz : natural := 25000000; out0_hz : natural := 25000000; out0_deg : natural := 0; -- keep 0 out0_tol_hz : natural := 0; -- Hz tolerance out1_hz : natural := 0; -- 0 to disable out1_deg : natural := 0; out1_tol_hz : natural := 0; out2_hz : natural := 0; out2_deg : natural := 0; out2_tol_hz : natural := 0; out3_hz : natural := 0; out3_deg : natural := 0; out3_tol_hz : natural := 0; reset_en : natural := 0; standby_en : natural := 0; dynamic_en : natural := 0 ); port ( clk_i : in std_logic; clk_o : out std_logic_vector(3 downto 0); reset : in std_logic := '0'; standby : in std_logic := '0'; phasesel : in std_logic_vector(1 downto 0) := "00"; phasedir, phasestep, phaseloadreg : std_logic := '0'; locked : out std_logic ); end; architecture mix of ecp5pll is type T_clocks is record in_hz : natural; out0_hz : natural; out0_deg : natural; out1_hz : natural; out1_deg : natural; out2_hz : natural; out2_deg : natural; out3_hz : natural; out3_deg : natural; end record T_clocks; type T_secondary is record div : natural; freq_string : string(0 to 9); freq : natural; phase : natural; cphase : natural; fphase : natural; end record T_secondary; type A_secondary is array(1 to 3) of T_secondary; type A_srequest is array(1 to 3) of natural; type T_params is record result : T_clocks; refclk_div : natural; feedback_div : natural; output_div : natural; fin_string : string(0 to 9); fout_string : string(0 to 9); fout : natural; fvco : natural; primary_cphase : natural; primary_fphase : natural; secondary : A_secondary; end record T_params; function enabled_str(en: integer) return string is begin if en = 0 then return "DISABLED"; else return "ENABLED"; end if; end enabled_str; function Hz2MHz_str(int: integer) return string is constant base : natural := 10; constant digit : string(0 to 9) := "0123456789"; variable temp : string(0 to 8) := (others => '0'); variable power : natural := 1; begin -- convert integer to string for i in temp'high downto 0 loop temp(i) := digit(int/power mod base); power := power * base; end loop; -- insert decimal point "123.456789" return temp(0 to 2) & "." & temp(3 to temp'high); end Hz2MHz_str; function F_ecp5pll(request: T_clocks) return T_params is constant PFD_MIN: natural := 3125000; constant PFD_MAX: natural := 400000000; constant VCO_MIN: natural := 400000000; constant VCO_MAX: natural := 800000000; constant VCO_OPTIMAL: natural := (VCO_MIN+VCO_MAX)/2; variable params: T_params; variable input_div, input_div_min, input_div_max : natural; variable feedback_div, feedback_div_min, feedback_div_max : natural; variable output_div, output_div_min, output_div_max : natural; variable fout: natural; variable fvco: natural := 0; variable error, error_prev: natural := 999999999; variable result: T_clocks; variable sfreq, sphase: A_srequest; variable div: natural; variable freq: natural; variable phase_compensation: natural; variable phase_count_x8: natural; variable phase_shift: natural; begin params.fvco := 0; sfreq(1) := request.out1_hz; sphase(1) := request.out1_deg; sfreq(2) := request.out2_hz; sphase(2) := request.out2_deg; sfreq(3) := request.out3_hz; sphase(3) := request.out3_deg; -- generate primary output -- search 128*80*128=1.3e6 combination space to find the best match -- loop over allowed MIN/MAX range only input_div_min := in_hz/PFD_MAX; if input_div_min < 1 then input_div_min := 1; end if; input_div_max := in_hz/PFD_MIN; if input_div_max > 128 then input_div_max := 128; end if; for input_div in input_div_min to input_div_max loop if out0_hz / 1000000 * input_div < 2000 then feedback_div := out0_hz * input_div / in_hz; else feedback_div := out0_hz / in_hz * input_div; end if; if feedback_div < 2 then feedback_div_min := 1; else feedback_div_min := feedback_div-1; end if; if feedback_div > 79 then feedback_div_max := 80; else feedback_div_max := feedback_div+1; end if; for feedback_div in feedback_div_min to feedback_div_max loop output_div_min := (VCO_MIN/feedback_div) / (in_hz/input_div); if output_div_min < 1 then output_div_min := 1; end if; output_div_max := (VCO_MAX/feedback_div) / (in_hz/input_div); if output_div_max > 128 then output_div_max := 128; end if; fout := in_hz * feedback_div / input_div; for output_div in output_div_min to output_div_max loop fvco := fout * output_div; error := abs(fout-out0_hz); for channel in 1 to 3 loop if sfreq(channel) > 0 then div := 1; if fvco >= sfreq(channel) then div := fvco/sfreq(channel); end if; freq := fvco/div; error := error + abs(freq-sfreq(channel)); end if; end loop; if error < error_prev -- prefer least error or (error=error_prev and abs(fvco-VCO_OPTIMAL) < abs(params.fvco-VCO_OPTIMAL)) -- or if 0 error prefer closest to optimal VCO frequency then error_prev := error; phase_compensation := (output_div+1)/2*8-8+output_div/2*8; -- output_div/2*8 = 180 deg shift phase_count_x8 := phase_compensation + 8*output_div*out0_deg/360; if phase_count_x8 > 1023 then phase_count_x8 := phase_count_x8 mod (output_div*8); -- wraparound 360 deg end if; params.refclk_div := input_div; params.feedback_div := feedback_div; params.output_div := output_div; params.fin_string := Hz2MHz_str(in_hz); params.fout_string := Hz2MHz_str(fout); params.fout := in_hz * feedback_div / input_div; params.fvco := fvco; params.primary_cphase := phase_count_x8 / 8; params.primary_fphase := phase_count_x8 mod 8; end if; end loop; end loop; end loop; -- generate secondary outputs for channel in 1 to 3 loop if sfreq(channel) > 0 then div := 1; if params.fvco >= sfreq(channel) then div := params.fvco/sfreq(channel); end if; freq := params.fvco/div; phase_compensation := div*8-8; phase_count_x8 := phase_compensation + 8*div*sphase(channel)/360; if phase_count_x8 > 1023 then phase_count_x8 := phase_count_x8 mod (div*8); -- wraparound 360 deg end if; phase_shift := 8*div*sphase(channel)/360 * 360 / (8*div); -- reported phase shift params.secondary(channel).div := div; params.secondary(channel).freq_string := Hz2MHz_str(freq); params.secondary(channel).freq := freq; params.secondary(channel).phase := phase_shift; params.secondary(channel).cphase := phase_count_x8 / 8; params.secondary(channel).fphase := phase_count_x8 mod 8; else params.secondary(channel).div := 1; end if; end loop; params.result.in_hz := request.in_hz; params.result.out0_hz := natural(params.fout); params.result.out0_deg := 0; -- FIXME params.result.out1_hz := natural(params.secondary(1).freq); params.result.out1_deg := natural(params.secondary(1).phase); params.result.out2_hz := natural(params.secondary(2).freq); params.result.out2_deg := natural(params.secondary(2).phase); params.result.out3_hz := natural(params.secondary(3).freq); params.result.out3_deg := natural(params.secondary(3).phase); return params; end F_ecp5pll; constant request : T_clocks := ( in_hz => in_hz, out0_hz => out0_hz, out0_deg => out0_deg, out1_hz => out1_hz, out1_deg => out1_deg, out2_hz => out2_hz, out2_deg => out2_deg, out3_hz => out3_hz, out3_deg => out3_deg ); constant params : T_params := F_ecp5pll(request); component EHXPLLL generic ( CLKI_DIV : integer := 1; CLKFB_DIV : integer := 1; CLKOP_DIV : integer := 8; CLKOS_DIV : integer := 8; CLKOS2_DIV : integer := 8; CLKOS3_DIV : integer := 8; CLKOP_ENABLE : string := "ENABLED"; CLKOS_ENABLE : string := "DISABLED"; CLKOS2_ENABLE : string := "DISABLED"; CLKOS3_ENABLE : string := "DISABLED"; CLKOP_CPHASE : integer := 0; CLKOS_CPHASE : integer := 0; CLKOS2_CPHASE : integer := 0; CLKOS3_CPHASE : integer := 0; CLKOP_FPHASE : integer := 0; CLKOS_FPHASE : integer := 0; CLKOS2_FPHASE : integer := 0; CLKOS3_FPHASE : integer := 0; FEEDBK_PATH : string := "CLKOP"; CLKOP_TRIM_POL : string := "RISING"; CLKOP_TRIM_DELAY : integer := 0; CLKOS_TRIM_POL : string := "RISING"; CLKOS_TRIM_DELAY : integer := 0; OUTDIVIDER_MUXA : string := "DIVA"; OUTDIVIDER_MUXB : string := "DIVB"; OUTDIVIDER_MUXC : string := "DIVC"; OUTDIVIDER_MUXD : string := "DIVD"; PLL_LOCK_MODE : integer := 0; PLL_LOCK_DELAY : integer := 200; STDBY_ENABLE : string := "DISABLED"; REFIN_RESET : string := "DISABLED"; SYNC_ENABLE : string := "DISABLED"; INT_LOCK_STICKY : string := "ENABLED"; DPHASE_SOURCE : string := "DISABLED"; PLLRST_ENA : string := "DISABLED"; INTFB_WAKE : string := "DISABLED" ); port ( CLKI, CLKFB, RST, STDBY, PLLWAKESYNC, PHASESEL1, PHASESEL0, PHASEDIR, PHASESTEP, PHASELOADREG, ENCLKOP, ENCLKOS, ENCLKOS2, ENCLKOS3 : IN std_logic := 'X'; CLKOP, CLKOS, CLKOS2, CLKOS3, LOCK, INTLOCK, REFCLK, CLKINTFB : OUT std_logic := 'X' ); end component; -- internal signal declarations signal CLKOP_t : std_logic; signal REFCLK : std_logic; signal PHASESEL_HW : std_logic_vector(1 downto 0); attribute FREQUENCY_PIN_CLKI : string; attribute FREQUENCY_PIN_CLKOP : string; attribute FREQUENCY_PIN_CLKOS : string; attribute FREQUENCY_PIN_CLKOS2 : string; attribute FREQUENCY_PIN_CLKOS3 : string; attribute FREQUENCY_PIN_CLKI of PLL_inst : label is params.fin_string; attribute FREQUENCY_PIN_CLKOP of PLL_inst : label is params.fout_string; attribute FREQUENCY_PIN_CLKOS of PLL_inst : label is params.secondary(1).freq_string; attribute FREQUENCY_PIN_CLKOS2 of PLL_inst : label is params.secondary(2).freq_string; attribute FREQUENCY_PIN_CLKOS3 of PLL_inst : label is params.secondary(3).freq_string; attribute ICP_CURRENT : string; attribute LPF_RESISTOR : string; attribute ICP_CURRENT of PLL_inst : label is "12"; attribute LPF_RESISTOR of PLL_inst : label is "8"; attribute syn_keep : boolean; attribute NGD_DRC_MASK : integer; attribute NGD_DRC_MASK of mix : architecture is 1; begin assert false report "clk_o(0) " & Hz2MHz_str(params.result.out0_hz) & " MHz " & Hz2MHz_str(params.result.out0_deg*1000000) & " deg" severity note; assert false report "clk_o(1) " & Hz2MHz_str(params.result.out1_hz) & " MHz " & Hz2MHz_str(params.result.out1_deg*1000000) & " deg" severity note; assert false report "clk_o(2) " & Hz2MHz_str(params.result.out2_hz) & " MHz " & Hz2MHz_str(params.result.out2_deg*1000000) & " deg" severity note; assert false report "clk_o(3) " & Hz2MHz_str(params.result.out3_hz) & " MHz " & Hz2MHz_str(params.result.out3_deg*1000000) & " deg" severity note; assert abs(out0_hz - params.result.out0_hz) <= out0_tol_hz report "clk_o(0) " & Hz2MHz_str(params.result.out0_hz) & " MHz exceeds " & Hz2MHz_str(out0_hz) & "+-" & Hz2MHz_str(out0_tol_hz) & " MHz out0_tol_hz" severity failure; assert abs(out1_hz - params.result.out1_hz) <= out1_tol_hz report "clk_o(1) " & Hz2MHz_str(params.result.out1_hz) & " MHz exceeds " & Hz2MHz_str(out1_hz) & "+-" & Hz2MHz_str(out1_tol_hz) & " MHz out1_tol_hz" severity failure; assert abs(out2_hz - params.result.out2_hz) <= out2_tol_hz report "clk_o(2) " & Hz2MHz_str(params.result.out2_hz) & " MHz exceeds " & Hz2MHz_str(out2_hz) & "+-" & Hz2MHz_str(out2_tol_hz) & " MHz out2_tol_hz" severity failure; assert abs(out3_hz - params.result.out3_hz) <= out3_tol_hz report "clk_o(3) " & Hz2MHz_str(params.result.out3_hz) & " MHz exceeds " & Hz2MHz_str(out3_hz) & "+-" & Hz2MHz_str(out3_tol_hz) & " MHz out3_tol_hz" severity failure; G_dynamic: if dynamic_en /= 0 generate PHASESEL_HW <= std_logic_vector(unsigned(phasesel)-1); end generate; PLL_inst: EHXPLLL generic map ( CLKI_DIV => params.refclk_div, CLKFB_DIV => params.feedback_div, FEEDBK_PATH => "CLKOP", OUTDIVIDER_MUXA => "DIVA", CLKOP_ENABLE => enabled_str(out0_hz), CLKOP_DIV => params.output_div, CLKOP_CPHASE => params.primary_cphase, CLKOP_FPHASE => params.primary_fphase, -- CLKOP_TRIM_DELAY=> 0, CLKOP_TRIM_POL=> "FALLING", OUTDIVIDER_MUXB => "DIVB", CLKOS_ENABLE => enabled_str(out1_hz), CLKOS_DIV => params.secondary(1).div, CLKOS_CPHASE => params.secondary(1).cphase, CLKOS_FPHASE => params.secondary(1).fphase, -- CLKOS_TRIM_DELAY=> 0, CLKOS_TRIM_POL=> "FALLING", OUTDIVIDER_MUXC => "DIVC", CLKOS2_ENABLE => enabled_str(out2_hz), CLKOS2_DIV => params.secondary(2).div, CLKOS2_CPHASE => params.secondary(2).cphase, CLKOS2_FPHASE => params.secondary(2).fphase, OUTDIVIDER_MUXD => "DIVD", CLKOS3_ENABLE => enabled_str(out3_hz), CLKOS3_DIV => params.secondary(3).div, CLKOS3_CPHASE => params.secondary(3).cphase, CLKOS3_FPHASE => params.secondary(3).fphase, INTFB_WAKE => "DISABLED", STDBY_ENABLE => enabled_str(standby_en), PLLRST_ENA => enabled_str(reset_en), DPHASE_SOURCE => enabled_str(dynamic_en), PLL_LOCK_MODE => 0 ) port map ( CLKI => clk_i, CLKFB => CLKOP_t, RST => reset, STDBY => standby, PLLWAKESYNC => '0', PHASESEL1 => PHASESEL_HW(1), PHASESEL0 => PHASESEL_HW(0), PHASEDIR => phasedir, PHASESTEP => phasestep, PHASELOADREG => phaseloadreg, ENCLKOP =>'0', ENCLKOS => '0', ENCLKOS2 => '0', ENCLKOS3 => '0', CLKOP => CLKOP_t, CLKOS => clk_o(1), CLKOS2 => clk_o(2), CLKOS3 => clk_o(3), INTLOCK => open, REFCLK => REFCLK, CLKINTFB => open, LOCK => locked ); clk_o(0) <= CLKOP_t; end mix;