From 24a920f2d1265b0d4cf50c11227cdcb5bbb30441 Mon Sep 17 00:00:00 2001 From: Franck Jullien Date: Tue, 21 Sep 2021 10:58:54 +0200 Subject: [PATCH] efinix: add preliminary DDR support (WIP) --- litex/build/efinix/__init__.py | 3 +- litex/build/efinix/ddr.py | 97 ++++++++++++++++++++++ litex/build/efinix/efinity.py | 7 +- litex/build/efinix/ifacewriter.py | 114 +++++++++++++++++++++++++- litex/build/efinix/platform.py | 9 +- litex/soc/cores/clock/efinix_trion.py | 20 +++-- 6 files changed, 235 insertions(+), 15 deletions(-) create mode 100644 litex/build/efinix/ddr.py diff --git a/litex/build/efinix/__init__.py b/litex/build/efinix/__init__.py index c0efa1e9e..eda581876 100644 --- a/litex/build/efinix/__init__.py +++ b/litex/build/efinix/__init__.py @@ -1,3 +1,4 @@ from litex.build.efinix.programmer import EfinixProgrammer from litex.build.efinix.dbparser import EfinixDbParser -from litex.build.efinix.ifacewriter import InterfaceWriter \ No newline at end of file +from litex.build.efinix.ifacewriter import InterfaceWriter +from litex.build.efinix.ddr import EfinixDDR \ No newline at end of file diff --git a/litex/build/efinix/ddr.py b/litex/build/efinix/ddr.py new file mode 100644 index 000000000..ffe3dc463 --- /dev/null +++ b/litex/build/efinix/ddr.py @@ -0,0 +1,97 @@ +import os + +from migen import * +from migen.genlib.cdc import * +from migen import ClockDomain + +from litex.build.generic_platform import * +from litex.soc.interconnect import axi + +from litex.build import tools + +class EfinixDDR(Module): + def __init__(self, platform, config): + self.blocks = [] + self.platform = platform + self.config = config + self.nb_ports = 1 + + if config['ports'] != None: + self.nb_ports = self.config['ports'] + + self.clock_domains.cd_axi_ddr = ClockDomain() + + self.port0 = port0 = axi.AXIInterface(data_width=256, address_width=32, id_width=8, clock_domain="axi_ddr") + + if self.nb_ports == 2: + self.port1 = port1 = axi.AXIInterface(data_width=256, address_width=32, id_width=8, clock_domain="axi_ddr") + + axi_clk = platform.add_iface_io('axi_user_clk') + self.cd_axi_ddr.clk.eq(axi_clk), + + for i in range (0, self.nb_ports): + ios = [('axi', i, + Subsignal('wdata', Pins(256)), + Subsignal('wready', Pins(1)), + Subsignal('wid', Pins(8)), + Subsignal('bready', Pins(1)), + Subsignal('rdata', Pins(256)), + Subsignal('aid', Pins(8)), + Subsignal('bvalid', Pins(1)), + Subsignal('rlast', Pins(1)), + Subsignal('bid', Pins(8)), + Subsignal('asize', Pins(3)), + Subsignal('atype', Pins(1)), + Subsignal('aburst', Pins(2)), + Subsignal('wvalid', Pins(1)), + Subsignal('aaddr', Pins(32)), + Subsignal('rid', Pins(8)), + Subsignal('avalid', Pins(1)), + Subsignal('rvalid', Pins(1)), + Subsignal('alock', Pins(2)), + Subsignal('rready', Pins(1)), + Subsignal('rresp', Pins(2)), + Subsignal('wstrb', Pins(32)), + Subsignal('aready', Pins(1)), + Subsignal('alen', Pins(8)), + Subsignal('wlast', Pins(1)), + )] + + io = platform.add_iface_ios(ios) + + port = port0 + if i == 0: + port = port1 + + is_read = port.ar.valid + self.comb += [io.aaddr.eq(Mux(is_read, port.ar.addr, port.aw.addr)), + io.aid.eq(Mux(is_read, port.ar.id, port.aw.id)), + io.alen.eq(Mux(is_read, port.ar.len, port.aw.len)), + io.asize.eq(Mux(is_read, port.ar.size[0:4], port.aw.size[0:4])), #TODO: check + io.aburst.eq(Mux(is_read, port.ar.burst, port.aw.burst)), + io.alock.eq(Mux(is_read, port.ar.lock, port.aw.lock)), + io.avalid.eq(Mux(is_read, port.ar.valid, port.aw.valid)), + + io.atype.eq(~is_read), + port.aw.ready.eq(io.aready), + port.ar.ready.eq(io.aready), + + io.wid.eq(port.w.id), + io.wstrb.eq(port.w.strb), + io.wdata.eq(port.w.data), + io.wlast.eq(port.w.last), + io.wvalid.eq(port.w.valid), + port.w.ready.eq(io.wready), + + port.r.id.eq(io.rid), + port.r.data.eq(io.rdata), + port.r.last.eq(io.rlast), + port.r.resp.eq(io.rresp), + port.r.valid.eq(io.rvalid), + io.rready.eq(port.r.ready), + + port.b.id.eq(io.bid), + port.b.valid.eq(io.bvalid), + io.bready.eq(port.b.ready), + # port.b.resp ?? + ] diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py index 68a3ddddf..e02deffc3 100644 --- a/litex/build/efinix/efinity.py +++ b/litex/build/efinix/efinity.py @@ -188,6 +188,7 @@ def _build_peri(efinity_path, build_name, partnumber, named_sc, named_pc, fragme header = platform.toolchain.ifacewriter.header(build_name, partnumber) gen = platform.toolchain.ifacewriter.generate() + #TODO: move this to ifacewriter gpio = _build_iface_gpio(named_sc, named_pc, fragment, platform, specials_gpios) add = '\n'.join(additional_iface_commands) footer = platform.toolchain.ifacewriter.footer() @@ -274,7 +275,7 @@ class EfinityToolchain(): self.efinity_path = efinity_path self.additional_sdc_commands = [] self.additional_xml_commands = [] - self.ifacewriter = InterfaceWriter("iface.py", efinity_path) + self.ifacewriter = InterfaceWriter(efinity_path) self.specials_gpios = [] self.additional_iface_commands = [] @@ -338,6 +339,10 @@ class EfinityToolchain(): additional_iface_commands = self.additional_iface_commands, specials_gpios = self.specials_gpios) + # DDR doesn't have Python API so we need to configure it + # directly in the peri.xml file + self.ifacewriter.add_ddr_xml(build_name) + # Run if run: subprocess.call([self.efinity_path + '/scripts/efx_run.py', build_name + '.xml', '-f', 'compile']) diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index 51860d908..e10e66083 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -1,12 +1,120 @@ import os +import csv +import re +import datetime + +from xml.dom import expatbuilder +import xml.etree.ElementTree as et + from litex.build import tools +namespaces = { 'efxpt' : 'http://www.efinixinc.com/peri_design_db', + 'xi' : 'http://www.w3.org/2001/XInclude' +} + class InterfaceWriter(): - def __init__(self, filename, efinity_path): - self.file = filename + def __init__(self, efinity_path): self.efinity_path = efinity_path self.blocks = [] + def add_ddr_xml(self, filename): + et.register_namespace('efxpt', "http://www.efinixinc.com/peri_design_db") + tree = et.parse(filename + '.peri.xml') + root = tree.getroot() + ddr_info = root.find('efxpt:ddr_info', namespaces) + + et.SubElement(ddr_info, 'efxpt:ddr', + name = 'ddr_inst1', + ddr_def = 'DDR_0', + cs_preset_id = '173', + cs_mem_type = 'LPDDR3', + cs_ctrl_width = 'x32', + cs_dram_width = 'x32', + cs_dram_density = '8G', + cs_speedbin = '800', + target0_enable = 'true', + target1_enable = 'false', + ctrl_type = 'none') + + axi_suffix = '' # '_1' for second port + type_suffix = '_0' # '_1' for second port + + gen_pin_target0 = et.SubElement(ddr_info, 'efxpt:gen_pin_target0') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wdata{}'.format(axi_suffix), type_name='WDATA{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wready{}'.format(axi_suffix), type_name='WREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wid{}'.format(axi_suffix), type_name='WID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_bready{}'.format(axi_suffix), type_name='BREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rdata{}'.format(axi_suffix), type_name='RDATA{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aid{}'.format(axi_suffix), type_name='AID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_bvalid{}'.format(axi_suffix), type_name='BVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rlast{}'.format(axi_suffix), type_name='RLAST{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_bid{}'.format(axi_suffix), type_name='BID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_asize{}'.format(axi_suffix), type_name='ASIZE{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_atype{}'.format(axi_suffix), type_name='ATYPE{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aburst{}'.format(axi_suffix), type_name='ABURST{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wvalid{}'.format(axi_suffix), type_name='WVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wlast{}'.format(axi_suffix), type_name='WLAST{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aaddr{}'.format(axi_suffix), type_name='AADDR{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rid{}'.format(axi_suffix), type_name='RID{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_avalid{}'.format(axi_suffix), type_name='AVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rvalid{}'.format(axi_suffix), type_name='RVALID{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_alock{}'.format(axi_suffix), type_name='ALOCK{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rready{}'.format(axi_suffix), type_name='RREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rresp{}'.format(axi_suffix), type_name='RRESP{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wstrb{}'.format(axi_suffix), type_name='WSTRB{}'.format(type_suffix), is_bus = 'true') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aready{}'.format(axi_suffix), type_name='AREADY{}'.format(type_suffix), is_bus = 'false') + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_alen{}'.format(axi_suffix), type_name='ALEN{}'.format(type_suffix), is_bus = 'true') + + et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_clk', type_name='ACLK_0', is_bus = 'false', is_clk = 'true', is_clk_invert = 'false') + + gen_pin_config = et.SubElement(ddr_info, 'efxpt:gen_pin_config') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SEQ_RST', is_bus = 'false') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SCL_IN', is_bus = 'false') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SEQ_START', is_bus = 'false') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='RSTN', is_bus = 'false') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SDA_IN', is_bus = 'false') + et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SDA_OEN', is_bus = 'false') + + cs_fpga = et.SubElement(ddr_info, 'efxpt:cs_fpga') + et.SubElement(cs_fpga, 'efxpt:param', name='FPGA_ITERM', value='120', value_type = 'str') + et.SubElement(cs_fpga, 'efxpt:param', name='FPGA_OTERM', value='34', value_type = 'str') + + cs_memory = et.SubElement(ddr_info, 'efxpt:cs_memory') + et.SubElement(cs_memory, 'efxpt:param', name='RTT_NOM', value='RZQ/2', value_type = 'str') + et.SubElement(cs_memory, 'efxpt:param', name='MEM_OTERM', value='40', value_type = 'str') + et.SubElement(cs_memory, 'efxpt:param', name='CL', value='RL=6/WL=3', value_type = 'str') + + timing = et.SubElement(ddr_info, 'efxpt:cs_memory_timing') + et.SubElement(timing, 'efxpt:param', name='tRAS', value= '42.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRC', value= '60.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRP', value= '18.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRCD', value= '18.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tREFI', value= '3.900', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRFC', value= '210.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRTP', value= '10.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tWTR', value= '10.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tRRD', value= '10.000', value_type='float') + et.SubElement(timing, 'efxpt:param', name='tFAW', value= '50.000', value_type='float') + + cs_control = et.SubElement(ddr_info, 'efxpt:cs_control') + et.SubElement(cs_control, 'efxpt:param', name='AMAP', value= 'ROW-COL_HIGH-BANK-COL_LOW', value_type='str') + et.SubElement(cs_control, 'efxpt:param', name='EN_AUTO_PWR_DN', value= 'Off', value_type='str') + et.SubElement(cs_control, 'efxpt:param', name='EN_AUTO_SELF_REF', value= 'No', value_type='str') + + cs_gate_delay = et.SubElement(ddr_info, 'efxpt:cs_gate_delay') + et.SubElement(cs_control, 'efxpt:param', name='EN_DLY_OVR', value= 'No', value_type='str') + et.SubElement(cs_control, 'efxpt:param', name='GATE_C_DLY', value= '3', value_type='str') + et.SubElement(cs_control, 'efxpt:param', name='GATE_F_DLY', value= '0', value_type='str') + + xml_string = et.tostring(root, 'utf-8') + reparsed = expatbuilder.parseString(xml_string, False) + print_string = reparsed.toprettyxml(indent=" ") + + # Remove lines with only whitespaces. Not sure why they are here + print_string = os.linesep.join([s for s in print_string.splitlines() if s.strip()]) + + tools.write_to_file("{}.peri.xml".format(filename), print_string) + def header(self, build_name, partnumber): header = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() header += """ @@ -93,7 +201,7 @@ design.create('{2}', '{3}', './../build', overwrite=True) def footer(self): return """ # Check design, generate constraints and reports -design.generate(enable_bitstream=True) +#design.generate(enable_bitstream=True) # Save the configured periphery design design.save()""" diff --git a/litex/build/efinix/platform.py b/litex/build/efinix/platform.py index efd8432a0..aadc324bd 100644 --- a/litex/build/efinix/platform.py +++ b/litex/build/efinix/platform.py @@ -73,4 +73,11 @@ class EfinixPlatform(GenericPlatform): 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) - return tmp \ No newline at end of file + return tmp + + def add_iface_ios(self, io): + self.add_extension(io) + tmp = self.request(io[0][0]) + for s in tmp.flatten(): + self.toolchain.specials_gpios.append(s) + return tmp diff --git a/litex/soc/cores/clock/efinix_trion.py b/litex/soc/cores/clock/efinix_trion.py index ec3a948fe..1b5c099ab 100644 --- a/litex/soc/cores/clock/efinix_trion.py +++ b/litex/soc/cores/clock/efinix_trion.py @@ -79,21 +79,23 @@ class TRIONPLL(Module): self.logger.info("Using {}".format(pll_res)) self.logger.info("Clock source: {}, using EXT_CLK{}".format(block['input_clock'], clock_no)) - def create_clkout(self, cd, freq, phase=0, margin=1e-2, with_reset=True): + def create_clkout(self, cd, freq, phase=0, margin=1e-2, with_reset=False, user_clk=True): assert self.nclkouts < self.nclkouts_max clk_out_name = '{}_CLKOUT{}'.format(self.pll_name, self.nclkouts) - self.platform.add_extension([(clk_out_name, 0, Pins(1))]) - tmp = self.platform.request(clk_out_name) + if user_clk == True: + self.platform.add_extension([(clk_out_name, 0, Pins(1))]) + tmp = self.platform.request(clk_out_name) - if with_reset: - self.specials += AsyncResetSynchronizer(cd, ~self.locked) + if with_reset: + self.specials += AsyncResetSynchronizer(cd, ~self.locked) + + # We don't want this IO to be in the interface configuration file as a simple GPIO + self.platform.toolchain.specials_gpios.append(tmp) + self.comb += cd.clk.eq(tmp) + create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts) - # We don't want this IO to be in the interface configuration file as a simple GPIO - self.platform.toolchain.specials_gpios.append(tmp) - self.comb += cd.clk.eq(tmp) - create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts) self.nclkouts += 1 block = self.platform.toolchain.ifacewriter.get_block(self.pll_name)