diff --git a/litex/build/efinix/common.py b/litex/build/efinix/common.py index 0c9d014b74..eeee469672 100644 --- a/litex/build/efinix/common.py +++ b/litex/build/efinix/common.py @@ -422,6 +422,130 @@ class EfinixDDRInput: def lower(dr): return EfinixDDRInputImpl(dr.platform, dr.i, dr.o1, dr.o2, dr.clk) +# Efinix QDROutput --------------------------------------------------------------------------------- + +class EfinixQDROutputImpl(Module): + def __init__(self, platform, i1, i2, i3, i4, o, clk, fastclk): + assert fastclk is not None + io_name = platform.get_pin_name(o) + io_pad = platform.get_pin_location(o) + io_prop = platform.get_pin_properties(o) + io_prop_dict = dict(io_prop) + io_data = platform.add_iface_io(io_name, 4) + self.comb += io_data[0].eq(i1) + self.comb += io_data[1].eq(i2) + self.comb += io_data[2].eq(i3) + self.comb += io_data[3].eq(i4) + block = { + "type" : "GPIO", + "mode" : "OUTPUT", + "name" : io_name, + "location" : io_pad, + "properties" : io_prop, + "size" : 1, + "out_reg" : "SERIAL", + "out_clk_pin" : clk.name_override, # FIXME. + "outfastclk_pin" : fastclk.name_override, # FIXME. + "is_inclk_inverted" : False, + "drive_strength" : io_prop_dict.get("DRIVE_STRENGTH", "4") + } + platform.toolchain.ifacewriter.blocks.append(block) + platform.toolchain.excluded_ios.append(platform.get_pin(o)) + +class EfinixQDROutput: + @staticmethod + def lower(dr): + if dr.platform.family == "Trion": + raise NotImplementedError("Attempted to use a QDR output, but platform does not support them") + return EfinixQDROutputImpl(dr.platform, dr.i1, dr.i2, dr.i3, dr.i4, dr.o, dr.clk, dr.fastclk) + +# Efinix QDRInput ---------------------------------------------------------------------------------- + +class EfinixQDRInputImpl(Module): + def __init__(self, platform, i, o1, o2, o3, o4, clk, fastclk): + assert fastclk is not None + io_name = platform.get_pin_name(i) + io_pad = platform.get_pin_location(i) + io_prop = platform.get_pin_properties(i) + io_data = platform.add_iface_io(io_name, 4) + self.comb += o1.eq(io_data[0]) + self.comb += o2.eq(io_data[1]) + self.comb += o3.eq(io_data[2]) + self.comb += o4.eq(io_data[3]) + block = { + "type" : "GPIO", + "mode" : "INPUT", + "name" : io_name, + "location" : io_pad, + "properties" : io_prop, + "size" : 1, + "in_reg" : "SERIAL", + "in_clk_pin" : clk.name_override, # FIXME. + "infastclk_pin" : fastclk.name_override, # FIXME. + "is_inclk_inverted" : False, + } + platform.toolchain.ifacewriter.blocks.append(block) + platform.toolchain.excluded_ios.append(platform.get_pin(i)) + +class EfinixQDRInput: + @staticmethod + def lower(dr): + if dr.platform.family == "Trion": + raise NotImplementedError("Attempted to use a QDR input, but platform does not support them") + return EfinixQDRInputImpl(dr.platform, dr.i, dr.o1, dr.o2, dr.o3, dr.o4, dr.clk, dr.fastclk) + +# Efinix QDRTristate --------------------------------------------------------------------------------- + +class EfinixQDRTristateImpl(Module): + def __init__(self, platform, io, o1, o2, o3, o4, oe, i1, i2, i3, i4, clk, fastclk_in, fastclk_out): + assert fastclk_in is not None + assert fastclk_out is not None + io_name = platform.get_pin_name(io) + io_pad = platform.get_pin_location(io) + io_prop = platform.get_pin_properties(io) + io_prop_dict = dict(io_prop) + io_data_i = platform.add_iface_io(io_name + "_OUT", 4) + io_data_o = platform.add_iface_io(io_name + "_IN", 4) + io_data_e = platform.add_iface_io(io_name + "_OE") + self.comb += io_data_i[0].eq(o1) + self.comb += io_data_i[1].eq(o2) + self.comb += io_data_i[2].eq(o3) + self.comb += io_data_i[3].eq(o4) + self.comb += io_data_e.eq(oe) + self.comb += i1.eq(io_data_o[0]) + self.comb += i2.eq(io_data_o[1]) + self.comb += i3.eq(io_data_o[2]) + self.comb += i4.eq(io_data_o[3]) + block = { + "type" : "GPIO", + "mode" : "INOUT", + "name" : io_name, + "location" : io_pad, + "properties" : io_prop, + "size" : 1, + "in_reg" : "SERIAL", + "in_clk_pin" : clk.name_override, # FIXME. + "infastclk_pin" : fastclk_in.name_override, # FIXME. + "out_reg" : "SERIAL", + "out_clk_pin" : clk.name_override, # FIXME. + "outfastclk_pin" : fastclk_out.name_override, # FIXME. + "oe_reg" : "REG", + "is_inclk_inverted" : False, + "drive_strength" : io_prop_dict.get("DRIVE_STRENGTH", "4") + } + platform.toolchain.ifacewriter.blocks.append(block) + platform.toolchain.excluded_ios.append(platform.get_pin(io)) + +class EfinixQDRTristate: + @staticmethod + def lower(dr): + assert dr.oe2 is None + assert dr.oe3 is None + assert dr.oe4 is None + if dr.platform.family == "Trion": + raise NotImplementedError("Attempted to use a QDR tristate, but platform does not support them") + return EfinixQDRTristateImpl(dr.platform, dr.io, dr.o1, dr.o2, dr.o3, dr.o4, dr.oe1, dr.i1, dr.i2, dr.i3, dr.i4, dr.clk, dr.fastclk_in, dr.fastclk_out) + # Efinix Special Overrides ------------------------------------------------------------------------- efinix_special_overrides = { @@ -436,4 +560,7 @@ def lower(dr): DDROutput : EfinixDDROutput, DDRInput : EfinixDDRInput, DDRTristate : EfinixDDRTristate, + QDRInput : EfinixQDRInput, + QDROutput : EfinixQDROutput, + QDRTristate : EfinixQDRTristate, } diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index f9e9cae781..6d7559c968 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -174,6 +174,9 @@ def generate_gpio(self, block, verbose=True): if "out_delay" in block: cmd += f'design.set_property("{name}","OUTDELAY","{block["out_delay"]}")\n' + if "outfastclk_pin" in block: + cmd += 'design.set_property("{}","OUTFASTCLK_PIN","{}")\n'.format(name, block["outfastclk_pin"]) + if "out_clk_inv" in block: cmd += f'design.set_property("{name}","IS_OUTCLK_INVERTED","{block["out_clk_inv"]}")\n' cmd += f'design.set_property("{name}","OE_CLK_PIN_INV","{block["out_clk_inv"]}")\n' @@ -184,6 +187,9 @@ def generate_gpio(self, block, verbose=True): if "in_delay" in block: cmd += f'design.set_property("{name}","INDELAY","{block["in_delay"]}")\n' + if "infastclk_pin" in block: + cmd += 'design.set_property("{}","INFASTCLK_PIN","{}")\n'.format(name, block["infastclk_pin"]) + if "in_clk_inv" in block: cmd += f'design.set_property("{name}","IS_INCLK_INVERTED","{block["in_clk_inv"]}")\n' @@ -216,6 +222,8 @@ def generate_gpio(self, block, verbose=True): cmd += f'design.set_property("{name}","IN_CLK_PIN","{block["in_clk_pin"]}")\n' if "in_delay" in block: cmd += f'design.set_property("{name}","INDELAY","{block["in_delay"]}")\n' + if "infastclk_pin" in block: + cmd += 'design.set_property("{}","INFASTCLK_PIN","{}")\n'.format(name, block["infastclk_pin"]) if prop: for p, val in prop: cmd += 'design.set_property("{}","{}","{}")\n'.format(name, p, val) @@ -237,6 +245,9 @@ def generate_gpio(self, block, verbose=True): if "out_delay" in block: cmd += 'design.set_property("{}","OUTDELAY","{}")\n'.format(name, block["out_delay"]) + if "outfastclk_pin" in block: + cmd += 'design.set_property("{}","OUTFASTCLK_PIN","{}")\n'.format(name, block["outfastclk_pin"]) + if "out_clk_inv" in block: cmd += f'design.set_property("{name}","IS_OUTCLK_INVERTED","{block["out_clk_inv"]}")\n' cmd += f'design.set_property("{name}","OE_CLK_PIN_INV","{block["out_clk_inv"]}")\n' diff --git a/litex/build/io.py b/litex/build/io.py index 8a54454500..489b57cb74 100644 --- a/litex/build/io.py +++ b/litex/build/io.py @@ -214,6 +214,117 @@ def iter_expressions(self): def lower(dr): return InferedDDRTristate(dr.io, dr.o1, dr.o2, dr.oe1, dr.oe2, dr.i1, dr.i2, dr.clk) +# QDR Input/Output --------------------------------------------------------------------------------- + +class QDRInput(Special): + def __init__(self, i, o1, o2, o3, o4, clk=ClockSignal(), fastclk=None): + Special.__init__(self) + self.i = wrap(i) + self.o1 = wrap(o1) + self.o2 = wrap(o2) + self.o3 = wrap(o3) + self.o4 = wrap(o4) + self.clk = clk if isinstance(clk, str) else wrap(clk) + self.fastclk = fastclk if isinstance(fastclk, str) else wrap(fastclk) + + def iter_expressions(self): + yield self, "i" , SPECIAL_INPUT + yield self, "o1" , SPECIAL_OUTPUT + yield self, "o2" , SPECIAL_OUTPUT + yield self, "o3" , SPECIAL_OUTPUT + yield self, "o4" , SPECIAL_OUTPUT + yield self, "clk", SPECIAL_INPUT + yield self, "fastclk", SPECIAL_INPUT + + @staticmethod + def lower(dr): + raise NotImplementedError("Attempted to use a QDR input, but platform does not support them") + + +class QDROutput(Special): + def __init__(self, i1, i2, i3, i4, o, clk=ClockSignal(), fastclk=None): + Special.__init__(self) + self.i1 = wrap(i1) + self.i2 = wrap(i2) + self.i3 = wrap(i3) + self.i4 = wrap(i4) + self.o = wrap(o) + self.clk = clk if isinstance(clk, str) else wrap(clk) + self.fastclk = fastclk if isinstance(fastclk, str) else wrap(fastclk) + + def iter_expressions(self): + yield self, "i1" , SPECIAL_INPUT + yield self, "i2" , SPECIAL_INPUT + yield self, "i3" , SPECIAL_INPUT + yield self, "i4" , SPECIAL_INPUT + yield self, "o" , SPECIAL_OUTPUT + yield self, "clk", SPECIAL_INPUT + yield self, "fastclk", SPECIAL_INPUT + + @staticmethod + def lower(dr): + raise NotImplementedError("Attempted to use a QDR output, but platform does not support them") + +# QDR Tristate ------------------------------------------------------------------------------------- + +class InferedQDRTristate(Module): + def __init__(self, io, o1, o2, o3, o4, oe1, oe2, oe3, oe4, i1, i2, i3, i4, clk, fastclk_in, fastclk_out): + _o = Signal() + _oe = Signal() + _i = Signal() + self.specials += QDROutput(o1, o2, o3, o4, _o, clk, fastclk_out) + self.specials += QDROutput(oe1, oe2, oe3, oe4, _oe, clk, fastclk_out) if (oe2 is not None) or (oe3 is not None) or (oe3 is not None) else SDROutput(oe1, _oe, clk) + self.specials += QDRInput(_i, i1, i2, i3, i4, clk, fastclk_in) + self.specials += Tristate(io, _o, _oe, _i) + +class QDRTristate(Special): + def __init__(self, io, o1, o2, o3, o4, + oe1, oe2=None, oe3=None, oe4=None, + i1=Signal(), i2=Signal(), i3=Signal(), i4=Signal(), + clk=ClockSignal(), fastclk=None, fastclk_in=None, fastclk_out=None): + Special.__init__(self) + self.io = io + self.o1 = o1 + self.o2 = o2 + self.o3 = o3 + self.o4 = o4 + self.oe1 = oe1 + self.oe2 = oe2 + self.oe3 = oe3 + self.oe4 = oe4 + self.i1 = i1 + self.i2 = i2 + self.i3 = i3 + self.i4 = i4 + self.clk = clk + self.fastclk_in = fastclk_in if fastclk_in is not None else fastclk + self.fastclk_out = fastclk_out if fastclk_out is not None else fastclk + + def iter_expressions(self): + yield self, "io" , SPECIAL_INOUT + yield self, "o1" , SPECIAL_INPUT + yield self, "o2" , SPECIAL_INPUT + yield self, "o3" , SPECIAL_INPUT + yield self, "o4" , SPECIAL_INPUT + yield self, "oe1", SPECIAL_INPUT + yield self, "oe2", SPECIAL_INPUT + yield self, "oe3", SPECIAL_INPUT + yield self, "oe4", SPECIAL_INPUT + yield self, "i1" , SPECIAL_OUTPUT + yield self, "i2" , SPECIAL_OUTPUT + yield self, "i3" , SPECIAL_OUTPUT + yield self, "i4" , SPECIAL_OUTPUT + yield self, "clk", SPECIAL_INPUT + yield self, "fastclk_in", SPECIAL_INPUT + yield self, "fastclk_out", SPECIAL_INPUT + + @staticmethod + def lower(dr): + return InferedDDRTristate(dr.io, dr.o1, dr.o2, dr.o3, dr.o4, + dr.oe1, dr.oe2, dr.oe3, dr.oe4, + dr.i1, dr.i2, dr.i3, dr.i4, + dr.clk, dr.fastclk_in, dr.fastclk_out) + # Clock Reset Generator ---------------------------------------------------------------------------- class CRG(Module):