Getting Started
Overview
Regardless of if you use it in a traditional Verilog-based workflow or natively in an Amaranth design, using Manta consists of the following steps:
- Specify and configure the cores you wish to include in your FPGA design.
- Generate RTL for these cores, and include this RTL in your design.
- Build the design, and upload it to your device.
- Operate the cores from the host machine.
Manta’s cores are small, configurable blocks that each provide some functionality. More information on each core can be found on their respective documentation pages. Manta supports an arbitrary amount of unique cores, limited only by the available resources of your device.
Usage in Traditional Verilog-Based Workflows
Although modern HDLs are rising in popularity, most existing FPGA designs use a Verilog-based workflow. This consists of synthesizing a number of Verilog files into a single design. Manta can easily be used in such a workflow, as described below:
- The cores and interface used to communicate with them is specified in a configuration file, written in YAML. This file is typically named
manta.yaml
. - A Verilog file containing the cores specified in the configuration file is generated. This is most often done at the command line with the
manta gen
command (for example,manta gen manta.yaml manta.v
) but can also be done with thegenerate_verilog
method of theManta
Python class. This file contains the definition of a Verilog module namedmanta
, which includes all the cores specified in the configuration file. - This
manta
module is instantiated in your design, and the signals to each core are connected to your logic. Connections are also made to the signals of the interface specified in the configuration file. Themanta inst
command can also be used to generate a Verilog instantiation that can be copy-pasted into your source. - After the design is built and uploaded to the FPGA, the cores are operated from the host machine. Each core exposes a Python API containing methods that can be invoked from a Python script. These are described in great detail in the documentation for each core.
VHDL works too!
If your FPGA design is VHDL-based, fret not! Most synthesis tools support mixed-language projects, and will happily ingest both a Verilog-based Manta module inside of a VHDL-based design. Just take care to ensure that interfaces match between the VHDL and Verilog modules.
Example
A minimal example of a manta.yaml
file may be observed below:
cores:
my_io_core:
type: io
inputs:
my_input_signal: 6
outputs:
my_output_signal: 12
uart:
port: "auto"
baudrate: 3e6
clock_freq: 100e6
This includes a single IO core in the manta
module, which communicates with the host machine over a 3Mbaud UART link. Instantiating this core in your design might look like the following, as generated by manta inst
:
manta manta_inst (
.clk(clk),
.rst(rst),
.rx(rx),
.tx(tx),
.my_input_signal(my_input_signal),
.my_output_signal(my_output_signal));
Reset is active high!
The Manta instance will reset while rst
is held high. If you want to share reset logic with an active low reset signal (for example, rst_n
), be sure to invert it first.
More examples of Verilog-based designs can be found in the examples/verilog folder of the repo.
Usage in Amaranth Designs
Since Manta itself is written in Amaranth, it’s very easy to use Manta in an Amaranth design. In this flow, the RTL build and generation are offloaded to Amaranth’s build system, such that you need only to configure and operate the core, which is done from Python.
Configuration is done by creating a Manta object in Python, adding cores to it, and adding it to your design as an Amaranth submodule:
from amaranth import *
from manta import *
class ExampleDesign(Elaborateable):
def __init__(self):
self.manta = Manta()
self.manta.interface = UARTInterface("auto", 2e6, 12e6)
self.manta.cores.my_io_core = IOCore(inputs=[], outputs=[])
def elaborate(self, platform):
m = Module()
m.submodules.manta = self.manta
return m
def operate(self):
self.manta.cores.my_io_core.set_probe("foo", 2)
self.manta.cores.my_io_core.get_probe(bar)
Using this ExampleDesign
in the configure-build-operate flow might look like the following:
>>> from amaranth_boards.icestick import ICEStickPlatform
>>> design = ExampleDesign()
>>> ICEStickPlatform.build(design, do_program=True)
>>> design.operate()
>>> design.manta.my_io_core.get_probe(bar)
Here, Amaranth’s board definitions and build system are being used to build and program an iCEstick development board. More examples of Amaranth-based designs can be found in the examples/amaranth folder of the repo.
Usage with older versions of Amaranth
Unfortunately, Manta has a hard dependency on Amaranth 0.5 (due to this bugfix), and it may not work correctly in projects built upon older versions of Amaranth. If this is the case for your project, you may need to generate a standalone Verilog module from the Verilog-based flow, and then include in your project as an Instance. Alternatively, you may upgrade your project’s version of Amaranth by following the migration guides.
Adding Manta as an Instance Variable
It’s worth noting that this usage represents a slight departure from typical Amaranth style. Typically, submodules would be defined and added to a Module
in the elaborate
method. Here, the Manta module is instead defined as an instance variable in the __init__
function, and then later added as a submodule in the elaborate
method.
This is necessary as the Manta
object contains both HDL needed for build and methods for operating the cores. Saving the Manta
instance in the class and re-using it later removes the need to define and configure separate instances when elaborating and operating the cores.
Lastly, including manta
as an instance variable also allows it to be directly accessed from an interpreter, as shown above. This allows for a more interactive debugging session, as the definition of the operate
method doesn’t have to change when you wish to use Manta’s cores differently.