IO Core
Overview
Registers are a fundamental building block of digital hardware, and the IO core provides a simple way of interacting with them from the host machine. It allows you to define inputs and outputs of arbitrary width, and then write values to the outputs and read values from the inputs.
This is a very, very simple task - however it's surprisingly useful in practice. Both the Use Cases page and the repository's examples folder contain examples of the IO Core for your reference.
It's not instantaneous!
If you're trying to read or write values in your design with cycle-accurate timing, this will not do that for you. The time taken by Python to evaluate an expression is not constant, nor is the amount of time it takes for your OS to put bytes on the wire. If you need cycle-accurate timing, the Logic Analyzer Core and it's Playback feature may be helpful.
Configuration
As explained in the getting started page, the IO Core must be configured and included in the FPGA design before it can be operated. Configuration is performed differently depending on if you're using a traditional Verilog-based workflow, or if you're building an Amaranth-native design.
Verilog-Based Workflows
The IO Core is used by adding an entry in a cores
section of a configuration file. This is best shown by example:
---
cores:
my_io_core:
type: io
inputs:
kermit: 3
piggy: 1
animal: 38
scooter:
width: 4
initial_value: 13
outputs:
fozzy: 1
gonzo: 3
name
(required): The name of the IO core, which is used when working with the API.type
(required): This denotes that this is an IO core. All cores contain atype
field, which must be set toio
to be recognized as an IO core.inputs
(optional): This lists all inputs from from the FPGA fabric to the host machine. Signals in this list may be read by the host, but cannot be written to. This parameter is somewhat optional as an IO Core must have at least one probe, but it need not be an input.outputs
(optional): This lists all outputs from the host machine to the FPGA fabric. Signals in this list are usually written to by the host, but they can also be read from. Doing so returns the value last written to the register. This parameter is somewhat optional as an IO Core must have at least one probe, but it need not be an output.initial_value
(optional): This sets an initial value for an output probe to take after the FPGA powers on. This is done with aninitial
statement in Manta's Verilog, and is independent of the input clock or resets elsewhere in the FPGA. This parameter is optional, and defaults to zero.
Name things carefully!
The names of the core and its probes are referenced in the autogenerated Verilog. This means that while the names can be arbitrary, they must be unique within your project and not contain any characters that your synthesis engine won't appreciate.
Amaranth-Native Designs
Since Amaranth modules are Python objects, the configuration of the IO Core is given by the arguments given during initialization. See the documentation for the IOCore
class constructor below, as well as the Amaranth examples in the repo.
Operation
Regardless of the technique you used to configure your IO Core, it is operated using the set_probe()
and get_probe()
methods. Documentation for these methods is available below.
These methods are members of the IOCore
class, so if you're using Manta in a Verilog-based workflow, you'll first need to obtain a Manta
object that contains an IOCore
member. This is done with Manta.from_config()
, as shown in the Verilog examples.
Python API Documentation
manta.IOCore
A synthesizable module for setting and getting the values of registers of arbitrary size.
Create an IO Core, with the given input and output probes.
This function is the main mechanism for configuring an IO Core in an Amaranth-native design.
Parameters:
-
inputs
(Optional[List[Signal]]
, default:[]
) –A list of Amaranth Signals to use as inputs. Defaults to an empty list. This parameter is somewhat optional as an IO Core must have at least one probe, but it need not be an input.
-
outputs
(Optional[List[Signal]]
, default:[]
) –A list of Amaranth Signals to use as outputs. Defaults to an empty list. This parameter is somewhat optional as an IO Core must have at least one probe, but it need not be an output.
get_probe
Get the value of an input or output probe on the FPGA.
If called on an output probe, this function will return the last value written to the output probe. If no value has been written to the output probe, then it will return the probe's initial value. This method is blocking.
Parameters:
-
probe
(str | Signal
) –The probe to get the value of. This may be either a string containing the name of the probe, or the Amaranth Signal representing the probe itself. Strings are typically used in Verilog-based workflows, and Amaranth Signals are typically used in Amaranth-native designs.
Returns:
-
value
(int
) –The value of the probe, interpreted as an unsigned integer.
Raises:
-
ValueError
–The probe was not found in the IO Core, or many probes were found with the same name.
set_probe
Set the value of an output probe on the FPGA.
This method is blocking.
Parameters:
-
probe
(str | Signal
) –The output probe to set the value of. This may be either a string containing the name of the probe, or the Amaranth Signal representing the probe itself. Strings are typically used in Verilog-based workflows, and Amaranth Signals are typically used in Amaranth-native designs.
-
value
(int
) –The value to set the probe to. This may be either positive or negative, but must fit within the width of the probe.
Returns:
-
–
None
Raises:
-
ValueError
–The probe was not found to be an output of the IO Core, or many probes were found with the same name.