From a3b4e42b98971f8765c4e2359a6e962f76fab1b0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 3 Aug 2019 18:36:58 +0000 Subject: [PATCH] build.plat,vendor: automatically create sync domain from default_clk. But only if it is not defined by the programmer. Closes #57. --- nmigen/build/plat.py | 22 +++++++++++++--- nmigen/vendor/lattice_ecp5.py | 4 +++ nmigen/vendor/lattice_ice40.py | 40 +++++++++++++++++++++++++++++ nmigen/vendor/xilinx_7series.py | 4 +++ nmigen/vendor/xilinx_spartan_3_6.py | 36 ++++++++++++++------------ 5 files changed, 87 insertions(+), 19 deletions(-) diff --git a/nmigen/build/plat.py b/nmigen/build/plat.py index f11dce6..dc917e7 100644 --- a/nmigen/build/plat.py +++ b/nmigen/build/plat.py @@ -7,6 +7,7 @@ import jinja2 from .. import __version__ from ..hdl.ast import * +from ..hdl.cd import * from ..hdl.dsl import * from ..hdl.ir import * from ..back import rtlil, verilog @@ -71,11 +72,27 @@ class Platform(ResourceManager, metaclass=ABCMeta): self.toolchain_program(products, name, **(program_opts or {})) + @abstractmethod + def create_missing_domain(self, name): + if name == "sync" and self.default_clk is not None: + clk_i = self.request(self.default_clk).i + if self.default_rst is not None: + rst_i = self.request(self.default_rst).i + + m = Module() + m.domains += ClockDomain("sync", reset_less=self.default_rst is None) + m.d.comb += ClockSignal("sync").eq(clk_i) + if self.default_rst is not None: + m.d.comb += ResetSignal("sync").eq(rst_i) + return m + def prepare(self, fragment, name="top", **kwargs): assert not self._prepared self._prepared = True fragment = Fragment.get(fragment, self) + fragment = fragment.prepare(ports=list(self.iter_ports()), + missing_domain=self.create_missing_domain) def add_pin_fragment(pin, pin_fragment): pin_fragment = Fragment.get(pin_fragment, self) @@ -226,9 +243,8 @@ class TemplatedPlatform(Platform): autogenerated = "Automatically generated by nMigen {}. Do not edit.".format(__version__) def emit_design(backend): - return {"rtlil": rtlil, "verilog": verilog}[backend].convert( - fragment, name=name, platform=self, ports=list(self.iter_ports()), - missing_domain=lambda name: None) + return {"rtlil": rtlil, "verilog": verilog}[backend].convert(fragment, name=name, + ports=list(self.iter_ports()), missing_domain=lambda name: None) def emit_commands(format): commands = [] diff --git a/nmigen/vendor/lattice_ecp5.py b/nmigen/vendor/lattice_ecp5.py index b90e50e..d22a583 100644 --- a/nmigen/vendor/lattice_ecp5.py +++ b/nmigen/vendor/lattice_ecp5.py @@ -127,6 +127,10 @@ class LatticeECP5Platform(TemplatedPlatform): """ ] + def create_missing_domain(self, name): + # No additional reset logic needed. + return super().create_missing_domain(name) + _single_ended_io_types = [ "HSUL12", "LVCMOS12", "LVCMOS15", "LVCMOS18", "LVCMOS25", "LVCMOS33", "LVTTL33", "SSTL135_I", "SSTL135_II", "SSTL15_I", "SSTL15_II", "SSTL18_I", "SSTL18_II", diff --git a/nmigen/vendor/lattice_ice40.py b/nmigen/vendor/lattice_ice40.py index ee6f5ce..74fc36f 100644 --- a/nmigen/vendor/lattice_ice40.py +++ b/nmigen/vendor/lattice_ice40.py @@ -119,6 +119,46 @@ class LatticeICE40Platform(TemplatedPlatform): """ ] + def create_missing_domain(self, name): + # For unknown reasons (no errata was ever published, and no documentation mentions this + # issue), iCE40 BRAMs read as zeroes for ~3 us after configuration and release of internal + # global reset. Note that this is a *time-based* delay, generated purely by the internal + # oscillator, which may not be observed nor influenced directly. For details, see links: + # * https://github.com/cliffordwolf/icestorm/issues/76#issuecomment-289270411 + # * https://github.com/cliffordwolf/icotools/issues/2#issuecomment-299734673 + # + # To handle this, it is necessary to have a global reset in any iCE40 design that may + # potentially instantiate BRAMs, and assert this reset for >3 us after configuration. + # (We add a margin of 5x to allow for PVT variation.) If the board includes a dedicated + # reset line, this line is ORed with the power on reset. + # + # The power-on reset timer counts up because the vendor tools do not support initialization + # of flip-flops. + if name == "sync" and self.default_clk is not None: + clk_i = self.request(self.default_clk).i + if self.default_rst is not None: + rst_i = self.request(self.default_rst).i + + m = Module() + # Power-on-reset domain + m.domains += ClockDomain("ice40_por", reset_less=True) + delay = int(15e-6 * self.default_clk_frequency) + timer = Signal(max=delay) + ready = Signal() + m.d.comb += ClockSignal("ice40_por").eq(clk_i) + with m.If(timer == delay): + m.d.ice40_por += ready.eq(1) + with m.Else(): + m.d.ice40_por += timer.eq(timer + 1) + # Primary domain + m.domains += ClockDomain("sync") + m.d.comb += ClockSignal("sync").eq(clk_i) + if self.default_rst is not None: + m.d.comb += ResetSignal("sync").eq(~ready | rst_i) + else: + m.d.comb += ResetSignal("sync").eq(~ready) + return m + def should_skip_port_component(self, port, attrs, component): # On iCE40, a differential input is placed by only instantiating an SB_IO primitive for # the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs diff --git a/nmigen/vendor/xilinx_7series.py b/nmigen/vendor/xilinx_7series.py index 2fbcec5..34452ef 100644 --- a/nmigen/vendor/xilinx_7series.py +++ b/nmigen/vendor/xilinx_7series.py @@ -122,6 +122,10 @@ class Xilinx7SeriesPlatform(TemplatedPlatform): """ ] + def create_missing_domain(self, name): + # No additional reset logic needed. + csuper().create_missing_domain(name) + def _get_xdr_buffer(self, m, pin, i_invert=None, o_invert=None): def get_dff(clk, d, q): # SDR I/O is performed by packing a flip-flop into the pad IOB. diff --git a/nmigen/vendor/xilinx_spartan_3_6.py b/nmigen/vendor/xilinx_spartan_3_6.py index 754b2a9..aea391d 100644 --- a/nmigen/vendor/xilinx_spartan_3_6.py +++ b/nmigen/vendor/xilinx_spartan_3_6.py @@ -59,6 +59,23 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform): package = abstractproperty() speed = abstractproperty() + @property + def family(self): + device = self.device.upper() + if device.startswith("XC3S"): + if device.endswith("A"): + return "3A" + elif device.endswith("E"): + raise NotImplementedError("""Spartan 3E family is not supported + as a nMigen platform.""") + else: + raise NotImplementedError("""Spartan 3 family is not supported + as a nMigen platform.""") + elif device.startswith("XC6S"): + return "6" + else: + assert False + file_templates = { **TemplatedPlatform.build_script_templates, "{{name}}.v": r""" @@ -142,22 +159,9 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform): """ ] - @property - def family(self): - device = self.device.upper() - if device.startswith("XC3S"): - if device.endswith("A"): - return "3A" - elif device.endswith("E"): - raise NotImplementedError("""Spartan 3E family is not supported - as a nMigen platform.""") - else: - raise NotImplementedError("""Spartan 3 family is not supported - as a nMigen platform.""") - elif device.startswith("XC6S"): - return "6" - else: - assert False + def create_missing_domain(self, name): + # No additional reset logic needed. + return super().create_missing_domain(name) def _get_xdr_buffer(self, m, pin, i_invert=None, o_invert=None): def get_dff(clk, d, q): -- 2.30.2