├── amaranth_to_litex ├── __init__.py └── wrapper │ └── litex.py ├── .gitignore ├── setup.py ├── examples ├── simple.py ├── counter.py └── usb.py ├── pyproject.toml └── README.md /amaranth_to_litex/__init__.py: -------------------------------------------------------------------------------- 1 | from .wrapper.litex import * 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | /*.egg-info 3 | /.eggs 4 | /build* 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="amaranth_to_litex", 5 | author="Pierre-Olivier Vauboin", 6 | author_email="po@lambdaconcept.com", 7 | packages=find_packages(), 8 | project_urls={ 9 | "Source Code": "https://github.com/lambdaconcept/amaranth-to-litex", 10 | "Bug Tracker": "https://github.com/lambdaconcept/amaranth-to-litex/issues", 11 | }, 12 | ) 13 | -------------------------------------------------------------------------------- /examples/simple.py: -------------------------------------------------------------------------------- 1 | from migen import * 2 | 3 | from amaranth_to_litex import * 4 | from counter import CounterStream 5 | 6 | 7 | class Top(Module): 8 | def __init__(self, platform): 9 | 10 | self.submodules.cnt = amaranth_to_litex(platform, 11 | CounterStream(width=26), 12 | ) 13 | 14 | self.comb += self.cnt.source.ready.eq(1) 15 | 16 | led = platform.request("rgb_led", 0) 17 | self.comb += [ 18 | led.r.eq(self.cnt.source.data[-1]), 19 | led.g.eq(self.cnt.source.data[-1]), 20 | led.b.eq(self.cnt.source.data[-1]), 21 | ] 22 | 23 | 24 | def main(): 25 | from litex_boards.platforms import lambdaconcept_ecpix5 26 | 27 | platform = lambdaconcept_ecpix5.Platform(device="85F") 28 | top = Top(platform) 29 | platform.build(top, build_dir="build.simple") 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "amaranth_to_litex" 3 | version = "0.1.0" 4 | description = "Use amaranth-to-litex to simply import Amaranth code into a Litex project." 5 | authors = [ 6 | {name = "Pierre-Olivier Vauboin", email = "po@lambdaconcept.com"}, 7 | ] 8 | dependencies = [ 9 | "migen @ git+https://github.com/m-labs/migen.git", 10 | "jinja2>=3.1.3", 11 | "lambdalib @ git+https://github.com/lambdaconcept/lambdalib", 12 | "litex-boards @ git+https://github.com/litex-hub/litex-boards.git", 13 | "amaranth-yosys>=0.40.0.0.post93", 14 | ] 15 | requires-python = ">=3.8" 16 | readme = "README.md" 17 | license = {text = "Resources"} 18 | 19 | [project.urls] 20 | "Source Code" = "https://github.com/lambdaconcept/amaranth-to-litex" 21 | "Bug Tracker" = "https://github.com/lambdaconcept/amaranth-to-litex/issues" 22 | [build-system] 23 | requires = ["pdm-backend"] 24 | build-backend = "pdm.backend" 25 | -------------------------------------------------------------------------------- /examples/counter.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | 3 | from lambdalib.interface import stream 4 | 5 | 6 | # https://github.com/amaranth-lang/amaranth/blob/main/examples/basic/ctr_en.py 7 | class Counter(Elaboratable): 8 | def __init__(self, width): 9 | self.v = Signal(width, reset=2**width-1) 10 | self.o = Signal() 11 | self.en = Signal() 12 | 13 | def elaborate(self, platform): 14 | m = Module() 15 | m.d.sync += self.v.eq(self.v + 1) 16 | m.d.comb += self.o.eq(self.v[-1]) 17 | return EnableInserter(self.en)(m) 18 | 19 | 20 | class CounterStream(Elaboratable): 21 | def __init__(self, width): 22 | self.width = width 23 | self.source = stream.Endpoint([("data", width)]) 24 | 25 | def elaborate(self, platform): 26 | source = self.source 27 | 28 | m = Module() 29 | 30 | m.submodules.cnt = cnt = Counter(self.width) 31 | 32 | m.d.comb += [ 33 | source.data.eq(cnt.v), 34 | source.valid.eq(1), 35 | source.first.eq(cnt.v == 0), 36 | source.last.eq(cnt.v == 2**self.width-1), 37 | 38 | cnt.en.eq(self.source.ready), 39 | ] 40 | 41 | return m 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amaranth-to-litex 2 | 3 | Use amaranth-to-litex to simply import Amaranth code into a Litex project. 4 | 5 | ### Exported interfaces 6 | 7 | The converter automatically collects the interfaces contained in self.__ dict __: 8 | 9 | * Signals 10 | * Records 11 | * Endpoints (streams) 12 | * Pins (platform.request()) 13 | * Constants 14 | 15 | ### Quick start 16 | 17 | Run example: 18 | 19 | ``` 20 | python examples/usb.py 21 | ``` 22 | 23 | __CounterStream__ is an Amaranth module, imported into a Litex project with: 24 | ``` 25 | self.submodules.cnt = amaranth_to_litex(platform, 26 | CounterStream(width=26), 27 | ) 28 | ``` 29 | 30 | __USBGenericDevice__ is a LUNA based device, imported in Litex: 31 | ``` 32 | litex_pads = platform.request("ulpi") 33 | usb_pins = amaranth_pins_from_litex(litex_pads, { 34 | "data": "io", 35 | "clk" : "i", 36 | "dir" : "i", 37 | }) 38 | 39 | self.submodules.dev = dev = amaranth_to_litex(platform, 40 | USBGenericDevice( 41 | pins=usb_pins, 42 | vid=0xffff, pid=0x1234, 43 | ), 44 | ) 45 | ``` 46 | 47 | This works by: 48 | 49 | 1. first compiling the Amaranth module into verilog, see the generated files: 50 | 51 | ``` 52 | less build/CounterStream.v 53 | less build/USBGenericDevice.v 54 | ``` 55 | 56 | 2. then recreating a Litex module with the same interfaces, see the generated file: 57 | 58 | ``` 59 | less build/CounterStream.py 60 | less build/USBGenericDevice.py 61 | ``` 62 | 63 | ### API Reference 64 | 65 | * Convert an Amaranth module to a Litex module: 66 | ``` 67 | self.submodules.litex_module = amaranth_to_litex(platform, MyAmaranthModule()): 68 | ``` 69 | 70 | * Create an Amaranth signal: 71 | ``` 72 | amaranth_sig = amaranth_signal(name="my_signal") 73 | ``` 74 | 75 | * Convert Litex platform pins to Amaranth pins: 76 | ``` 77 | litex_pads = platform.request("spi") 78 | amaranth_pins = amaranth_pins_from_litex(litex_pads) 79 | ``` 80 | -------------------------------------------------------------------------------- /examples/usb.py: -------------------------------------------------------------------------------- 1 | # 2023 - LambdaConcept - po@lambdaconcept.com 2 | 3 | from migen import * 4 | 5 | from luna.gateware.architecture.car import PHYResetController 6 | 7 | from lambdalib.cores.usb import USBGenericDevice 8 | from counter import CounterStream 9 | 10 | from amaranth_to_litex import * 11 | 12 | 13 | class _CRG(Module): 14 | def __init__(self, platform, sys_clk_freq): 15 | 16 | clk100 = platform.request("clk100") 17 | 18 | # System clock 19 | self.clock_domains += ClockDomain("sys") 20 | self.comb += ClockSignal("sys").eq(clk100) 21 | 22 | # USB clock 23 | self.clock_domains += ClockDomain("usb") 24 | platform.add_period_constraint(ClockSignal("usb"), 1e9/60e6) 25 | 26 | # USB reset controller (LUNA) 27 | self.submodules.usb_ctrl = usb_ctrl = amaranth_to_litex(platform, 28 | PHYResetController(), 29 | ) 30 | self.comb += ResetSignal("usb").eq(usb_ctrl.phy_reset) 31 | 32 | 33 | class Top(Module): 34 | def __init__(self, sys_clk_freq, platform): 35 | 36 | # CRG 37 | self.submodules.crg = _CRG( 38 | platform, sys_clk_freq, 39 | ) 40 | 41 | # Convert Litex pads to Amaranth pins 42 | litex_pads = platform.request("ulpi") 43 | # Amaranth platforms specify the pin direction "i", "o", "io", 44 | # but not Litex platforms. Some Amaranth modules rely on this 45 | # for accessing pins by their subsignals (pin.i, pin.oe, ...) 46 | # In such case for correct mapping we need to help 47 | # the converter by passing an explicit direction hint. 48 | usb_pins = amaranth_pins_from_litex(litex_pads, { 49 | "data": "io", 50 | "clk" : "i", 51 | "dir" : "i", 52 | }) 53 | 54 | # Create the USB device (LUNA) 55 | self.submodules.dev = dev = amaranth_to_litex(platform, 56 | USBGenericDevice( 57 | pins=usb_pins, 58 | vid=0xffff, pid=0x1234, 59 | ), 60 | ) 61 | 62 | # Create a dummy data generator 63 | self.submodules.cnt = cnt = amaranth_to_litex(platform, 64 | CounterStream(width=8), 65 | ) 66 | 67 | # Send dummy data to the USB IN endpoint 68 | self.comb += cnt.source.connect(dev.sink) 69 | 70 | 71 | def main(): 72 | from litex_boards.platforms import lambdaconcept_ecpix5 73 | 74 | platform = lambdaconcept_ecpix5.Platform(device="85F") 75 | top = Top(100e6, platform) 76 | platform.build(top, build_dir="build.usb") 77 | 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /amaranth_to_litex/wrapper/litex.py: -------------------------------------------------------------------------------- 1 | # 2023 - LambdaConcept - po@lambdaconcept.com 2 | 3 | import os 4 | import sys 5 | import zlib 6 | import jinja2 7 | import pprint 8 | import logging 9 | import textwrap 10 | import importlib.util 11 | from collections import defaultdict 12 | 13 | from amaranth import * 14 | from amaranth.hdl import ir 15 | from amaranth.hdl.rec import * 16 | from amaranth.back.verilog import convert_fragment 17 | 18 | 19 | __all__ = [ 20 | "amaranth_to_litex", 21 | "amaranth_signal", 22 | "amaranth_pins_from_litex", 23 | 24 | "gen_verilog", 25 | ] 26 | 27 | 28 | logging.basicConfig() 29 | logger = logging.getLogger() 30 | # logger.setLevel(logging.DEBUG) 31 | pp = pprint.PrettyPrinter(indent=4, compact=False) 32 | 33 | 34 | # adapted from https://github.com/amaranth-lang/amaranth/blob/main/amaranth/back/verilog.py 35 | def convert(elaboratable, name="top", platform=None, ports=None, *, emit_src=True, 36 | strip_internal_attrs=False, return_fragment=False, **kwargs): 37 | fragment = ir.Fragment.get(elaboratable, platform).prepare(ports=ports, **kwargs) 38 | verilog_text, name_map = convert_fragment(fragment, name, emit_src=emit_src, 39 | strip_internal_attrs=strip_internal_attrs) 40 | if return_fragment: 41 | return verilog_text, fragment 42 | 43 | return verilog_text 44 | 45 | 46 | def isinstance_endpoint(record): 47 | # To be more generic we recognise stream endpoints based on their fields 48 | # rather than matching the class type. 49 | required = {"valid", "ready", "payload"} 50 | for field in required: 51 | if not hasattr(record, field): 52 | return False 53 | return True 54 | 55 | 56 | def gen_module_name(elaboratable): 57 | # Make a hash of all the parameters contained in the elaboratable 58 | # to generate a unique but reproducable suffix name. 59 | # Converting twice the same Amaranth module instanciated with the 60 | # same parameters will yield the same name and thus generate 61 | # only one verilog. 62 | basename = elaboratable.__class__.__name__ 63 | 64 | attrs = [] 65 | for key, value in elaboratable.__dict__.items(): 66 | if not key.startswith("_"): 67 | attrs.append(key) 68 | attrs.append(str(value)) 69 | 70 | mangle = bytes("_".join(attrs), encoding="ascii") 71 | suffix = hex(zlib.crc32(mangle))[2:] 72 | 73 | logger.debug("mangle: %s", mangle) 74 | logger.debug("suffix: %s", suffix) 75 | 76 | return "{}_{}".format(basename, suffix) 77 | 78 | 79 | def get_ports(elaboratable): 80 | # Iterate over the elaboratable object to get the list of ports to be 81 | # exported to the verilog generator. 82 | # Also records important information inside the metadata dict for later 83 | # to help reconstruct the python wrapper. 84 | 85 | ports = [] 86 | metadata = defaultdict(dict) 87 | 88 | for key, value in elaboratable.__dict__.items(): 89 | logger.debug("member: %s, type: %s, value: %s", key, type(value), value) 90 | 91 | if isinstance(value, Signal): 92 | ports.append(value) 93 | metadata["signals"][key] = value 94 | metadata["duid"][value.duid] = key # value.name 95 | 96 | elif isinstance(value, Record): 97 | for name, _, _ in value.layout: 98 | field = value[name] 99 | 100 | if isinstance(field, Signal): 101 | ports.append(field) 102 | metadata["duid"][field.duid] = ".".join([key, name]) 103 | 104 | elif isinstance(field, Record): 105 | for subname, _, _ in field.layout: 106 | subfield = field[subname] 107 | 108 | ports.append(subfield) 109 | metadata["duid"][subfield.duid] = ".".join([key, name, subname]) 110 | 111 | if isinstance_endpoint(value): 112 | metadata["endpoints"][key] = value 113 | elif isinstance(value, Record): 114 | # we recognise amaranth pins as a special record 115 | # that contains our private member __litex_pads. 116 | if hasattr(value, "__litex_pads"): 117 | metadata["pins"][key] = value 118 | else: 119 | metadata["records"][key] = value 120 | 121 | elif isinstance(value, int) or \ 122 | isinstance(value, float) or \ 123 | isinstance(value, str): 124 | if not key.startswith("_"): 125 | metadata["constants"][key] = value 126 | 127 | return ports, metadata 128 | 129 | 130 | def get_layout_description(layout): 131 | desc = [] 132 | 133 | for name, shape, _ in layout: 134 | logger.debug("layout: %s, %s", name, shape) 135 | 136 | if isinstance(shape, Layout): 137 | text = get_layout_description(shape) 138 | else: 139 | text = shape 140 | 141 | desc.append("(\"{}\", {})".format(name, text)) 142 | 143 | return "[{}]".format(", ".join(desc)) 144 | 145 | 146 | def get_record_description(record): 147 | return get_layout_description(record.layout) 148 | 149 | 150 | def get_endpoint_description(endpoint): 151 | return get_layout_description(endpoint.payload.layout) 152 | 153 | 154 | def import_pyfile(name, filename): 155 | spec = importlib.util.spec_from_file_location(name, filename) 156 | module = importlib.util.module_from_spec(spec) 157 | sys.modules[name] = module 158 | spec.loader.exec_module(module) 159 | return module 160 | 161 | 162 | def gen_lhs_rhs(fragment, metadata, pad_name, sig): 163 | logger.debug("lookup amaranth duid: %s", sig.duid) 164 | sig_name = "self." + metadata["duid"][sig.duid] 165 | 166 | # get the corresponding direction 167 | dir = None 168 | for port, direction in fragment.ports.items(): 169 | if port.duid == sig.duid: 170 | dir = direction 171 | break 172 | 173 | if dir == "i": 174 | lhs = sig_name 175 | rhs = pad_name 176 | elif dir == "o": 177 | lhs = pad_name 178 | rhs = sig_name 179 | else: 180 | raise NotImplementedError 181 | 182 | logger.debug("comb: (%s), %s, %s", dir, lhs, rhs) 183 | return (lhs, rhs) 184 | 185 | 186 | def gen_litex_statements(fragment, metadata): 187 | tristates = [] 188 | connects = [] 189 | 190 | # for all pins instances found in the module (usually only one though) 191 | for key, amaranth_pins in metadata["pins"].items(): 192 | 193 | # get the corresponding litex pads 194 | litex_pads = amaranth_pins.__litex_pads 195 | 196 | # use the amaranth pins for iteration because they contain 197 | # the most useful information (duid, dir, ...) 198 | for name, _, _ in amaranth_pins.layout: 199 | logger.debug("lookup amaranth pin: %s", name) 200 | 201 | sig = amaranth_pins[name] 202 | pad_name = "{}.{}".format(key, name) 203 | 204 | if isinstance(sig, Signal): 205 | tuples = [ gen_lhs_rhs(fragment, metadata, pad_name, sig) ] 206 | 207 | # In Amaranth, tristates are automatically instanciated when 208 | # a platform pin direction is set to "io". 209 | # In Litex we need to instanciate the tristate manually, 210 | # we can detect this based on the presence of subsignals "i", "o", "oe". 211 | elif isinstance(sig, Record): 212 | if hasattr(sig, "i") and hasattr(sig, "o") and hasattr(sig, "oe"): 213 | ts_name = "t_{}".format(name) 214 | tristates.append((ts_name, pad_name)) 215 | 216 | tuples = [ 217 | gen_lhs_rhs(fragment, metadata, ts_name + ".i", sig.i), 218 | gen_lhs_rhs(fragment, metadata, ts_name + ".o", sig.o), 219 | gen_lhs_rhs(fragment, metadata, ts_name + ".oe", sig.oe), 220 | ] 221 | 222 | elif hasattr(sig, "i"): 223 | tuples = [ gen_lhs_rhs(fragment, metadata, pad_name, sig.i) ] 224 | elif hasattr(sig, "o"): 225 | tuples = [ gen_lhs_rhs(fragment, metadata, pad_name, sig.o) ] 226 | 227 | connects += tuples 228 | 229 | return tristates, connects 230 | 231 | 232 | def gen_litex(fragment, metadata, name=None, output_dir=None): 233 | if output_dir is None: 234 | output_dir = "" 235 | 236 | params = {} 237 | 238 | # iterate over the instance ports and recreate the signal mapping 239 | for sig, direction in fragment.ports.items(): 240 | logger.debug("sig: %s, duid: %s", sig.name, sig.duid) 241 | 242 | try: 243 | value = "self." + metadata["duid"][sig.duid] 244 | except KeyError: 245 | # Signals that are not listed in the metadata dict are likely 246 | # special signals like clocks and resets. 247 | # Valid clock/reset names are "*_clk" and "*_rst", except for the 248 | # the sys domain named "clk" and "rst". 249 | domain = sig.name[:-4] 250 | if not domain: 251 | domain = "sys" 252 | 253 | if sig.name.endswith("clk"): 254 | value = "ClockSignal(\"{}\")".format(domain) 255 | elif sig.name.endswith("rst"): 256 | value = "ResetSignal(\"{}\")".format(domain) 257 | 258 | key = "{}_{}".format(direction, sig.name) 259 | params[key] = value 260 | 261 | # auto generate tristates and pins/pads connections 262 | tristates, connects = gen_litex_statements(fragment, metadata) 263 | 264 | template = """ 265 | # Automatically generated by amaranth_to_litex. 266 | # 267 | # ############### DO NOT EDIT. ############### 268 | 269 | import os 270 | 271 | from migen import * 272 | 273 | from litex.soc.interconnect import stream 274 | 275 | # Verilog interface 276 | 277 | \"\"\" 278 | {{classname}} my_{{classname.lower()}}( 279 | {% for i, (k, v) in enumerate(params.items()) %} 280 | {{".{}()".format(k[2:])}}{{"," if i < len(params)-1 else ""}} 281 | {% endfor %} 282 | ); 283 | \"\"\" 284 | 285 | class {{classname}}(Module): 286 | def __init__(self, platform): 287 | 288 | # Signals 289 | 290 | {% for name, sig in signals.items() %} 291 | self.{{name}} = Signal({{sig.width}}, reset={{sig.reset}}, reset_less={{sig.reset_less}}) 292 | {% endfor %} 293 | 294 | # Pins 295 | 296 | {% for name, rec in pins.items() %} 297 | self.{{name}} = Record({{get_record_description(rec)}}) 298 | {% endfor %} 299 | 300 | # Records 301 | 302 | {% for name, rec in records.items() %} 303 | self.{{name}} = Record({{get_record_description(rec)}}) 304 | {% endfor %} 305 | 306 | # Endpoints 307 | 308 | {% for name, ep in endpoints.items() %} 309 | self.{{name}} = stream.Endpoint({{get_endpoint_description(ep)}}) 310 | {% endfor %} 311 | 312 | # Constants 313 | 314 | {% for name, value in constants.items() %} 315 | self.{{name}} = {{value.__repr__()}} 316 | {% endfor %} 317 | 318 | # # # 319 | 320 | params = dict( 321 | {% for k, v in params.items() %} 322 | {{k}} = {{v}}, 323 | {% endfor %} 324 | ) 325 | self.specials += Instance("{{instancename}}", **params) 326 | 327 | if platform is not None: 328 | platform.add_source(os.path.join("{{output_dir}}", "{{instancename}}.v"), "verilog") 329 | 330 | def autoconnect_pads(self, {{", ".join(pins.keys())}}): 331 | 332 | # Tristates 333 | 334 | {% for name, sig in tristates %} 335 | {{name}} = TSTriple(len({{sig}})) 336 | self.specials += {{name}}.get_tristate({{sig}}) 337 | {% endfor %} 338 | 339 | # Connect 340 | 341 | self.comb += [ 342 | {% for lhs, rhs in connects %} 343 | {{lhs}}.eq({{rhs}}), 344 | {% endfor %} 345 | ] 346 | """ 347 | 348 | source = textwrap.dedent(template).strip() 349 | compiled = jinja2.Template(source, trim_blocks=True, lstrip_blocks=True) 350 | output = compiled.render(dict( 351 | classname=name, 352 | instancename=name, 353 | output_dir=output_dir, 354 | signals=metadata["signals"], 355 | pins=metadata["pins"], 356 | records=metadata["records"], 357 | endpoints=metadata["endpoints"], 358 | constants=metadata["constants"], 359 | params=params, 360 | tristates=tristates, 361 | connects=connects, 362 | 363 | # utility functions 364 | get_record_description=get_record_description, 365 | get_endpoint_description=get_endpoint_description, 366 | enumerate=enumerate, 367 | len=len, 368 | )) 369 | 370 | # write python file 371 | filename = os.path.join(output_dir, name + ".py") 372 | os.makedirs(output_dir, exist_ok=True) 373 | with open(filename, "w") as f: 374 | f.write(output) 375 | 376 | # import python file 377 | module = import_pyfile(name, filename) 378 | return getattr(module, name) 379 | 380 | 381 | def gen_verilog(elaboratable, name=None, output_dir=None, platform=None): 382 | ports, metadata = get_ports(elaboratable) 383 | logger.debug("ports: \n%s", pp.pformat(ports)) 384 | logger.debug("metadata: %s", pp.pformat(metadata)) 385 | ver, frag = convert(elaboratable, name=name, ports=ports, platform=platform, 386 | emit_src=False, return_fragment=True) 387 | 388 | # write verilog file 389 | filename = os.path.join(output_dir, name + ".v") 390 | os.makedirs(output_dir, exist_ok=True) 391 | with open(filename, "w") as f: 392 | f.write(ver) 393 | 394 | return frag, metadata 395 | 396 | 397 | def amaranth_signal(*args, **kwargs): 398 | return Signal(*args, **kwargs) 399 | 400 | 401 | def amaranth_pins_from_litex(pads, dirs=None): 402 | if dirs is None: 403 | dirs = {} 404 | 405 | # Cast the amaranth record layout using the 406 | # direction hints passed as parameter. 407 | cast = [] 408 | for name, shape in pads.layout: 409 | if name in dirs: 410 | 411 | dir = dirs[name] 412 | subs = [] 413 | 414 | # see pin_layout in amaranth/lib/io.py 415 | if "i" in dir: 416 | subs.append(("i", shape)) 417 | if "o" in dir: 418 | subs.append(("o", shape)) 419 | if dir in ["oe", "io"]: 420 | subs.append(("oe", 1)) 421 | 422 | cast.append((name, subs)) 423 | 424 | else: 425 | cast.append((name, shape)) 426 | 427 | logger.debug("cast: \n%s", pp.pformat(cast)) 428 | rec = Record(cast, name="__pins__" + pads.name) 429 | 430 | # Add a private member to the amaranth pins record to remember 431 | # this is a conversion from a litex pads record. 432 | rec.__litex_pads = pads 433 | return rec 434 | 435 | 436 | def amaranth_to_litex(platform, elaboratable, name=None, output_dir=None, 437 | autoconnect_pads=True): 438 | if name is None: 439 | name = gen_module_name(elaboratable) 440 | if output_dir is None: 441 | output_dir = "build" 442 | 443 | fragment, metadata = gen_verilog(elaboratable, name=name, output_dir=output_dir, platform=platform) 444 | litex_class = gen_litex(fragment, metadata, name=name, output_dir=output_dir) 445 | 446 | litex_instance = litex_class(platform) 447 | # Add private metadata members to the litex instance to remember 448 | # this is a conversion from an amaranth module. 449 | litex_instance.__fragment = fragment 450 | litex_instance.__metadata = metadata 451 | 452 | if autoconnect_pads: 453 | pads = [ rec.__litex_pads 454 | for rec in metadata["pins"].values() 455 | ] 456 | litex_instance.autoconnect_pads(*pads) 457 | 458 | return litex_instance 459 | 460 | 461 | if __name__ == "__main__": 462 | from ..cores.counter import * 463 | 464 | ctr = Counter(width=24) 465 | amaranth_to_litex(None, ctr, name=None, output_dir="") 466 | --------------------------------------------------------------------------------