diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index 9d709e25e..828c56a26 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -180,15 +180,70 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) return b return None + def generate_gpio(self, block, verbose=True): + name = block['name'] + mode = block['mode'] + cmd = '' + + +# TODO: {'type': 'GPIO', 'mode': 'OUTPUT', 'location': 'U16', 'size': 4, 'in_reg': 'DDIO_RESYNC', 'out_clk_pin': '', 'is_inclk_inverted': False, 'name': 'auto_ethtx_tx_data_d1', 'name_d2': 'auto_ethtx_tx_data_d2'} + + if mode == 'INPUT': + if len(block['location']) == 1: + cmd += 'design.create_input_gpio("{}")\n'.format(name) + cmd += 'design.assign_pkg_pin("{}","{}")\n'.format(name, block['location'][0]) + else: + cmd += 'design.create_input_gpio("{}",{},0)\n'.format(name, block['size']-1) + for i, pad in enumerate(block['location']): + cmd += 'design.assign_pkg_pin("{}[{}]","{}")\n'.format(name, i, pad) + if 'in_reg' in block: + cmd += 'design.set_property("{}","IN_REG","{}")\n'.format(name, block['in_reg']) + cmd += 'design.set_property("{}","IN_CLK_PIN","{}")\n\n'.format(name, block['in_clk_pin']) + return cmd + + if mode == 'OUTPUT': + if len(block['location']) == 1: + cmd += 'design.create_output_gpio("{}")\n'.format(name) + cmd += 'design.assign_pkg_pin("{}","{}")\n'.format(name, block['location'][0]) + else: + cmd += 'design.create_input_gpio("{}",{},0)\n'.format(name, block['size']-1) + for i, pad in enumerate(block['location']): + cmd += 'design.assign_pkg_pin("{}[{}]","{}")\n'.format(name, i, pad) + if 'out_reg' in block: + cmd += 'design.set_property("{}","OUT_REG","{}")\n'.format(name, block['out_reg']) + cmd += 'design.set_property("{}","OUT_CLK_PIN","{}")\n\n'.format(name, block['out_clk_pin']) + return cmd + + if mode == 'INPUT_CLK': + cmd += 'design.create_input_clock_gpio("{}")\n'.format(name) + cmd += 'design.set_property("{}","IN_PIN","{}")\n'.format(name, name) + cmd += 'design.assign_pkg_pin("{}","{}")\n\n'.format(name, block['location']) + return cmd + + if mode == 'OUTPUT_CLK': + cmd += 'design.create_clockout_gpio("{}")\n'.format(name) + cmd += 'design.set_property("{}","OUT_CLK_PIN","{}")\n'.format(name, name) + cmd += 'design.assign_pkg_pin("{}","{}")\n\n'.format(name, block['location']) + return cmd + + cmd = '# TODO: ' + str(block) +'\n' + return cmd + def generate_pll(self, block, verbose=True): name = block['name'] cmd = '# ---------- PLL {} ---------\n'.format(name) cmd += 'design.create_block("{}", block_type="PLL")\n'.format(name) - cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_src="{}", refclk_name="{}", ext_refclk_no="{}")\n\n' \ - .format(name, block['resource'], block['input_clock'], block['input_clock_name'], block['clock_no']) - cmd += 'pll_config = {{ "REFCLK_FREQ":"{}" }}\n'.format(block['input_freq'] / 1e6) - cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) + + if block['input_clock'] == 'EXTERNAL': + cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_src="{}", refclk_name="{}", ext_refclk_no="{}")\n\n' \ + .format(name, block['resource'], block['input_clock'], block['input_clock_name'], block['clock_no']) + else: + cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_name="{}", refclk_src="CORE")\n'.format(name, block['resource'], block['input_signal']) + cmd += 'design.set_property("{}", "CORE_CLK_PIN", "{}", block_type="PLL")\n\n'.format(name, block['input_signal']) + + cmd += 'pll_config = {{ "REFCLK_FREQ":"{}" }}\n'.format(block['input_freq'] / 1e6) + cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) cmd += 'design.set_property("{}","LOCKED_PIN","{}", block_type="PLL")\n'.format(name, block['locked']) if block['reset'] != '': @@ -198,10 +253,10 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) for i, clock in enumerate(block['clk_out']): if i > 0: cmd += 'pll_config = {{ "CLKOUT{}_EN":"1", "CLKOUT{}_PIN":"{}" }}\n'.format(i, i, clock[0]) - cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) else: cmd += 'pll_config = {{ "CLKOUT{}_PIN":"{}" }}\n'.format(i, clock[0]) - cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) + + cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name) cmd += 'target_freq = {\n' for i, clock in enumerate(block['clk_out']): @@ -215,8 +270,8 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) cmd += 'print("#### {} ####")\n'.format(name) cmd += 'clksrc_info = design.trace_ref_clock("{}", block_type="PLL")\n'.format(name) cmd += 'pprint.pprint(clksrc_info)\n' - cmd += 'clock_source_prop = ["REFCLK_SOURCE", "EXT_CLK", "CLKOUT0_EN", "CLKOUT1_EN","REFCLK_FREQ", "RESOURCE"]\n' - cmd += 'clock_source_prop += ["M", "N", "O", "CLKOUT0_DIV", "CLKOUT2_DIV", "VCO_FREQ", "PLL_FREQ"]\n' + cmd += 'clock_source_prop = ["REFCLK_SOURCE", "CORE_CLK_PIN", "EXT_CLK", "CLKOUT0_EN", "CLKOUT1_EN","REFCLK_FREQ", "RESOURCE"]\n' + cmd += 'clock_source_prop += ["CLKOUT0_FREQ", "CLKOUT1_FREQ", "CLKOUT2_FREQ"]\n' cmd += 'prop_map = design.get_property("{}", clock_source_prop, block_type="PLL")\n'.format(name) cmd += 'pprint.pprint(prop_map)\n' @@ -228,6 +283,9 @@ design.create('{2}', '{3}', './../gateware', overwrite=True) for b in self.blocks: if b['type'] == 'PLL': output += self.generate_pll(b) + if b['type'] == 'GPIO': + output += self.generate_gpio(b) + return output def footer(self): diff --git a/litex/build/efinix/platform.py b/litex/build/efinix/platform.py index aadc324bd..3b2aa0957 100644 --- a/litex/build/efinix/platform.py +++ b/litex/build/efinix/platform.py @@ -18,6 +18,9 @@ class EfinixPlatform(GenericPlatform): def __init__(self, *args, toolchain="efinity", **kwargs): GenericPlatform.__init__(self, *args, **kwargs) + self.pll_available = ['PLL_TL0', 'PLL_TR0', 'PLL_TR1', 'PLL_TR2', 'PLL_TR3', 'PLL_BR0', 'PLL_BR1', 'PLL_BR2', 'PLL_BL0'] + self.pll_used = [] + if 'LITEX_ENV_EFINITY' in os.environ: self.efinity_path = os.environ['LITEX_ENV_EFINITY'].rstrip('/') os.environ['EFINITY_HOME'] = self.efinity_path @@ -55,29 +58,63 @@ class EfinixPlatform(GenericPlatform): # get_pin_location(p[1]) # not tested with subsignal like get_pin_location(p.clk) def get_pin_location(self, sig): + if sig is None: + return None sc = self.constraint_manager.get_sig_constraints() for s, pins, others, resource in sc: - if s == sig: - return pins[0] + if (s == sig) and (pins[0] != 'X'): + return pins return None def get_pin_name(self, sig): + if sig is None: + return None sc = self.constraint_manager.get_sig_constraints() for s, pins, others, resource in sc: if s == sig: - return resource[0] + if resource[2]: + return resource[0] + '_' + resource[2] + else: + return resource[0] return None - def add_iface_io(self, name, size=1): + def get_sig_constraint(self, sig): + sc = self.constraint_manager.get_sig_constraints() + for s, pins, others, resource in sc: + if s == sig: + return sc + return None + + def add_iface_io(self, name, size=1, append=True): self.add_extension([(name, 0, Pins(size))]) tmp = self.request(name) # We don't want this IO to be in the interface configuration file as a simple GPIO - self.toolchain.specials_gpios.append(tmp) + if append: + self.toolchain.specials_gpios.append(tmp) return tmp - def add_iface_ios(self, io): + def add_iface_ios(self, io, append=True): self.add_extension(io) tmp = self.request(io[0][0]) - for s in tmp.flatten(): - self.toolchain.specials_gpios.append(s) + if append: + for s in tmp.flatten(): + self.toolchain.specials_gpios.append(s) return tmp + + def del_record_signal(self, record, sig): + for pos, (name, item) in enumerate(vars(record).items()): + if isinstance(item, Signal): + if item == sig: + # Two first pos are name and layout + del record.layout[pos-2] + delattr(record, name) + break + + def get_pll_resource(self, name): + self.pll_used.append(name) + self.pll_available.remove(name) + print('Pll used : ' + str(self.pll_used)) + print('Pll pll_available: ' + str(self.pll_available)) + + def get_free_pll_resource(self): + return self.pll_available[0] \ No newline at end of file diff --git a/litex/build/efinix/rgmii.py b/litex/build/efinix/rgmii.py new file mode 100644 index 000000000..ac6db1549 --- /dev/null +++ b/litex/build/efinix/rgmii.py @@ -0,0 +1,194 @@ +# +# This file is part of LiteEth. +# +# Copyright (c) 2015-2020 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +# RGMII PHY for 7-Series Xilinx FPGA + +from migen import * +from migen.genlib.resetsync import AsyncResetSynchronizer + +from litex.build.generic_platform import * +from litex.soc.cores.clock import * + +from liteeth.common import * +from liteeth.phy.common import * + + +class LiteEthPHYRGMIITX(Module): + def __init__(self, platform, pads): + self.sink = sink = stream.Endpoint(eth_phy_description(8)) + + # # # + + name = platform.get_pin_name(pads.tx_data) + pad = platform.get_pin_location(pads.tx_data) + name = 'auto_' + name + tx_data_d1 = [] + tx_data_d2 = [] + # This a workaround, we could use signals with 4 bits but there is + # a problem with the Python API that prevents it + + #tx_data_d1 = platform.add_iface_io(name + '_HI', 4) + #tx_data_d2 = platform.add_iface_io(name + '_LO', 4) + + for i in range(4): + tx_data_d1.append(platform.add_iface_io(name + str(i) + '_HI')) + tx_data_d2.append(platform.add_iface_io(name + str(i) + '_LO')) + + block = {'type':'GPIO', + 'mode':'OUTPUT', + 'name':name + str(i), + #'name':name, + 'location':[pad[i]], + 'size':1, + #'location':pad, + #'size':4, + 'out_reg':'DDIO_RESYNC', + 'out_clk_pin':'auto_eth_tx_clk', # -------------------------- TODO + 'is_inclk_inverted':False + } + platform.toolchain.ifacewriter.blocks.append(block) + + platform.del_record_signal(pads, pads.tx_data) + + #self.comb += pads.tx_ctl.eq(sink.valid) + #self.comb += tx_data_d1.eq(sink.data[0:4]) + #self.comb += tx_data_d2.eq(sink.data[4:8]) + #self.comb += sink.ready.eq(1) + + self.comb += [ pads.tx_ctl.eq(sink.valid), + tx_data_d1[0].eq(sink.data[0]), + tx_data_d1[1].eq(sink.data[1]), + tx_data_d1[2].eq(sink.data[2]), + tx_data_d1[3].eq(sink.data[3]), + tx_data_d2[0].eq(sink.data[4]), + tx_data_d2[1].eq(sink.data[5]), + tx_data_d2[2].eq(sink.data[6]), + tx_data_d2[3].eq(sink.data[7]), + sink.ready.eq(1), + ] + +class LiteEthPHYRGMIIRX(Module): + def __init__(self, platform, pads): + self.source = source = stream.Endpoint(eth_phy_description(8)) + + # # # + + rx_ctl_d = Signal() + rx_data = Signal(8) + + # pads.rx_ctl can't be connected to a special GPIO (DDIO) because + # of this board layout. + + # Add a DDIO_RESYNC input block with 'auto_eth_rx_clk' as clock + name = platform.get_pin_name(pads.rx_data) + pad = platform.get_pin_location(pads.rx_data) + name = 'auto_' + name + rx_data_d1 = [] + rx_data_d2 = [] + # This a workaround, we could use signals with 4 bits but there is + # a problem with the Python API that prevents it + for i in range(4): + rx_data_d1.append(platform.add_iface_io(name + str(i) + '_HI')) + rx_data_d2.append(platform.add_iface_io(name + str(i) + '_LO')) + + block = {'type':'GPIO', + 'mode':'INPUT', + 'name':name + str(i), + 'location':[pad[i]], + 'size':1, + 'in_reg':'DDIO_RESYNC', + 'in_clk_pin':'auto_eth_rx_clk', + 'is_inclk_inverted':False + } + platform.toolchain.ifacewriter.blocks.append(block) + + platform.del_record_signal(pads, pads.rx_data) + + self.comb += rx_data.eq(Cat(rx_data_d1[0], rx_data_d1[1], rx_data_d1[2], rx_data_d1[3], + rx_data_d2[0], rx_data_d2[1], rx_data_d2[2], rx_data_d2[3])) + + self.sync += rx_ctl_d.eq(pads.rx_ctl) + + last = Signal() + self.comb += last.eq(~pads.rx_ctl & rx_ctl_d) + self.sync += [ + source.valid.eq(pads.rx_ctl), + source.data.eq(rx_data) + ] + self.comb += source.last.eq(last) + +class LiteEthPHYRGMIICRG(Module, AutoCSR): + def __init__(self, platform, clock_pads, with_hw_init_reset, tx_delay=2e-9, hw_reset_cycles=256): + self._reset = CSRStorage() + + # # # + + # Clocks + + self.clock_domains.cd_eth_rx = ClockDomain() + self.clock_domains.cd_eth_tx = ClockDomain() + self.clock_domains.cd_eth_tx_delayed = ClockDomain(reset_less=True) + + # Add a GPIO block with clock input property + # Add a input 'auto_eth_rx_clk' to the top.v + clkrx = platform.add_iface_io('auto_eth_rx_clk') + block = {'type':'GPIO', + 'size':1, + # Get the location from the original resource + 'location': platform.get_pin_location(clock_pads.rx)[0], + 'name':platform.get_pin_name(clkrx), + 'mode':'INPUT_CLK' + } + platform.toolchain.ifacewriter.blocks.append(block) + self.comb += self.cd_eth_rx.clk.eq(clkrx) + + clktx = platform.add_iface_io('auto_eth_tx_delayed_clk') + block = {'type':'GPIO', + 'size':1, + # Get the location from the original resource + 'location': platform.get_pin_location(clock_pads.tx)[0], + 'name':platform.get_pin_name(clktx), + 'mode':'OUTPUT_CLK' + } + platform.toolchain.ifacewriter.blocks.append(block) + self.comb += clktx.eq(self.cd_eth_tx.clk) + + self.submodules.pll = pll = TRIONPLL(platform) + # Internal clock must come from a named signal + pll.register_clkin(None, 125e6, name='auto_eth_rx_clk') + pll.create_clkout(None, 125e6, phase=90, name='auto_eth_tx_delayed_clk') + pll.create_clkout(None, 125e6, name='auto_eth_tx_clk') + + platform.delete(clock_pads) + + ## Reset + #self.reset = reset = Signal() + #if with_hw_init_reset: + # self.submodules.hw_reset = LiteEthPHYHWReset(cycles=hw_reset_cycles) + # self.comb += reset.eq(self._reset.storage | self.hw_reset.reset) + #else: + # self.comb += reset.eq(self._reset.storage) + #if hasattr(clock_pads, "rst_n"): + # self.comb += clock_pads.rst_n.eq(~reset) + #self.specials += [ + # AsyncResetSynchronizer(self.cd_eth_tx, reset), + # AsyncResetSynchronizer(self.cd_eth_rx, reset), + #] + + +class LiteEthPHYRGMII(Module, AutoCSR): + dw = 8 + tx_clk_freq = 125e6 + rx_clk_freq = 125e6 + def __init__(self, platform, clock_pads, pads, with_hw_init_reset=True, tx_delay=2e-9, rx_delay=2e-9, + iodelay_clk_freq=200e6, hw_reset_cycles=256): + self.submodules.crg = LiteEthPHYRGMIICRG(platform, clock_pads, with_hw_init_reset, tx_delay, hw_reset_cycles) + self.submodules.tx = ClockDomainsRenamer("eth_tx")(LiteEthPHYRGMIITX(platform, pads)) + self.submodules.rx = ClockDomainsRenamer("eth_rx")(LiteEthPHYRGMIIRX(platform, pads)) + self.sink, self.source = self.tx.sink, self.rx.source + + #if hasattr(pads, "mdc"): + # self.submodules.mdio = LiteEthPHYMDIO(pads) diff --git a/litex/build/generic_platform.py b/litex/build/generic_platform.py index a09156e34..12461ed62 100644 --- a/litex/build/generic_platform.py +++ b/litex/build/generic_platform.py @@ -192,6 +192,12 @@ class ConstraintManager: def delete(self, signal): for res, obj in self.matched: + if isinstance(signal, Record): + if isinstance(obj, Signal): + continue + else: + if isinstance(obj, Record): + continue if obj == signal: self.matched.remove((res, obj)) @@ -280,12 +286,14 @@ class ConstraintManager: if has_subsignals: for element in resource[2:]: if isinstance(element, Subsignal): - sig = getattr(obj, element.name) - pins, others = _separate_pins(top_constraints + - element.constraints) - pins = self.connector_manager.resolve_identifiers(pins) - r.append((sig, pins, others, - (name, number, element.name))) + # Because we could have removed one Signal From the record + if hasattr(obj, element.name): + sig = getattr(obj, element.name) + pins, others = _separate_pins(top_constraints + + element.constraints) + pins = self.connector_manager.resolve_identifiers(pins) + r.append((sig, pins, others, + (name, number, element.name))) else: pins, others = _separate_pins(top_constraints) pins = self.connector_manager.resolve_identifiers(pins) diff --git a/litex/soc/cores/clock/efinix_trion.py b/litex/soc/cores/clock/efinix_trion.py index a179e1857..3811b7d63 100644 --- a/litex/soc/cores/clock/efinix_trion.py +++ b/litex/soc/cores/clock/efinix_trion.py @@ -53,33 +53,44 @@ class TRIONPLL(Module): self.platform.toolchain.ifacewriter.blocks.append(block) - def register_clkin(self, clkin, freq): + def register_clkin(self, clkin, freq, name= ''): block = self.platform.toolchain.ifacewriter.get_block(self.pll_name) - # If clkin has resource, PLL clock input is EXTERNAL - # When PLL clock is external, it must not be present in the top file - # Add a test on clkin resource here block['input_clock_name'] = self.platform.get_pin_name(clkin) - pin_name = self.platform.get_pin_location(clkin) - self.platform.delete(clkin) + # If clkin has a pin number, PLL clock input is EXTERNAL + if self.platform.get_pin_location(clkin): + + pad_name = self.platform.get_pin_location(clkin) + self.platform.delete(clkin) - #tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]" - #sdc = self.platform.toolchain.additional_sdc_commands - #sdc.append(tpl.format(clk=block['input_clock_name'], period=1/freq)) + #tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]" + #sdc = self.platform.toolchain.additional_sdc_commands + #sdc.append(tpl.format(clk=block['input_clock_name'], period=1/freq)) - parser = EfinixDbParser(self.platform.efinity_path, self.platform.device) - (pll_res, clock_no) = parser.get_pll_inst_from_pin(pin_name) + parser = EfinixDbParser(self.platform.efinity_path, self.platform.device) + try: + (pll_res, clock_no) = parser.get_pll_inst_from_pin(pad_name) + except: + self.logger.error("Cannot find a pll with {} as input".format(pad_name)) + quit() + + block['input_clock'] = 'EXTERNAL' + block['resource'] = pll_res + block['clock_no'] = clock_no + self.logger.info("Clock source: {}, using EXT_CLK{}".format(block['input_clock'], clock_no)) + self.platform.get_pll_resource(pll_res) + else: + block['input_clock'] = 'INTERNAL' + block['resource'] = self.platform.get_free_pll_resource() + block['input_signal'] = name + self.logger.info("Clock source: {}".format(block['input_clock'])) - block['input_clock'] = 'EXTERNAL' block['input_freq'] = freq - block['resource'] = pll_res - block['clock_no'] = clock_no - self.logger.info("Using {}".format(pll_res)) - self.logger.info("Clock source: {}, using EXT_CLK{}".format(block['input_clock'], clock_no)) + self.logger.info("Using {}".format(block['resource'])) - def create_clkout(self, cd, freq, phase=0, margin=1e-2, name='', with_reset=False, user_clk=True): + def create_clkout(self, cd, freq, phase=0, margin=1e-2, name='', with_reset=False): assert self.nclkouts < self.nclkouts_max if name != '': @@ -87,7 +98,7 @@ class TRIONPLL(Module): else: clk_out_name = '{}_CLKOUT{}'.format(self.pll_name, self.nclkouts) - if user_clk == True: + if cd != None: self.platform.add_extension([(clk_out_name, 0, Pins(1))]) tmp = self.platform.request(clk_out_name)