diff --git a/README.md b/README.md index 1c09fce95..282ea5aa6 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,8 @@ Amaranth can be used to target any FPGA or ASIC process that accepts behavioral * AMD 7-series (toolchains: Vivado, ISE); * AMD UltraScale, UltraScale+ (toolchains: Vivado); * Altera (toolchains: Quartus); - * Quicklogic EOS S3 (toolchains: **Yosys+VPR**). + * Quicklogic EOS S3 (toolchains: **Yosys+VPR**); + * GateMate (toolchains: **Yosys**+GateMate p_r). FOSS toolchains are listed in **bold**. diff --git a/amaranth/vendor/__init__.py b/amaranth/vendor/__init__.py old mode 100644 new mode 100755 index c60bb4d0d..b475ab862 --- a/amaranth/vendor/__init__.py +++ b/amaranth/vendor/__init__.py @@ -6,6 +6,7 @@ __all__ = [ "AlteraPlatform", "AMDPlatform", + "GateMatePlatform" "GowinPlatform", "IntelPlatform", "LatticeECP5Platform", @@ -27,6 +28,9 @@ def __getattr__(name): if name in ("AlteraPlatform", "IntelPlatform"): from ._altera import AlteraPlatform return AlteraPlatform + if name == "GateMatePlatform": + from ._gatemate import GateMatePlatform + return GateMatePlatform if name == "GowinPlatform": from ._gowin import GowinPlatform return GowinPlatform diff --git a/amaranth/vendor/_gatemate.py b/amaranth/vendor/_gatemate.py new file mode 100755 index 000000000..9e1aea57e --- /dev/null +++ b/amaranth/vendor/_gatemate.py @@ -0,0 +1,87 @@ +from abc import abstractmethod +from amaranth import * +from amaranth.build import * +from amaranth.lib.cdc import ResetSynchronizer + + +__all__ = ["GateMatePlatform"] + + +class GateMatePlatform(TemplatedPlatform): + """ + Required tools: + * ``yosys`` + * ``p_r`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_GATEMATE``, if present. + """ + + + toolchain = "GateMate" + + required_tools = [ + "yosys", + "p_r", + ] + + file_templates = { + **TemplatedPlatform.build_script_templates, + "{{name}}.il": r""" + # {{autogenerated}} + {{emit_rtlil()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{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 + {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} + synth_gatemate {{get_override("synth_opts")|options}} -top {{name}} -vlog {{name}}_synth.v + {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}} + """, + "{{name}}.ccf": r""" + # {{autogenerated}} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} + Net "{{port_name}}" Loc = "{{pin_name}}" + {%- for constraint, value in attrs.items() -%} + | {{constraint}}={{value}} + {%- endfor -%}; + {% endfor %} + """, + } + + command_templates = [ + r""" + {{invoke_tool("yosys")}} + {{quiet("-q")}} + {{get_override("yosys_opts")|options}} + -l {{name}}.rpt + {{name}}.ys + """, + r""" + {{invoke_tool("p_r")}} + {{verbose("-v")}} + -i {{name}}_synth.v + -o {{name}} + -ccf {{name}}.ccf + -cCP > {{name}}.tim + """, + ] + + # Common logic + + def add_clock_constraint(self, clock, frequency): + super().add_clock_constraint(clock, frequency) + clock.attrs["keep"] = True diff --git a/examples/Blinky.py b/examples/Blinky.py new file mode 100644 index 000000000..68fe8a710 --- /dev/null +++ b/examples/Blinky.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 + +from amaranth import * +from amaranth.sim import * +from amaranth.back import verilog + +import argparse +import subprocess +import importlib +import os +import shutil + +top_name = "Blinky" + +class Blinky(Elaboratable): + + def __init__(self, num_leds=1, clock_divider=21): + self.num_leds = num_leds + self.clock_divider = clock_divider + self.leds = Signal(num_leds) + + def elaborate(self, platform): + # Create a new Amaranth module + m = Module() + + # This is a local signal, which will not be accessible from outside. + count = Signal(self.clock_divider) + + # If the platform is not defined then it is simulation + if platform is not None: + + # ULX3S + #leds = [platform.request("led", i) for i in range(self.num_leds)] + #m.d.comb += [led.o.eq(self.leds[i]) for i, led in enumerate(leds)] + + # Olimex GateMate + led = platform.request("led", 0) + m.d.comb += led.o.eq(self.leds) + + # In the sync domain all logic is clocked at the positive edge of + # the implicit clk signal. + m.d.sync += count.eq(count + 1) + with m.If(count == (2**self.clock_divider - 1)): + m.d.sync += [ + self.leds.eq(~self.leds), + count.eq(0) + ] + + return m + + +def clean(): + files_to_remove = [f"{top_name}.vcd", f"{top_name}.gtkw", f"{top_name}.v"] + build_dir = "build" + + for file in files_to_remove: + try: + os.remove(file) + print(f"Removed {file}") + except FileNotFoundError: + print(f"{file} not found, skipping") + + if os.path.isdir(build_dir): + try: + shutil.rmtree(build_dir) + print(f"Removed {build_dir} directory") + except OSError as e: + print(f"Error removing {build_dir}: {e}") + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + parser.add_argument("-s", "--simulate", action="store_true", help="Simulate Blinky Example") + parser.add_argument("-b", "--build", action="store_true", help="Build The Blinky Example") + parser.add_argument("-v", "--verilog", action="store_true", help="Generate Verilog for Blinky Example") + parser.add_argument("-p", "--platform", type=str, required=False, help="Platform module (e.g., amaranth_boards.ulx3s.ULX3S_85F_Platform)") + parser.add_argument("-n", "--num-leds", type=int, default=1, help="Number of LEDs") + parser.add_argument("-cd", "--clock-divider", type=int, default=21, help="Clock divider (bit width of the counter)") + parser.add_argument("-cf", "--clock-frequency", type=float, default=1.0, help="Clock frequency in MHz") + parser.add_argument("-rt", "--runtime", type=int, default=30000, help="Testbench runtime in clock cycles") + parser.add_argument("-c", "--clean", action="store_true", help="Clean generated files and build directory") + parser.add_argument("-dp", "--do-program", action="store_true", help="Program the device after building") + parser.add_argument("-gw", "--gtkwave", action="store_true", help="Open GTKWave after simulation") + + args = parser.parse_args() + + if args.clean: + clean() + + else: + num_leds = args.num_leds if args.num_leds is not None else 1 + clock_divider = args.clock_divider if args.clock_divider is not None else 21 + clock_frequency = args.clock_frequency if args.clock_frequency is not None else 1.0 + runtime = args.runtime if args.runtime is not None else 30000 + do_program = args.do_program + + if args.simulate: + + def testbench(): + for _ in range(runtime): + yield Tick() + + # Instantiate the Blinky module + dut = Blinky(num_leds, clock_divider) + + # Create a simulator + sim = Simulator(dut) + sim.add_clock(1e-6 / clock_frequency) + sim.add_process(testbench) + with sim.write_vcd(f"{top_name}.vcd", f"{top_name}.gtkw", traces=[dut.leds]): + sim.run() + + # Open GTKWave with the generated VCD file if --gtkwave is set + if args.gtkwave: + subprocess.run(["gtkwave", f"{top_name}.vcd"]) + + elif args.build: + if args.platform is None: + raise ValueError("Platform must be specified for building") + platform_module_name, platform_class_name = args.platform.rsplit(".", 1) + platform_module = importlib.import_module(platform_module_name) + platform_class = getattr(platform_module, platform_class_name) + + plat = platform_class() + plat.build(Blinky(num_leds, clock_divider), do_program=do_program) + + elif args.verilog: + dut = Blinky(num_leds, clock_divider) + with open(f"{top_name}.v", "w") as f: + f.write(verilog.convert(dut, ports=[dut.leds])) + +# TODO: Maybe write an additional file where all of the amaranth_boards with their vendors are specified so that you can type ulx3s -85F instead of amaranth_boards.ulx3s.ULX3S_85F_Platform