--- /dev/null
+from collections import namedtuple, OrderedDict
+
+from nmigen import *
+
+
+__all__ = ["PLL_LatticeECP5"]
+
+
+class PLL_LatticeECP5(Elaboratable):
+ class Parameters:
+ class Output(namedtuple("Output", ["domain", "freq", "div", "cphase", "fphase"])):
+ """PLL output parameters."""
+ __slots__ = ()
+
+ """PLL parameters for Lattice ECP5 FPGAs.
+
+ Parameters
+ ----------
+ i_domain : str
+ Input clock domain.
+ i_freq : int or float
+ Input clock frequency, in Hz.
+ i_reset_less : bool
+ If `True`, the input clock domain does not use a reset signal. Defaults to `True`.
+ o_domain : str
+ Primary output clock domain.
+ o_freq : int or float
+ Primary output clock frequency, in Hz.
+ internal_fb : bool
+ Internal feedback mode. Optional. Defaults to `False`.
+
+ Attributes
+ ----------
+ i_domain : str
+ Input clock domain.
+ i_freq : int
+ Input clock frequency, in Hz.
+ i_reset_less : bool
+ If `True`, the input clock domain does not use a reset signal.
+ i_div : int
+ Input clock divisor.
+ o_domain : str
+ Primary output clock domain.
+ o_freq : int
+ Primary output clock frequency, in Hz.
+ fb_internal : bool
+ Internal feedback mode.
+ fb_div : int
+ Feedback clock divisor.
+ op : :class:`PLL_LatticeECP5.Parameters.Output`
+ Primary output parameters.
+ os, os2, os3 : :class:`PLL_LatticeECP5.Parameters.Output` or None
+ Secondary output parameters, or `None` if absent.
+ """
+ def __init__(self, *, i_domain, i_freq, o_domain, o_freq, i_reset_less=True, fb_internal=False):
+ if not isinstance(i_domain, str):
+ raise TypeError("Input domain must be a string, not {!r}"
+ .format(i_domain))
+ if not isinstance(i_freq, (int, float)):
+ raise TypeError("Input frequency must be an integer or a float, not {!r}"
+ .format(i_freq))
+ if not 8e6 <= i_freq <= 400e6:
+ raise ValueError("Input frequency must be between 8 and 400 MHz, not {} MHz"
+ .format(i_freq / 1e6))
+ if not isinstance(o_domain, str):
+ raise TypeError("Output domain must be a string, not {!r}"
+ .format(o_domain))
+ if not isinstance(o_freq, (int, float)):
+ raise TypeError("Output frequency must be an integer or a float, not {!r}"
+ .format(o_freq))
+ if not 10e6 <= o_freq <= 400e6:
+ raise ValueError("Output frequency must be between 10 and 400 MHz, not {} MHz"
+ .format(o_freq / 1e6))
+
+ self.i_domain = i_domain
+ self.i_freq = int(i_freq)
+ self.i_reset_less = bool(i_reset_less)
+ self.o_domain = o_domain
+ self.o_freq = int(o_freq)
+ self.fb_internal = bool(fb_internal)
+
+ self._i_div = None
+ self._fb_div = None
+ self._op = None
+ self._os = None
+ self._os2 = None
+ self._os3 = None
+ self._2nd_outputs = OrderedDict()
+ self._frozen = False
+
+ @property
+ def i_div(self):
+ self.compute()
+ return self._i_div
+
+ @property
+ def fb_div(self):
+ self.compute()
+ return self._fb_div
+
+ @property
+ def op(self):
+ self.compute()
+ return self._op
+
+ @property
+ def os(self):
+ self.compute()
+ return self._os
+
+ @property
+ def os2(self):
+ self.compute()
+ return self._os2
+
+ @property
+ def os3(self):
+ self.compute()
+ return self._os3
+
+ def add_secondary_output(self, *, domain, freq, phase=0.0):
+ """Add secondary PLL output.
+
+ Arguments
+ ---------
+ domain : str
+ Output clock domain.
+ freq : int
+ Output clock frequency.
+ phase : int or float
+ Output clock phase, in degrees. Optional. Defaults to 0.
+ """
+ if self._frozen:
+ raise ValueError("PLL parameters have already been computed. Other outputs cannot "
+ "be added")
+ if not isinstance(domain, str):
+ raise TypeError("Output domain must be a string, not {!r}"
+ .format(domain))
+ if not isinstance(freq, (int, float)):
+ raise TypeError("Output frequency must be an integer or a float, not {!r}"
+ .format(freq))
+ if not 10e6 <= freq <= 400e6:
+ raise ValueError("Output frequency must be between 10 and 400 MHz, not {} MHz"
+ .format(freq / 1e6))
+ if not isinstance(phase, (int, float)):
+ raise TypeError("Output phase must be an integer or a float, not {!r}"
+ .format(phase))
+ if not 0 <= phase <= 360:
+ raise ValueError("Output phase must be between 0 and 360 degrees, not {}"
+ .format(phase))
+ if len(self._2nd_outputs) == 3:
+ raise ValueError("This PLL can drive at most 3 secondary outputs")
+ if domain in self._2nd_outputs:
+ raise ValueError("Output domain '{}' has already been added".format(domain))
+
+ self._2nd_outputs[domain] = freq, phase
+
+ def _iter_variants(self):
+ for i_div in range(1, 128 + 1):
+ pfd_freq = self.i_freq / i_div
+ if not 3.125e6 <= pfd_freq <= 400e6:
+ continue
+ for fb_div in range(1, 80 + 1):
+ for op_div in range(1, 128 + 1):
+ vco_freq = pfd_freq * fb_div * op_div
+ if not 400e6 <= vco_freq <= 800e6:
+ continue
+ op_freq = vco_freq / op_div
+ if not 10e6 <= op_freq <= 400e6:
+ continue
+ yield (i_div, fb_div, op_div, pfd_freq, op_freq)
+
+ def compute(self):
+ """Compute PLL parameters.
+
+ This method is idempotent. As a side-effect of its first call, the visible state of the
+ :class:`PLL_LatticeECP5.Parameters` instance becomes immutable (e.g. adding more PLL outputs
+ will fail).
+ """
+ if self._frozen:
+ return
+
+ variants = list(self._iter_variants())
+ if not variants:
+ raise ValueError("Input ({} MHz) to primary output ({} MHz) constraint was not "
+ "satisfied"
+ .format(self.i_freq / 1e6, self.o_freq / 1e6))
+
+ def error(variant):
+ i_div, fb_div, op_div, pfd_freq, op_freq = variant
+ vco_freq = pfd_freq * fb_div * op_div
+ return abs(op_freq - self.o_freq), abs(vco_freq - 600e6), abs(pfd_freq - 200e6)
+
+ i_div, fb_div, op_div, pfd_freq, op_freq = min(variants, key=error)
+
+ vco_freq = pfd_freq * fb_div * op_div
+ op_shift = (1 / op_freq) * 0.5
+
+ self._i_div = i_div
+ self._fb_div = fb_div
+
+ self._op = PLL_LatticeECP5.Parameters.Output(
+ domain = self.o_domain,
+ freq = op_freq,
+ div = op_div,
+ cphase = op_shift * vco_freq,
+ fphase = 0,
+ )
+
+ for i, (os_domain, (os_freq, os_phase)) in enumerate(self._2nd_outputs.items()):
+ os_name = "_os{}".format(i + 1 if i > 0 else "")
+ os_shift = (1 / os_freq) * os_phase / 360.0
+ os_params = PLL_LatticeECP5.Parameters.Output(
+ domain = os_domain,
+ freq = os_freq,
+ div = vco_freq // os_freq,
+ cphase = self._op.cphase + (os_shift * vco_freq),
+ fphase = 0,
+ )
+ setattr(self, os_name, os_params)
+
+ self._frozen = True
+
+ """PLL for Lattice ECP5 FPGAs.
+
+ Parameters
+ ----------
+ params : :class:`PLL_LatticeECP5.Parameters`
+ PLL parameters.
+
+ Attributes
+ ----------
+ params : :class:`PLL_LatticeECP5.Parameters`
+ PLL parameters.
+ locked : Signal(), out
+ PLL lock status.
+ """
+ def __init__(self, params):
+ if not isinstance(params, PLL_LatticeECP5.Parameters):
+ raise TypeError("PLL parameters must be an instance of PLL_LatticeECP5.Parameters, not {!r}"
+ .format(params))
+
+ params.compute()
+ self.params = params
+ self.locked = Signal()
+
+ def elaborate(self, platform):
+ pll_kwargs = {
+ "a_ICP_CURRENT" : 12,
+ "a_LPF_RESISTOR" : 8,
+ "a_MFG_ENABLE_FILTEROPAMP" : 1,
+ "a_MFG_GMCREF_SEL" : 2,
+ "p_INTFB_WAKE" : "DISABLED",
+ "p_STDBY_ENABLE" : "DISABLED",
+ "p_DPHASE_SOURCE" : "DISABLED",
+ "p_OUTDIVIDER_MUXA" : "DIVA",
+ "p_OUTDIVIDER_MUXB" : "DIVB",
+ "p_OUTDIVIDER_MUXC" : "DIVC",
+ "p_OUTDIVIDER_MUXD" : "DIVD",
+
+ "i_PHASESEL0" : Const(0),
+ "i_PHASESEL1" : Const(0),
+ "i_PHASEDIR" : Const(1),
+ "i_PHASESTEP" : Const(1),
+ "i_PHASELOADREG" : Const(1),
+ "i_PLLWAKESYNC" : Const(0),
+ "i_ENCLKOP" : Const(0),
+
+ "o_LOCK" : self.locked,
+
+ "a_FREQUENCY_PIN_CLKI" : int(self.params.i_freq / 1e6),
+ "p_CLKI_DIV" : self.params.i_div,
+ "i_CLKI" : ClockSignal(self.params.i_domain),
+
+ "a_FREQUENCY_PIN_CLKOP" : int(self.params.op.freq / 1e6),
+ "p_CLKOP_ENABLE" : "ENABLED",
+ "p_CLKOP_DIV" : self.params.op.div,
+ "p_CLKOP_CPHASE" : self.params.op.cphase,
+ "p_CLKOP_FPHASE" : self.params.op.fphase,
+ "o_CLKOP" : ClockSignal(self.params.op.domain),
+ }
+
+ # Secondary outputs
+
+ if self.params.os is not None:
+ pll_kwargs.update({
+ "a_FREQUENCY_PIN_CLKOS" : int(self.params.os.freq / 1e6),
+ "p_CLKOS_ENABLE" : "ENABLED",
+ "p_CLKOS_DIV" : self.params.os.div,
+ "p_CLKOS_CPHASE" : self.params.os.cphase,
+ "p_CLKOS_FPHASE" : self.params.os.fphase,
+ "o_CLKOS" : ClockSignal(self.params.os.domain),
+ })
+ else:
+ pll_kwargs.update({
+ "p_CLKOS_ENABLE" : "DISABLED",
+ })
+
+ if self.params.os2 is not None:
+ pll_kwargs.update({
+ "a_FREQUENCY_PIN_CLKOS2" : int(self.params.os2.freq / 1e6),
+ "p_CLKOS2_ENABLE" : "ENABLED",
+ "p_CLKOS2_DIV" : self.params.os2.div,
+ "p_CLKOS2_CPHASE" : self.params.os2.cphase,
+ "p_CLKOS2_FPHASE" : self.params.os2.fphase,
+ "o_CLKOS2" : ClockSignal(self.params.os2.domain),
+ })
+ else:
+ pll_kwargs.update({
+ "p_CLKOS2_ENABLE" : "DISABLED",
+ })
+
+ if self.params.os3 is not None:
+ pll_kwargs.update({
+ "a_FREQUENCY_PIN_CLKOS3" : int(self.params.os3.freq / 1e6),
+ "p_CLKOS3_ENABLE" : "ENABLED",
+ "p_CLKOS3_DIV" : self.params.os3.div,
+ "p_CLKOS3_CPHASE" : self.params.os3.cphase,
+ "p_CLKOS3_FPHASE" : self.params.os3.fphase,
+ "o_CLKOS3" : ClockSignal(self.params.os3.domain),
+ })
+ else:
+ pll_kwargs.update({
+ "p_CLKOS3_ENABLE" : "DISABLED",
+ })
+
+ # Reset
+
+ if not self.params.i_reset_less:
+ pll_kwargs.update({
+ "p_PLLRST_ENA" : "ENABLED",
+ "i_RST" : ResetSignal(self.params.i_domain),
+ })
+ else:
+ pll_kwargs.update({
+ "p_PLLRST_ENA" : "DISABLED",
+ "i_RST" : Const(0),
+ })
+
+ # Feedback
+
+ pll_kwargs.update({
+ "p_CLKFB_DIV" : int(self.params.fb_div),
+ })
+
+ if self.params.fb_internal:
+ clkintfb = Signal()
+ pll_kwargs.update({
+ "p_FEEDBK_PATH" : "INT_OP",
+ "i_CLKFB" : clkintfb,
+ "o_CLKINTFB" : clkintfb,
+ })
+ else:
+ pll_kwargs.update({
+ "p_FEEDBK_PATH" : "CLKOP",
+ "i_CLKFB" : ClockSignal(self.params.op.domain),
+ "o_CLKINTFB" : Signal(),
+ })
+
+ return Instance("EHXPLLL", **pll_kwargs)
--- /dev/null
+from collections import namedtuple, OrderedDict
+
+from nmigen import *
+
+
+__all__ = ["PLL_Xilinx7Series"]
+
+
+class PLL_Xilinx7Series(Elaboratable):
+ class Parameters:
+ class Output(namedtuple("Output", ["domain", "freq", "div", "phase"])):
+ """PLL output parameters."""
+ __slots__ = ()
+
+ """PLL parameters for Xilinx 7 Series FPGAs.
+
+ Parameters
+ ----------
+ i_domain : str
+ Input clock domain.
+ i_freq : int or float
+ Input clock frequency, in Hz.
+ i_reset_less : bool
+ If `True`, the input clock domain does not use a reset signal. Defaults to `True`.
+ o_domain : str
+ Primary output clock domain.
+ o_freq : int or float
+ Primary output clock frequency, in Hz.
+
+ Attributes
+ ----------
+ i_domain : str
+ Input clock domain.
+ i_freq : int
+ Input clock frequency, in Hz.
+ i_reset_less : bool
+ If `True`, the input clock domain does not use a reset signal.
+ o_domain : str
+ Primary output clock domain.
+ o_freq : int
+ Primary output clock frequency, in Hz.
+ divclk_div : int
+ Input clock divisor.
+ clkfbout_mult : int
+ Feedback clock multiplier.
+ clkout0 : :class:`PLL_Xilinx7Series.Parameters.Output`
+ Primary output parameters.
+ clkout{1..5} : :class:`PLL_Xilinx7Series.Parameters.Output` or None
+ Secondary output parameters, or `None` if absent.
+ """
+ def __init__(self, *, i_domain, i_freq, o_domain, o_freq, i_reset_less=True):
+ if not isinstance(i_domain, str):
+ raise TypeError("Input domain must be a string, not {!r}"
+ .format(i_domain))
+ if not isinstance(i_freq, (int, float)):
+ raise TypeError("Input frequency must be an integer or a float, not {!r}"
+ .format(i_freq))
+ if not 19e6 <= i_freq <= 800e6:
+ raise ValueError("Input frequency must be between 19 and 800 MHz, not {} MHz"
+ .format(i_freq / 1e6))
+ if not isinstance(o_domain, str):
+ raise TypeError("Output domain must be a string, not {!r}"
+ .format(o_domain))
+ if not isinstance(o_freq, (int, float)):
+ raise TypeError("Output frequency must be an integer or a float, not {!r}"
+ .format(o_freq))
+ if not 6.25e6 <= o_freq <= 800e6:
+ raise ValueError("Output frequency must be between 6.25 and 800 MHz, not {} MHz"
+ .format(o_freq / 1e6))
+
+ self.i_domain = i_domain
+ self.i_freq = int(i_freq)
+ self.i_reset_less = bool(i_reset_less)
+ self.o_domain = o_domain
+ self.o_freq = int(o_freq)
+
+ self._divclk_div = None
+ self._clkfbout_mult = None
+ self._clkout0 = None
+ self._clkout1 = None
+ self._clkout2 = None
+ self._clkout3 = None
+ self._clkout4 = None
+ self._clkout5 = None
+
+ self._2nd_outputs = OrderedDict()
+ self._frozen = False
+
+ @property
+ def divclk_div(self):
+ self.compute()
+ return self._divclk_div
+
+ @property
+ def clkfbout_mult(self):
+ self.compute()
+ return self._clkfbout_mult
+
+ @property
+ def clkout0(self):
+ self.compute()
+ return self._clkout0
+
+ @property
+ def clkout1(self):
+ self.compute()
+ return self._clkout1
+
+ @property
+ def clkout2(self):
+ self.compute()
+ return self._clkout2
+
+ @property
+ def clkout3(self):
+ self.compute()
+ return self._clkout3
+
+ @property
+ def clkout4(self):
+ self.compute()
+ return self._clkout4
+
+ @property
+ def clkout5(self):
+ self.compute()
+ return self._clkout5
+
+ def add_secondary_output(self, *, domain, freq, phase=0.0):
+ """Add secondary PLL output.
+
+ Arguments
+ ---------
+ domain : str
+ Output clock domain.
+ freq : int
+ Output clock frequency.
+ phase : int or float
+ Output clock phase, in degrees. Optional. Defaults to 0.
+ """
+ if self._frozen:
+ raise ValueError("PLL parameters have already been computed. Other outputs cannot "
+ "be added")
+ if not isinstance(domain, str):
+ raise TypeError("Output domain must be a string, not {!r}"
+ .format(domain))
+ if not isinstance(freq, (int, float)):
+ raise TypeError("Output frequency must be an integer or a float, not {!r}"
+ .format(freq))
+ if not 6.25e6 <= freq <= 800e6:
+ raise ValueError("Output frequency must be between 6.25 and 800 MHz, not {} MHz"
+ .format(freq / 1e6))
+ if not isinstance(phase, (int, float)):
+ raise TypeError("Output phase must be an integer or a float, not {!r}"
+ .format(phase))
+ if not 0 <= phase <= 360.0:
+ raise ValueError("Output phase must be between 0 and 360 degrees, not {}"
+ .format(phase))
+ if len(self._2nd_outputs) == 5:
+ raise ValueError("This PLL can drive at most 5 secondary outputs")
+ if domain in self._2nd_outputs:
+ raise ValueError("Output domain '{}' has already been added".format(domain))
+
+ self._2nd_outputs[domain] = freq, phase
+
+ def _iter_variants(self):
+ # FIXME: PFD freq ?
+ for divclk_div in range(1, 56 + 1):
+ for clkfbout_mult in reversed(range(2, 64 + 1)):
+ vco_freq = self.i_freq * clkfbout_mult / divclk_div
+ # This VCO range assumes a -1 speedgrade.
+ if not 800e6 <= vco_freq <= 1600e6:
+ continue
+ for clkout0_div in range(1, 128 + 1):
+ clkout0_freq = vco_freq / clkout0_div
+ if not 6.25e6 <= clkout0_freq <= 800e6:
+ continue
+ yield (divclk_div, clkfbout_mult, clkout0_freq, clkout0_div)
+
+ def compute(self):
+ """Compute PLL parameters.
+
+ This method is idempotent. As a side-effect of its first call, the visible state of the
+ :class:`PLL_Xilinx7Series.Parameters` instance becomes immutable (e.g. adding more PLL outputs
+ will fail).
+ """
+ if self._frozen:
+ return
+
+ variants = list(self._iter_variants())
+ if not variants:
+ raise ValueError("Input ({} MHz) to primary output ({} MHz) constraint was not "
+ "satisfied"
+ .format(self.i_freq / 1e6, self.o_freq / 1e6))
+
+ def error(variant):
+ divclk_div, clkfbout_mult, clkout0_freq, clkout0_div = variant
+ vco_freq = self.i_freq * clkfbout_mult / divclk_div
+ # Idem, assuming a -1 speedgrade.
+ return abs(clkout0_freq - self.o_freq), abs(vco_freq - (800e6 + 1600e6) / 2)
+
+ divclk_div, clkfbout_mult, clkout0_freq, clkout0_div = min(variants, key=error)
+
+ self._divclk_div = divclk_div
+ self._clkfbout_mult = clkfbout_mult
+
+ vco_freq = self.i_freq * clkfbout_mult / divclk_div
+ self._clkout0 = PLL_Xilinx7Series.Parameters.Output(
+ domain = self.o_domain,
+ freq = clkout0_freq,
+ div = clkout0_div,
+ phase = 0.0,
+ )
+
+ for i, (out_domain, (out_freq, out_phase)) in enumerate(self._2nd_outputs.items()):
+ out_name = "_clkout{}".format(i + 1)
+ out_params = PLL_Xilinx7Series.Parameters.Output(
+ domain = out_domain,
+ freq = out_freq,
+ div = vco_freq / out_freq,
+ phase = (self._clkout0.phase + out_phase) % 360.0,
+ )
+ setattr(self, out_name, out_params)
+
+ self._frozen = True
+
+ """PLL for Xilinx 7 Series FPGAs.
+
+ Parameters
+ ----------
+ params : :class:`PLL_Xilinx7Series.Parameters`
+ PLL parameters.
+
+ Attributes
+ ----------
+ params : :class:`PLL_Xilinx7Series.Parameters`
+ PLL parameters.
+ locked : Signal(), out
+ PLL lock status.
+ """
+ def __init__(self, params):
+ if not isinstance(params, PLL_Xilinx7Series.Parameters):
+ raise TypeError("PLL parameters must be an instance of PLL_Xilinx7Series.Parameters, not {!r}"
+ .format(params))
+
+ params.compute()
+ self.params = params
+ self.locked = Signal()
+
+ def elaborate(self, platform):
+ pll_fb = Signal()
+
+ pll_kwargs = {
+ "p_STARTUP_WAIT" : "FALSE",
+ "i_PWRDWN" : Const(0),
+ "o_LOCKED" : self.locked,
+
+ "p_REF_JITTER1" : 0.01,
+ "p_CLKIN1_PERIOD" : 1e9 / self.params.i_freq,
+ "i_CLKIN1" : ClockSignal(self.params.i_domain),
+
+ "p_DIVCLK_DIVIDE" : self.params.divclk_div,
+ "p_CLKFBOUT_MULT" : self.params.clkfbout_mult,
+ "i_CLKFBIN" : pll_fb,
+ "o_CLKFBOUT" : pll_fb,
+
+ "p_CLKOUT0_DIVIDE" : self.params.clkout0.div,
+ "p_CLKOUT0_PHASE" : self.params.clkout0.phase,
+ "o_CLKOUT0" : ClockSignal(self.params.clkout0.domain),
+ }
+
+ if self.params.i_reset_less:
+ pll_kwargs.update({
+ "i_RST" : Const(0),
+ })
+ else:
+ pll_kwargs.update({
+ "i_RST" : ResetSignal(self.params.i_domain),
+ })
+
+ for i in range(5):
+ clkout_name = "clkout{}".format(i + 1)
+ clkout_params = getattr(self.params, clkout_name)
+ if clkout_params is not None:
+ pll_kwargs.update({
+ f"p_{clkout_name.upper()}_DIVIDE" : clkout_params.div,
+ f"p_{clkout_name.upper()}_PHASE" : clkout_params.phase,
+ f"o_{clkout_name.upper()}" : ClockSignal(clkout_params.domain),
+ })
+
+ return Instance("PLLE2_BASE", **pll_kwargs)
--- /dev/null
+# nmigen: UnusedElaboratable=no
+
+import unittest
+from nmigen import *
+
+from ..cores.pll.lattice_ecp5 import PLL_LatticeECP5
+
+
+class PLL_LatticeECP5__ParametersTestCase(unittest.TestCase):
+ def test_simple(self):
+ params1 = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ self.assertEqual(params1.i_domain, "foo")
+ self.assertEqual(params1.i_freq, 100e6)
+ self.assertEqual(params1.i_reset_less, True)
+ self.assertEqual(params1.o_domain, "bar")
+ self.assertEqual(params1.o_freq, 50e6)
+ self.assertEqual(params1.fb_internal, False)
+
+ params2 = PLL_LatticeECP5.Parameters(
+ i_domain = "baz",
+ i_freq = int(12e6),
+ i_reset_less = False,
+ o_domain = "qux",
+ o_freq = int(48e6),
+ fb_internal = True,
+ )
+ self.assertEqual(params2.i_domain, "baz")
+ self.assertEqual(params2.i_freq, 12e6)
+ self.assertEqual(params2.i_reset_less, False)
+ self.assertEqual(params2.o_domain, "qux")
+ self.assertEqual(params2.o_freq, 48e6)
+ self.assertEqual(params2.fb_internal, True)
+
+ def test_wrong_i_domain(self):
+ with self.assertRaisesRegex(TypeError,
+ r"Input domain must be a string, not 1"):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = 1,
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+
+ def test_wrong_i_freq_type(self):
+ with self.assertRaisesRegex(TypeError,
+ r"Input frequency must be an integer or a float, not 'baz'"):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = "baz",
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+
+ def test_wrong_i_freq_range(self):
+ with self.assertRaisesRegex(ValueError,
+ r"Input frequency must be between 8 and 400 MHz, not 420.0 MHz"):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 420e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+
+ def test_wrong_o_domain(self):
+ with self.assertRaisesRegex(TypeError,
+ r"Output domain must be a string, not 1"):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = 1,
+ o_freq = 50e6,
+ )
+
+ def test_wrong_o_freq_type(self):
+ with self.assertRaisesRegex(TypeError,
+ r"Output frequency must be an integer or a float, not 'baz'"):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 50e6,
+ o_domain = "bar",
+ o_freq = "baz",
+ )
+
+ def test_wrong_o_freq_range(self):
+ with self.assertRaisesRegex(ValueError,
+ r"Output frequency must be between 10 and 400 MHz, not 420.0 MHz"):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 420e6,
+ )
+
+ def test_add_secondary_output_wrong_domain(self):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ with self.assertRaisesRegex(TypeError,
+ r"Output domain must be a string, not 1"):
+ params.add_secondary_output(domain=1, freq=10e6)
+
+ def test_add_secondary_output_wrong_freq_type(self):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ with self.assertRaisesRegex(TypeError,
+ r"Output frequency must be an integer or a float, not 'a'"):
+ params.add_secondary_output(domain="baz", freq="a")
+
+ def test_add_secondary_output_wrong_freq_range(self):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ with self.assertRaisesRegex(ValueError,
+ r"Output frequency must be between 10 and 400 MHz, not 8.0 MHz"):
+ params.add_secondary_output(domain="baz", freq=8e6)
+
+ def test_add_secondary_output_wrong_phase_type(self):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ with self.assertRaisesRegex(TypeError,
+ r"Output phase must be an integer or a float, not 'a'"):
+ params.add_secondary_output(domain="baz", freq=10e6, phase="a")
+
+ def test_add_secondary_output_wrong_phase_range(self):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ with self.assertRaisesRegex(ValueError,
+ r"Output phase must be between 0 and 360 degrees, not -1"):
+ params.add_secondary_output(domain="baz", freq=10e6, phase=-1)
+
+ def test_add_secondary_output_exceeded(self):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ params.add_secondary_output(domain="a", freq=10e6)
+ params.add_secondary_output(domain="b", freq=10e6)
+ params.add_secondary_output(domain="c", freq=10e6)
+ with self.assertRaisesRegex(ValueError,
+ r"This PLL can drive at most 3 secondary outputs"):
+ params.add_secondary_output(domain="d", freq=10e6)
+
+ def test_add_secondary_output_same_domain(self):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ params.add_secondary_output(domain="a", freq=10e6)
+ with self.assertRaisesRegex(ValueError,
+ r"Output domain 'a' has already been added"):
+ params.add_secondary_output(domain="a", freq=10e6)
+
+ def test_compute_primary(self):
+ def result(i_freq, o_freq):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "i",
+ i_freq = i_freq,
+ o_domain = "o",
+ o_freq = o_freq,
+ )
+ params.compute()
+ return (params.i_div, params.fb_div, params.op.div)
+
+ vectors = [
+ # Values are taken from ecppll in prjtrellis.
+ # i_freq, o_freq, i_div, fb_div, op_div
+ ( 12e6, 48e6, 1, 4, 12),
+ ( 12e6, 60e6, 1, 5, 10),
+ ( 20e6, 30e6, 2, 3, 20),
+ ( 45e6, 30e6, 3, 2, 20),
+ ( 100e6, 400e6, 1, 4, 1),
+ ( 200e6, 400e6, 1, 2, 1),
+ ( 50e6, 400e6, 1, 8, 1),
+ ( 70e6, 40e6, 7, 4, 15),
+ ( 12e6, 36e6, 1, 3, 17),
+ ( 12e6, 96e6, 1, 8, 6),
+ ( 90e6, 40e6, 9, 4, 15),
+ ( 90e6, 50e6, 9, 5, 12),
+ ( 43e6, 86e6, 1, 2, 7),
+ ]
+
+ self.assertEqual(
+ [(i_freq, o_freq, *result(i_freq, o_freq)) for i_freq, o_freq, *_ in vectors],
+ vectors
+ )
+
+ # TODO
+ # def test_compute_secondary(self):
+ # pass
+
+ def test_add_secondary_output_frozen(self):
+ params = PLL_LatticeECP5.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ params.compute()
+ with self.assertRaisesRegex(ValueError,
+ r"PLL parameters have already been computed. Other outputs cannot be added"):
+ params.add_secondary_output(domain="a", freq=10e6)
--- /dev/null
+# nmigen: UnusedElaboratable=no
+
+import unittest
+from nmigen import *
+
+from ..cores.pll.xilinx_7series import PLL_Xilinx7Series
+
+
+class PLL_Xilinx7Series__ParametersTestCase(unittest.TestCase):
+ def test_simple(self):
+ params1 = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ self.assertEqual(params1.i_domain, "foo")
+ self.assertEqual(params1.i_freq, 100e6)
+ self.assertEqual(params1.i_reset_less, True)
+ self.assertEqual(params1.o_domain, "bar")
+ self.assertEqual(params1.o_freq, 50e6)
+
+ params2 = PLL_Xilinx7Series.Parameters(
+ i_domain = "baz",
+ i_freq = int(20e6),
+ i_reset_less = False,
+ o_domain = "qux",
+ o_freq = int(40e6),
+ )
+ self.assertEqual(params2.i_domain, "baz")
+ self.assertEqual(params2.i_freq, 20e6)
+ self.assertEqual(params2.i_reset_less, False)
+ self.assertEqual(params2.o_domain, "qux")
+ self.assertEqual(params2.o_freq, 40e6)
+
+ def test_wrong_i_domain(self):
+ with self.assertRaisesRegex(TypeError,
+ r"Input domain must be a string, not 1"):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = 1,
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+
+ def test_wrong_i_freq_type(self):
+ with self.assertRaisesRegex(TypeError,
+ r"Input frequency must be an integer or a float, not 'baz'"):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = "baz",
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+
+ def test_wrong_i_freq_range(self):
+ with self.assertRaisesRegex(ValueError,
+ r"Input frequency must be between 19 and 800 MHz, not 820.0 MHz"):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 820e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+
+ def test_wrong_o_domain(self):
+ with self.assertRaisesRegex(TypeError,
+ r"Output domain must be a string, not 1"):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = 1,
+ o_freq = 50e6,
+ )
+
+ def test_wrong_o_freq_type(self):
+ with self.assertRaisesRegex(TypeError,
+ r"Output frequency must be an integer or a float, not 'baz'"):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 50e6,
+ o_domain = "bar",
+ o_freq = "baz",
+ )
+
+ def test_wrong_o_freq_range(self):
+ with self.assertRaisesRegex(ValueError,
+ r"Output frequency must be between 6.25 and 800 MHz, not 820.0 MHz"):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 820e6,
+ )
+
+ def test_add_secondary_output_wrong_domain(self):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ with self.assertRaisesRegex(TypeError,
+ r"Output domain must be a string, not 1"):
+ params.add_secondary_output(domain=1, freq=10e6)
+
+ def test_add_secondary_output_wrong_freq_type(self):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ with self.assertRaisesRegex(TypeError,
+ r"Output frequency must be an integer or a float, not 'a'"):
+ params.add_secondary_output(domain="baz", freq="a")
+
+ def test_add_secondary_output_wrong_freq_range(self):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ with self.assertRaisesRegex(ValueError,
+ r"Output frequency must be between 6.25 and 800 MHz, not 5.0 MHz"):
+ params.add_secondary_output(domain="baz", freq=5e6)
+
+ def test_add_secondary_output_wrong_phase_type(self):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ with self.assertRaisesRegex(TypeError,
+ r"Output phase must be an integer or a float, not 'a'"):
+ params.add_secondary_output(domain="baz", freq=10e6, phase="a")
+
+ def test_add_secondary_output_wrong_phase_range(self):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ with self.assertRaisesRegex(ValueError,
+ r"Output phase must be between 0 and 360 degrees, not -1"):
+ params.add_secondary_output(domain="baz", freq=10e6, phase=-1)
+
+ def test_add_secondary_output_exceeded(self):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ params.add_secondary_output(domain="a", freq=10e6)
+ params.add_secondary_output(domain="b", freq=10e6)
+ params.add_secondary_output(domain="c", freq=10e6)
+ params.add_secondary_output(domain="d", freq=10e6)
+ params.add_secondary_output(domain="e", freq=10e6)
+ with self.assertRaisesRegex(ValueError,
+ r"This PLL can drive at most 5 secondary outputs"):
+ params.add_secondary_output(domain="f", freq=10e6)
+
+ def test_add_secondary_output_same_domain(self):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ params.add_secondary_output(domain="a", freq=10e6)
+ with self.assertRaisesRegex(ValueError,
+ r"Output domain 'a' has already been added"):
+ params.add_secondary_output(domain="a", freq=10e6)
+
+ # TODO
+ # def test_compute_primary(self):
+ # pass
+
+ # TODO
+ # def test_compute_secondary(self):
+ # pass
+
+ def test_add_secondary_output_frozen(self):
+ params = PLL_Xilinx7Series.Parameters(
+ i_domain = "foo",
+ i_freq = 100e6,
+ o_domain = "bar",
+ o_freq = 50e6,
+ )
+ params.compute()
+ with self.assertRaisesRegex(ValueError,
+ r"PLL parameters have already been computed. Other outputs cannot be added"):
+ params.add_secondary_output(domain="a", freq=10e6)