From e760a611da905b740f50ab02a58ed6b845e78af3 Mon Sep 17 00:00:00 2001 From: Tristan Gingold Date: Fri, 3 Feb 2023 19:11:42 +0100 Subject: testsuite/synth: add a test for #2336 --- testsuite/synth/issue2336/ecp5pll.vhd | 427 ++++++++++++++++++++++++++++++ testsuite/synth/issue2336/testsuite.sh | 8 + testsuite/synth/issue2336/top_ecp5pll.vhd | 51 ++++ 3 files changed, 486 insertions(+) create mode 100644 testsuite/synth/issue2336/ecp5pll.vhd create mode 100755 testsuite/synth/issue2336/testsuite.sh create mode 100644 testsuite/synth/issue2336/top_ecp5pll.vhd (limited to 'testsuite') diff --git a/testsuite/synth/issue2336/ecp5pll.vhd b/testsuite/synth/issue2336/ecp5pll.vhd new file mode 100644 index 000000000..9fc305449 --- /dev/null +++ b/testsuite/synth/issue2336/ecp5pll.vhd @@ -0,0 +1,427 @@ +-- (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; diff --git a/testsuite/synth/issue2336/testsuite.sh b/testsuite/synth/issue2336/testsuite.sh new file mode 100755 index 000000000..d4aa0cfa4 --- /dev/null +++ b/testsuite/synth/issue2336/testsuite.sh @@ -0,0 +1,8 @@ +#! /bin/sh + +. ../../testenv.sh + +GHDL_STD_FLAGS=--std=08 +synth_failure top_ecp5pll.vhd ecp5pll.vhd -e top_ecp5pll + +echo "Test successful" diff --git a/testsuite/synth/issue2336/top_ecp5pll.vhd b/testsuite/synth/issue2336/top_ecp5pll.vhd new file mode 100644 index 000000000..a64aa187c --- /dev/null +++ b/testsuite/synth/issue2336/top_ecp5pll.vhd @@ -0,0 +1,51 @@ +-- (c)EMARD +-- License=BSD + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity top_ecp5pll is + generic + ( + bits: integer := 26 + ); + port + ( + clk_25mhz : in std_logic; -- main clock input from 25MHz clock source + led : out std_logic_vector(7 downto 0) + ); +end; + +architecture mix of top_ecp5pll is + type T_blink is array (0 to 3) of unsigned(bits-1 downto 0); + signal R_blink: T_blink; + signal clocks: std_logic_vector(3 downto 0); +begin + clkgen_inst: entity work.ecp5pll + generic map + ( + in_Hz => natural( 25.0e6), + out0_Hz => natural( 40.0e6), out0_tol_hz => 0, + out1_Hz => natural( 50.0e6), out1_deg => 90, out1_tol_hz => 0, + out2_Hz => natural( 60.0e6), out2_deg => 180, out2_tol_hz => 0, + out3_Hz => natural( 6.0e6), out3_deg => 300, out3_tol_hz => 10 + ) + port map + ( + clk_i => clk_25mhz, + clk_o => clocks + ); + + G_blinks: for i in 0 to 3 + generate + process(clocks(i)) + begin + if rising_edge(clocks(i)) then + R_blink(i) <= R_blink(i)+1; + end if; + led(2*i+1 downto 2*i) <= std_logic_vector(R_blink(i)(bits-1 downto bits-2)); + end process; + end generate; + +end mix; -- cgit v1.2.3