more docs

This commit is contained in:
Fischer Moseley 2023-08-03 09:49:40 -07:00
parent 6d3a32a988
commit 7340ccbbcb
4 changed files with 96 additions and 68 deletions

View File

@ -77,9 +77,6 @@ The data bus is designed for simplicity, and consists of five signals used to pe
Each core has a bus input and output port, so that cores can be daisy-chained together. When it receives an incoming bus transaction (signalled by `valid`), the core checks the address on the wire against its own memory space. If the address lies within the core, the core will perform the requested operation against its own memory space. In the case of a read, it places the data at that address on `data`, and in the case of a write, it copies the value of `data` to the specified location in memory. However, if the address lies outside of the memory of the core, then no operations are performed.
In all cases, the transaction is passed from the input port to the output port, regardless of if it . An example of a read and write transcation are shown below:
<img src="/assets/read_transaction.png" width="350"/>
<img src="/assets/write_transaction.png" width="350"/>

View File

@ -1,10 +1,103 @@
## Overview
Registers are a fundamental building block of digital hardware. Registers store values as they move throughout the FPGA, and are operated on by the logic placed onboard the chip. Interfacing with this logic in an intuitive manner is Mantas primary design objective, and as a result it includes an Input/Output (IO) core to directly measure and control arbitrary signals on the FPGA. This is done by routing them to registers, which are then exposed to the host over Mantas internal bus.
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 a set of inputs and outputs of arbitrary width, and then set values to the outputs and read values from the inputs.
This is a very, very simple task - and while configuration is straightforward, there are a few caveats. More on both topics below:
## Configuration
Just like the rest of the cores, the IO core is configured via an entry in a project's configuration file. This is easiest to show by example:
```yaml
---
the_muppets:
type: io
inputs:
kermit: 3
piggy: 1
animal: 38
scooter: 4
outputs:
fozzy: 1
gonzo: 3
```
This configuration specifies four parameters:
- `name`: 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.
- `type`: This signals to the parser 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`: 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.
- `outputs`: This lists all outputs from the host machine to the FPGA fabric. Signals in this list may be written by the host, but ___can___ also be read from, and doing so returns the value last written to the register.
Lastly, the name of the core and the names of the 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. As an example, here's an instance of what the autogenerated module would look like for the configuration above:
```verilog
manta manta_inst (
.clk(clk),
.rx(rx),
.tx(tx),
.kermit(kermit),
.piggy(piggy),
.animal(animal),
.scooter(scooter),
.fozzy(fozzy),
.gonzo(gonzo));
```
## Python API
The IO core functionality is stored in the `Manta.IOCore` and `Manta.IOCoreProbe` classes in [src/manta/io_core/__init__.py](https://github.com/fischermoseley/manta/blob/main/src/manta/io_core/__init__.py), and it may be controlled with the two functions:
`Manta.IOCoreProbe.set(int, bool data)`
- [`int`, `bool`] _data_: The value to write to an output probe. May be signed or unsigned, but will raise an exception if the value is too large for the width of the port.
- _returns_: None
This method is blocking. When called it will dispatch a request to the FPGA, and wait until a response has been receieved.
---
`Manta.IOCoreProbe.set()`
- _returns_: The value of an input or output probe. In the case of an output probe, the value returned will be the last value written to the probe.
This method is blocking. When called it will dispatch a request to the FPGA, and wait until a response has been receieved.
---
### Example
A small example is shown below, using the [example configuration](#configuration) above. More extensive examples can also be found in the repository's [examples/](https://github.com/fischermoseley/manta/tree/main/examples) folder.
```python
>>> import Manta
>>> m = Manta
>>> m.my_io_core.fozzy.set(True)
>>> m.my_io_core.gonzo.set(4)
>>> m.my_io_core.scooter.get()
5
```
## Caveats
While the IO core performs a very, very simple task, it carries a few caveats.
- First, __it's not instantaneous__. Manta has designed to be as fast as possible, but setting and querying registers relies on passing messages between the host and FPGA, which is slow relative to FPGA clock speeds! If you're trying to set values in your design with cycle-accurate timing, this will not do that for you. However, the [Logic Analyzer's playback feature](./logic_analyzer.md#playback) might be helpful.
- Second, __the API methods are blocking__, and will wait for a response from the FPGA before resuming program execution. Depending on your application, you might want to run your IO Core operations in a seperate thread, but you can also decrease the execution time by using a faster interface between the host and FPGA. This means using a higher UART baudrate, or using Ethernet.
## How It Works
This is done with the architecture shown below:
<img src="/assets/io_core_block_diagram.png" alt="drawing" width="400"/>
This is done with the architecture shown in Figure \ref{io_core_block_diagram}. A series of connections are made to the users logic. These are called \textit{probes}, and each may be either an input or an output. If the probe is an input, then its value is taken from the users logic, and stored in a register that may be read by the host machine. If the probe is an output, then its value is provided to the users logic from a register written to by the host. The widths of these probes is arbitrary, and is set by the user at compile-time.
. A series of connections are made to the users logic. These are called \textit{probes}, and each may be either an input or an output. If the probe is an input, then its value is taken from the users logic, and stored in a register that may be read by the host machine. If the probe is an output, then its value is provided to the users logic from a register written to by the host. The widths of these probes is arbitrary, and is set by the user at compile-time.
However, the connection between these probes and the users logic is not direct. The state of each probe is buffered, and the buffers are updated when a \textit{strobe} register within the IO core is set by the host machine. During this update, new values for output probes are provided to user logic, and new values for input probes are read from user logic.
@ -14,66 +107,4 @@ This can easily cause data corruption if the signals were unbuffered. For instan
Buffering the probes mitigates these issues, but slightly modifies the way the host machine uses the core. When the host wishes to read from an input probe, it will set and then clear the strobe register, which pulls the current value of the probe into the buffer. The host then reads from buffer, which is guaranteed to not change as it is being read from. Writing to an output probe is done in much the same way. The host writes a new value to the buffer, which is flushed out to the users logic when the strobe register is set and cleared. This updates every bit in the output probe all at once, guaranteeing the user logic does not observe any intermediate values.
These buffers also provide a convenient location to perform clock domain crossing. Each buffer is essentially a two flip-flop synchronizer, which allows the IO core to interact with user logic on a different clock than Mantas internal bus.
% \begin{figure}[h!]
% \centering
% \includegraphics[width=0.8\textwidth]{io_core_memory_map}
% \caption{Memory map of an IO core.}
% \label{io_core_memory_map}
% \end{figure}
!!! warning "This isn't magic!"
While the IO Core has been designed to be as fast as possible,
setting and querying registers is nowhere near instantaneous!
If you're trying to set values in your design with cycle-accurate
timing, this will not do that for you.
## Options
- `inputs`
- `outputs`
## Example Configuration
```yaml
---
the_muppets_io_core:
type: io
inputs:
kermit: 3
piggy: 68
animal: 1
scooter: 4
outputs:
fozzy: 1
gonzo: 3
uart:
baudrate: 115200
port: "/dev/tty.usbserial-2102926963071"
```
## Python API
The caveat being that Manta is limited by the bandwidth of PySerial, which is limited by your operating system and system hardware. These calls may take significant time to complete, and __they are blocking__. More details can be found in the API reference.
### Example
```python
>>> import Manta
>>> m = Manta('manta.yaml')
>>> m.my_io_core.fozzy.set(True)
>>> m.my_io_core.gonzo.set(4)
>>> m.my_io_core.scooter.get()
5
```
## How It Works
These buffers also provide a convenient location to perform clock domain crossing. Each buffer is essentially a two flip-flop synchronizer, which allows the IO core to interact with user logic on a different clock than Mantas internal bus.