docs: update layout and references for LogicAnalyzerCore docs

This commit is contained in:
Fischer Moseley 2024-09-06 08:09:09 -06:00
parent b4ef5502c1
commit b83cb0b0a2
7 changed files with 112 additions and 31 deletions

View File

@ -36,7 +36,7 @@ my_io_core:
```
Inside this configuration, the following parameters may be set:
- `name` _(required)_: The name of the IO core. This name is used to reference the core when working with the API, and can be whatever you'd like.
- `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 a `type` field, which must be set to `io` 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.

View File

@ -1,12 +1,18 @@
## Overview
The Logic Analyzer core allows for debugging logic by capturing a set of digital signals to memory. This is done in response to a ___trigger___ condition, which starts a ___capture___, which continues until the onboard memory is full. The resulting data is then read out to the host machine, and can be displayed as a waveform, exported as a CSV file, or turned into a synthesizable playback module.
This is very similar to the behavior of a benchtop logic analyzer, but Manta's Logic Analyzer Core includes some extra features you may find useful. Both the [Use Cases](../use_cases) page and the repository's [examples](https://github.com/fischermoseley/manta/tree/main/examples) folder contain examples of the Logic Analyzer Core for your reference.
The Logic Analyzer core allows for debugging logic by capturing a set of digital signals to memory. This is done in response to a ___trigger___ condition, which starts the ___capture___, which continues until the onboard memory is full, and the resulting capture is then read out to the user.
While this is pretty much identical to the behavior of a benchtop logic analyzer, Manta has a few tricks up its sleeve that you may find useful! These are described below:
## Configuration
Just like the rest of the cores, the Logic Analyzer core is configured via an entry in a project's configuration file. This is easiest to show by example:
As explained in the [getting started](../getting_started) page, the Logic Analyzer 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
Cores are configured with an entry in a project's configuration file when using a Verilog-based workflow, and the Logic Analyzer Core is no different. This is best shown by example:
```yaml
---
@ -25,17 +31,16 @@ cores:
- moe RISING
- curly FALLING
```
Inside this configuration, the following parameters may be set:
There's a few parameters that get configured here, including:
- `name`_(required)_: The name of the Logic Analyzer core, which is used when working with the API.
- `type`_(required)_: This denotes that this is a Logic Analyzer core. All cores contain a `type` field, which must be set to `logic_analyzer` to be recognized as an Logic Analyzer core.
- `sample_depth`_(required)_: The number of samples saved in the capture. A larger sample depth will use more FPGA resources, but will show what the probes are doing over a longer time interval.
- `probes` _(required)_: The signals in your logic that the Logic Analyzer connects to. Each probe is specified with a name and a width.
- `name`: The name of the Logic Analyzer core. This name is used to reference the core when working with the API, and can be whatever you'd like.
- `type`: This denotes that this is a Logic Analyzer core. All cores contain a `type` field, which must be set to `logic_analyzer` to be recognized as an Logic Analyzer core.
!!! warning "Name things carefully!"
### Sample Depth
This refers to the number of samples saved in the capture, and is set with the `sample_depth` entry in the config file. A larger sample depth will use more resources, but show what your probes are doing over a longer time.
### Probes
Probes are the signals in your logic that the Logic Analyzer connects to, and are specified in the `probes` entry of the config file. Each probe requires both a name and a width to be specified. These names can be whatever you'd like, however they are referenced in the autogenerated Verilog - so don't use something your synthesis engine won't appreciate.
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.
### Triggers
Triggers are the conditions that your logic must meet in order to start a capture, and they're specified under the `triggers` entry in the config file. Manta's triggers are reprogrammable, meaning you don't need to rebuild your source code to change the trigger condition - just updating the configuration file is enough. If multiple triggers are provided, any one trigger being met will trigger the entire core.
@ -64,21 +69,20 @@ Each individual trigger is specified with the following structure:
- __argument__: A constant to compare against, if the operation specified requires one. On the FPGA, the argument will have just as many bits as the probe width.
Lastly, if you're not able to express your desired trigger condition in terms of the operators above, fear not! You can also specify an `external_trigger: true` entry in the config file, which exposes an input on Manta's top level for your own trigger.
### Trigger Position (optional)
Sometimes, you care more about what happens before a trigger is met than afterwards, or vice versa. To accommodate this, the logic analyzer has an optional _Trigger Position_ parameter, which sets when probe data is captured relative to the trigger condition being met. This is specified with the `trigger_position` entry in the configuration file, which sets how many samples to save prior to the trigger condition occurring. This is similar to a "holdoff" option on a traditional oscilloscope or logic analyzer.
### Trigger Location (optional)
Sometimes, you care more about what happens before a trigger is met than afterwards, or vice versa. To accommodate this, the logic analyzer has an optional _Trigger Location_ parameter, which sets when probe data is captured relative to the trigger condition being met. This is specified with the `trigger_position` entry in the configuration file, which sets how many samples to save prior to the trigger condition occurring. This is similar to a "holdoff" option on a traditional oscilloscope or logic analyzer.
If `trigger_position` is not specified, Manta will default to centering the capture window around the trigger condition. This results in just as many samples before the trigger as after.
### Capture Modes (optional)
The logic analyzer has a few different ways of capturing data, which are represented by the _capture modes_ below:
### Trigger Modes (optional)
The logic analyzer has a few different ways of capturing data, which are represented by the _trigger modes_ below:
- __Single-Shot__: Once the trigger condition is met, record the value of the probes on every clock cycle in a continuous single shot.
- __Incremental__: Record samples when the trigger condition is met, but __don't__ record the samples when the trigger condition is not met. This is super useful for applications like audio processing or memory controllers, where there are many system clock cycles between signals of interest.
- __Immediate__: Record the value of the probes on every clock cycle, beginning immediately, and regardless of if the trigger condition is met. This is useful for investigating cases where a trigger condition is never being met (such as latchup or deadlock conditions) or obtaining a random snapshot of the FPGA's state.
Most logic analyzers use a single-shot capture by default, so Manta will do the same if no `capture_mode` entry is provided in the project's configuration file.
Most logic analyzers use a single-shot capture by default, so Manta will do the same if no `trigger_mode` entry is provided in the project's configuration file.
## Usage
@ -115,6 +119,10 @@ This is useful for two situations in particular:
- _Sparse Sampling._ Sometimes designs will have a small number of inputs, but a huge amount of internal state. In situations like these, it may be more efficient to sample the inputs and simulate the logic, instead of directly sampling the state. For instance, debugging a misbehaving branch predictor in a CPU can be done by recording activity on the address and data busses and playing them back in simulation - which would use less FPGA resources than sampling the entire pattern history table.
## Python API
The Logic Analyzer core functionality is stored in the `Manta.LogicAnalyzerCore` class in [src/manta/logic_analyzer/\_\_init\_\_.py](https://github.com/fischermoseley/manta/blob/main/src/manta/logic_analyzer/__init__.py). This class contains methods for capturing data, exporting it as `.vcd`, `.v` or `.csv` files, or as a Python list.
## Python API Documentation
::: manta.LogicAnalyzerCore
::: manta.LogicAnalyzerCapture
::: manta.LogicAnalyzerPlayback

View File

@ -37,7 +37,7 @@ cores:
Inside this configuration, the following parameters may be set:
- `name`: The name of the Memory core. This name is used to reference the core when working with the API, and can be whatever you'd like.
- `name`: The name of the Memory core, which is used when working with the API.
- `type`: This denotes that this is a Memory core. All cores contain a `type` field, which must be set to `memory` to be recognized as an Memory core.
- `mode`: The mode for the Memory core to operate in. This must be one of `bidirectional`, `host_to_fpga`, or `fpga_to_host`. Bidirectional memories can be both read or written to by the host and FPGA, but they require the use of a True Dual Port RAM, which is not available on all platforms (most notably, the ice40). Host-to-fpga and fpga-to-host RAMs only require a Simple Dual Port RAM, which is available on nearly all platforms.
- `width`: The width of the Memory core, in bits.

37
doc/use_cases.md Normal file
View File

@ -0,0 +1,37 @@
Manta's capabilities are best reflected in its cores, for which a brief description of each is provided below:
### __Logic Analyzer Core__
_More details available on the [full documentation page](./logic_analyzer_core.md)._
This core captures a timeseries of digital signals from within the FPGA, much like a benchtop logic analyzer would. This captures data on the FPGA's native clock and presents it as a waveform, making it very useful for debugging logic cycle-by-cycle. This concept is very similar to the Xilinx [Integrated Logic Analyzer (ILA)](https://docs.xilinx.com/r/en-US/ug908-vivado-programming-debugging/ILA) and Intel [SignalTap](https://www.intel.com/content/www/us/en/docs/programmable/683819/21-3/logic-analyzer-introduction.html) utilities.
You may find this core useful for:
* _Verifying specification adherence for connected hardware_ - for instance, you're writing a S/PDIF decoder that works in simulation, but fails in hardware. The logic analyzer core can record a cycle-by-cycle capture of what's coming off the cable, letting you verify that your input signals are what you expect. Even better, Manta will let you play that capture back in your preferred simulator, letting you feed the exact same inputs to your module in simulation and check your logic.
* _Capturing arbitrary data_ - you're working on a DSP project, and you'd like to grab some test data from your onboard ADCs to start prototyping your signal processing with. Manta will grab that data, and export it for you.
### __I/O Core__
_More details available on the [full documentation page](./io_core.md)._
This core presents a series of user-accessbile registers to the FPGA fabric, which may be configured as either inputs or outputs. The value of an input register can be read off the FPGA by the host machine, and the value of an output register on the FPGA may be set by the host machine. This is handy for getting small amounts of information into and out of the FPGA, debugging, configuration, or experimentation. This concept is very similar to the Xilinx [Virtual IO](https://docs.xilinx.com/v/u/en-US/pg159-vio) and Intel [In-System Sources and Probes](https://www.intel.com/content/www/us/en/docs/programmable/683552/18-1/in-system-sources-and-probes-66964.html) tools.
You may find this core useful for:
* _Prototyping designs in Python, and incrementally migrating them to hardware_ - you're working on some real-time signal processing, but you want to prototype it with some sample data in Numpy before meticulously implementing everything in Verilog.
* _Making dashboards_ - you'd like to get some telemetry out of your existing FPGA design and display it nicely, but you don't want to implement an interface, design a packetization scheme, and write a library.
### __Memory Cores__
_More details available on the [full documentation page](./memory_core.md)._
This core creates a two-port block memory on the FPGA, and gives one port to the host machine, and the other to your logic on the FPGA. The width and depth of this block memory is configurable, allowing large chunks of arbitrarily-sized data to be shuffled onto and off of the FPGA by the host machine, via the Python API. This lets you establish a transport layer between the host and FPGA, that treats the data as exactly how it exists on the FPGA.
You may find this core useful for:
* _Moving data between a host and connected FPGA_ - you're working on a cool new machine learning accelerator, but you don't want to think about how to get training data and weights out of TensorFlow, and into your core.
* _Hand-tuning ROMs_ - you're designing a digital filter for a DSP project and would like to tune it in real-time, or you're developing a soft processor and want to upload program code without rebuilding a bitstream.

View File

@ -2,6 +2,7 @@ from manta.cli import main
from manta.ethernet import EthernetInterface
from manta.io_core import IOCore
from manta.logic_analyzer import LogicAnalyzerCore, TriggerModes
from manta.logic_analyzer.capture import LogicAnalyzerCapture, LogicAnalyzerPlayback
from manta.manta import Manta
from manta.memory_core import MemoryCore
from manta.uart import UARTInterface
@ -12,6 +13,8 @@ __all__ = [
"EthernetInterface",
"LogicAnalyzerCore",
"TriggerModes",
"LogicAnalyzerCapture",
"LogicAnalyzerPlayback",
"IOCore",
"MemoryCore",
]

View File

@ -16,12 +16,25 @@ class LogicAnalyzerCore(MantaCore):
Provides methods for generating synthesizable logic for the FPGA, as well
as methods for reading and writing the value of a register.
More information available in the online documentation at:
https://fischermoseley.github.io/manta/logic_analyzer_core/
"""
def __init__(self, sample_depth, probes):
"""
Create a Logic Analyzer Core with the given probes and sample depth.
This function is the main mechanism for configuring a Logic Analyzer in
an Amaranth-native design.
Args:
sample_depth (int): The number of samples saved in the capture. A
larger sample depth will use more FPGA resources, but will show
what the probes are doing over a longer time interval.
probes (List[Signal]): The signals in your logic that the Logic
Analyzer connects to. Each probe is specified with a name and
a width.
"""
self._sample_depth = sample_depth
self._probes = probes
self.trigger_location = sample_depth // 2
@ -211,9 +224,11 @@ class LogicAnalyzerCore(MantaCore):
def capture(self):
"""
Performs a capture, recording the state of all input probes to the
FPGA's memory, and then returns that as a LogicAnalyzerCapture class
on the host.
Performs a capture, recording the state of all probes to memory.
Returns:
capture (LogicAnalyzerCapture): A LogicAnalyzerCapture object
containing the capture and its metadata.
"""
print(" -> Resetting core...")

View File

@ -27,6 +27,14 @@ class LogicAnalyzerCapture:
def get_trace(self, name):
"""
Gets the value of a single probe over the capture.
Args:
name (str): The name of the probe.
Returns:
data (List[int]): The value of the probe at every timestep,
interpreted as an unsigned integer. Has length equal to
the `sample_depth` of the core that produced the capture.
"""
# Get index of probe with given name
@ -53,8 +61,13 @@ class LogicAnalyzerCapture:
def export_csv(self, path):
"""
Export the capture to a CSV file, containing the data of all probes in
the core.
Export the capture to a CSV file.
Args:
path (str): Path to the destination file.
Returns:
None
"""
names = [p.name for p in self._probes]
@ -74,8 +87,13 @@ class LogicAnalyzerCapture:
def export_vcd(self, path):
"""
Export the capture to a VCD file, containing the data of all probes in
the core.
Export the capture to a VCD file.
Args:
path (str): Path to the destination file.
Returns:
None
"""
from datetime import datetime