Including non-Amaranth Instances in Amaranth [repost]

A common question in the #amaranth-lang Libera.Chat channel asks how one can include a non-Amaranth component—e.g. a library cell or a component from a Verilog design—so it seemed worth writing up a quick note we can link to answer that.

Invoking a Component

The key is to create an amaranth.Instance component, as in the following example:

m.submodules.my_component = Instance("CustomFlipFlop",
		# Attributes
		a_HasAsyncReset = False,
		# Parameters
		p_NumberInputs  = "1",
		p_NumberOutputs = "1",

		# Inputs
		i_D = my_input_signal,
		# Ouptuts
		o_Q = my_output_signal,
		# Bidirectional / inouts
		io_DZ = my_tristate_pin

The first parameter to the Instance constructor is the name of the component to be instantiated, which usually matches e.g. your library cell instance, or your Verilog module name. The remaining (keyword) arguments are the inputs and outputs to the Instance. As these inputs/outputs to this function currently lack type information, we'll need to prefix each name with a short prefix, depending on its type:

Prefix Corresponding HDL Type
a_ Attribute
p_ Parameter
i_ Input Port
o_ Output Port
io_ Bidirectional / inout port

Including HDL Sources

If you're using an external HDL source – for example, a Verilog file – you'll need to tell Amaranth to include the relevant HDL source in your build plan. This is accomplished by calling the add_file method on the platform being used to build your project, which takes the following signature:

platform.add_file(filename, file_contents)

Here, filename is the name that will be used internally to the Amaranth build plan (including by the target synthesis tool); and file_contents is either the file's contents (as a str or bytes object), or a file-like object.

An example of adding a single Verilog file to a design is as follows:

if __name__ == "__main__":
    platform = ICE40HX1KBlinkEVNPlatform()
    # Add our Verilog file.
    platform.add_file("my_verilog_design.v", open("path_to_my_verilog_design.v")), do_program=True)

As this is Python, it's entirely possible to easily add e.g. all .v files in the current folder:

import glob

if __name__ == "__main__":
    platform = ICE40HX1KBlinkEVNPlatform()
    # Add all Verilog files in our working directory:
    for filename in glob.glob("*.v"): 
        platform.add_file(filename, open(filename)), do_program=True)