diff --git a/amaranth/vendor/gowin.py b/amaranth/vendor/gowin.py new file mode 100644 index 000000000..0d1038823 --- /dev/null +++ b/amaranth/vendor/gowin.py @@ -0,0 +1,551 @@ +from abc import abstractproperty + +from ..hdl import * +from ..lib.cdc import ResetSynchronizer +from ..build import * + +# Acknowledgments: +# Parts of this file originate from https://github.com/tcjie/Gowin + + +__all__ = ["GowinPlatform"] + + +class GowinPlatform(TemplatedPlatform): + """ + .. rubric:: Apicula toolchain + + Required tools: + * ``yosys`` + * ``nextpnr-gowin`` + * ``gowin_pack`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_Apicula``, if present. + + Build products: + * ``{{name}}.fs``: binary bitstream. + + .. rubric:: Gowin toolchain + + Required tools: + * ``gw_sh`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_Gowin``, if present. + + Build products: + * ``{{name}}.fs``: binary bitstream. + + """ + + toolchain = None # selected when creating platform + + device = abstractproperty() + series = abstractproperty() + subseries = abstractproperty() + voltage = abstractproperty() + size = abstractproperty() + package = abstractproperty() + speed = abstractproperty() + osc_div = abstractproperty() + + @property + def _part(self): + return "{}-{}{}{}{}".format(self.series, self.voltage, + self.size, self.package, self.speed) + + osc_dev_mapping = { + "OSC": [ + "GW1N-4", "GW1N-4B", "GW1N-4C", "GW1N-9", "GW1N-9C", + "GW1NR-4", "GW1NR-4B", "GW1NR-4C", "GW1NR-9", "GW1NR-9C", + "GW1NRF-4B", + "GW2A-18", "GW2A-18C", "GW2A-55", "GW2A-55C", + "GW2AN-55C", + "GW2AR-18", "GW2AR-18C", + "GW2ANR-18C"], + "OSCZ": [ + "GW1NS-4", "GW1NS-4C", + "GW1NSR-4", "GW1NSR-4C", + "GW1NSER-4C", + "GW1NZ-1"], + "OSCF": [ + "GW1NS-2", "GW1NS-2C", + "GW1NSE-2C", + "GW1NSR-2", "GW1NSR-2C"], + "OSCH": [ + "GW1N-1", "GW1N-1S", + "GW1NR-1"], + "OSCO": [ + "GW1N-2", "GW1N-1P5", "GW1N-2B", "GW1N-1P5B", + "GW1NR-2", "GW1NR-2B"], + "OSCW": [ + "GW2AN-18X", "GW2AN-9X"], + } + + # Each on-chip oscillator has unique properties + @property + def _osc_props(self): + props = { + "freq": 0, + "div_min": 2, + "div_max": 128, + "div_step": 2 + } + osc = self._oscillator + if osc is "OSC": + if "-4" in self.device: + props["freq"] = 210000000 + else: + props["freq"] = 250000000 + elif osc in ["OSCZ", "OSCO"]: + props["freq"] = 250000000 + elif osc in ["OSCF", "OSCH"]: + props["freq"] = 240000000 + elif osc is "OSCW": + props["freq"] = 200000000 + else: + assert False + return props + + @property + def osc_freq(self): + return self._osc_props["freq"] + + @property + def _oscillator(self): + oscillators = [] + for osc,devs in self.osc_dev_mapping.items(): + if self.device in devs: + oscillators.append(osc) + # should this be unique? + if len(oscillators) != 1: + assert False + return oscillators[0] + + + ### Common templates + + _common_file_templates = { + "{{name}}.v": r""" + /* {{autogenerated}} */ + {{emit_verilog()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{name}}.cst": r""" + // {{autogenerated}} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} + IO_LOC "{{port_name}}" {{pin_name}}; + {% for attr_name, attr_value in attrs.items() -%} + IO_PORT "{{port_name}}" {{attr_name}}={{attr_value}}; + {% endfor %} + {% endfor %} + """, + "{{name}}.sdc": r""" + // {{autogenerated}} + {% for net_signal,port_signal,frequency in platform.iter_clock_constraints() -%} + create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}] + {% endfor %} + {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} + """, + } + + + ### Apicula templates + + _apicula_required_tools = ["yosys", "nextpnr-gowin", "gowin_pack"] + + _apicula_file_templates = { + **TemplatedPlatform.build_script_templates, + **_common_file_templates, + "{{name}}.il": r""" + # {{autogenerated}} + {{emit_rtlil()}} + """, + "{{name}}.ys": r""" + # {{autogenerated}} + {% for file in platform.iter_files(".v") -%} + read_verilog {{get_override("read_verilog_opts")|options}} {{file}} + {% endfor %} + {% for file in platform.iter_files(".sv") -%} + read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}} + {% endfor %} + {% for file in platform.iter_files(".il") -%} + read_ilang {{file}} + {% endfor %} + read_ilang {{name}}.il + delete w:$verilog_initial_trigger + {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} + synth_gowin {{get_override("synth_opts")|options}} -top {{name}} -json {{name}}.json + {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}} + """, + } + + _apicula_command_templates = [ + r""" + {{invoke_tool("yosys")}} + {{quiet("-q")}} + {{get_override("yosys_opts")|options}} + -l {{name}}.rpt + {{name}}.ys + """, + r""" + {{invoke_tool("nextpnr-gowin")}} + {{quiet("--quiet")}} + {{get_override("nextpnr_opts")|options}} + --log {{name}}.tim + --device {{platform._part}} + --family {{platform.device}} + --json {{name}}.json + --freq {{platform.default_clk_frequency/1_000_000}} + --cst {{name}}.cst + --write {{name}}.json + """, + r""" + {{invoke_tool("gowin_pack")}} + -d {{platform.device}} + -o {{name}}.fs + {{get_override("gowin_pack_opts")|options}} + {{name}}.json + """ + ] + + ### Gowin vendor toolchain templates + + _gowin_required_tools = ["gw_sh"] + + @property + def _device_name(self): + return "{}-{}{}".format(self.series, self.size, self.subseries) + + _gowin_file_templates = { + **TemplatedPlatform.build_script_templates, + **_common_file_templates, + "{{name}}.tcl": r""" + # {{autogenerated}} + {% for file in platform.iter_files(".v",".sv",".vhd",".vhdl") -%} + add_file {{file}} + {% endfor %} + add_file -type verilog {{name}}.v + add_file -type cst {{name}}.cst + add_file -type sdc {{name}}.sdc + set_device -name {{platform._device_name}} {{platform._part}} + set_option -verilog_std v2001 -print_all_synthesis_warning 1 -show_all_warn 1 + {{get_override("add_options")|default("# (add_options placeholder)")}} + run all + """, + } + + _gowin_command_templates = [ + r""" + {{invoke_tool("gw_sh")}} + {{name}}.tcl + """, + "cp impl/pnr/project.fs {{name}}.fs" + ] + + def __init__(self, *, toolchain="Apicula"): + super().__init__() + + assert toolchain in ("Apicula", "Gowin") + self.toolchain = toolchain + + @property + def _toolchain_env_var(self): + if self.toolchain == "Apicula" or self.toolchain == "Gowin": + return f"AMARANTH_ENV_{self.toolchain}" + assert False + + @property + def required_tools(self): + if self.toolchain == "Apicula": + return self._apicula_required_tools + elif self.toolchain == "Gowin": + return self._gowin_required_tools + assert False + + @property + def file_templates(self): + if self.toolchain == "Apicula": + return self._apicula_file_templates + elif self.toolchain == "Gowin": + return self._gowin_file_templates + assert False + + @property + def command_templates(self): + if self.toolchain == "Apicula": + return self._apicula_command_templates + elif self.toolchain == "Gowin": + return self._gowin_command_templates + assert False + + def add_clock_constraint(self, clock, frequency): + super().add_clock_constraint(clock, frequency) + clock.attrs["keep"] = "true" + + @property + def default_clk_constraint(self): + if self.default_clk == "sys_clk0": + return Clock(self.osc_freq / self.osc_div) + # Use the defined Clock resource. + return super().default_clk_constraint + + def create_missing_domain(self, name): + if name == "sync" and self.default_clk is not None: + m = Module() + if self.default_clk == "sys_clk0": + props = self._osc_props + osc = self._oscillator + if (not isinstance(self.osc_div, int) + or self.osc_div < props["div_min"] + or self.osc_div > props["div_max"] + or (self.osc_div % props["div_step"]) != 0): + raise ValueError("OSC divider (osc_div) must be an integer between {} and {} in steps of {}, not {!r}".format( + props["div_min"], props["div_max"], props["div_step"], self.osc_div)) + + clk_i = Signal() + if osc is "OSCZ": + m.submodules += Instance(osc, + p_FREQ_DIV=self.osc_div, + i_OSCEN=Const(1), + o_OSCOUT=clk_i) + elif osc is "OSCO": + # TODO: Make use of regulator configurable + m.submodules += Instance(osc, + p_REGULATOR_EN=Const(1), + p_FREQ_DIV=self.osc_div, + i_OSCEN=Const(1), + o_OSCOUT=clk_i) + elif osc is "OSCF": + m.submodules += Instance(osc, + p_FREQ_DIV=self.osc_div, + o_OSCOUT30M=None, + o_OSCOUT=clk_i) + else: + m.submodules += Instance(osc, + p_FREQ_DIV=self.osc_div, + o_OSCOUT=clk_i) + + else: + clk_i = self.request(self.default_clk).i + + if self.default_rst is not None: + rst_i = self.request(self.default_rst).i + else: + rst_i = Const(0) + + m.domains += ClockDomain("sync") + m.d.comb += ClockSignal("sync").eq(clk_i) + m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync") + return m + + def _get_xdr_buffer(self, m, pin, i_invert=False, o_invert=False): + + def get_ireg(clk,d,q): + for bit in range(len(q)): + m.submodules += Instance("DFF", + i_clk = clk, + i_d = d[bit], + o_q = q[bit], + ) + + def get_oreg(clk,d,q): + for bit in range(len(q)): + m.submodules += Instance("DFF", + i_clk = clk, + i_d = d[bit], + o_q = q[bit] + ) + + def get_iddr(clk,d,q0,q1): + for bit in range(len(d)): + m.submodules += Instance("IDDR", + i_clk = clk, + i_d = d[bit], + o_q0 = q0[bit], + o_q1 = q1[bit] + ) + + def get_oddr(clk,d0,d1,q): + for bit in range(len(1)): + m.submodules += Instance("ODDR", + i_clk = clk, + i_d0 = d0[bit], + i_d1 = d1[bit], + o_q0 = q[bit], + ) + + def get_ineg(y, invert): + if invert: + a = Signal.like(y, name_suffix="_n") + m.d.comb += y.eq(~a) + return a + else: + return y + + def get_oneg(a, invert): + if invert: + y = Signal.like(a, name_suffix="_n") + m.d.comb += y.eq(~a) + return y + else: + return a + + + if "i" in pin.dir: + if pin.xdr < 2: + pin_i = get_ineg(pin.i, i_invert) + elif pin.xdr == 2: + pin_i0 = get_ineg(pin.i0, i_invert) + pin_i1 = get_ineg(pin.i1, i_invert) + if "o" in pin.dir: + if pin.xdr < 2: + pin_o = get_oneg(pin.o, o_invert) + elif pin.xdr == 2: + pin_o0 = get_oneg(pin.o0, o_invert) + pin_o1 = get_oneg(pin.o1, o_invert) + + i = o = t = None + + if "i" in pin.dir: + i = Signal(pin.width, name="{}_xdr_i".format(pin.name)) + if "o" in pin.dir: + o = Signal(pin.width, name="{}_xdr_o".format(pin.name)) + if pin.dir in ("oe", "io"): + t = Signal(1, name="{}_xdr_t".format(pin.name)) + + if pin.xdr == 0: + if "i" in pin.dir: + i = pin_i + if "o" in pin.dir: + o = pin_o + if pin.dir in ("oe", "io"): + t = ~pin.oe + elif pin.xdr == 1: + if "i" in pin.dir: + get_ireg(pin.i_clk, i, pin_i) + if "o" in pin.dir: + get_oreg(pin.o_clk, pin_o, o) + if pin.dir in ("oe", "io"): + get_oreg(pin.o_clk, ~pin.oe,t) + elif pin.xdr == 2: + if "i" in pin.dir: + get_iddr(pin.i_clk, i, pin_i0, pin_i1) + if "o" in pin.dir: + get_oddr(pin.o_clk, pin_o0, pin_o1, o) + if pin.dir in ("oe", "io"): + get_oreg(pin.o_clk, ~pin.oe, t) + else: + assert False + + return (i, o, t) + + def get_input(self, pin, port, attrs, invert): + self._check_feature("single-ended input", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUF", + i_I=port.io[bit], + o_O=i[bit] + ) + return m + + def get_output(self, pin, port, attrs, invert): + self._check_feature("single-ended output", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, port.io, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUF", + i_I=o[bit], + o_O=port.io[bit] + ) + return m + + def get_tristate(self, pin, port, attrs, invert): + self._check_feature("single-ended tristate", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("TBUF", + i_OEN=t, + i_I=o[bit], + o_O=port.io[bit] + ) + return m + + def get_input_output(self, pin, port, attrs, invert): + self._check_feature("single-ended input/output", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUF", + i_OEN=t, + i_I=o[bit], + o_O=i[bit], + io_IO=port.io[bit] + ) + return m + + def get_diff_input(self, pin, port, attrs, invert): + self._check_feature("differential input", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) + for bit in range(pin.wodth): + m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IBUF", + i_I=port.p[bit], + i_IB=port.n[bit], + o_O=i[bit] + ) + return m + + def get_diff_output(self, pin, port, attrs, invert): + self._check_feature("differential output", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_OBUF", + i_I=o[bit], + o_O=port.p[bit], + o_OB=port.n[bit], + ) + return m + + def get_diff_tristate(self, pin, port, attrs, invert): + self._check_feature("differential tristate", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_TBUF", + i_OEN=t, + i_I=o[bit], + o_O=port.p[bit], + o_OB=port.n[bit] + ) + return m + + def get_diff_input_output(self, pin, port, atttr, invert): + self._check_feature("differential input/output", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IOBUF", + i_OEN=t, + i_I=o[bit], + o_O=i[bit], + io_IO=port.p[bit], + io_IOB=port.n[bit] + ) + return m