neorv32/docs/datasheet/soc_dma.adoc

156 lines
9.7 KiB
Plaintext
Raw Permalink Normal View History

2024-02-24 08:25:27 +00:00
<<<
:sectnums:
==== Direct Memory Access Controller (DMA)
[cols="<3,<3,<4"]
[frame="topbot",grid="none"]
|=======================
| Hardware source file(s): | neorv32_dma.vhd |
| Software driver file(s): | neorv32_dma.c |
| | neorv32_dma.h |
| Top entity port: | none |
| Configuration generics: | `IO_DMA_EN` | implement DMA when `true`
| CPU interrupts: | fast IRQ channel 10 | DMA transfer done (see <<_processor_interrupts>>)
|=======================
**Overview**
The NEORV32 DMA provides a small-scale scatter/gather direct memory access controller that allows to transfer and
modify data independently of the CPU. A single read/write transfer channel is implemented that is configured via
memory-mapped registers. a configured transfer can either be triggered manually or by a programmable CPU FIRQ interrupt
(see <<_neorv32_specific_fast_interrupt_requests>>).
The DMA is connected to the central processor-internal bus system (see section <<_address_space>>) and can access the same
address space as the CPU core. It uses _interleaving mode_ accessing the central processor bus only if the CPU does not
currently request and bus access.
The controller can handle different data quantities (e.g. read bytes and write them back as sign-extend words) and can
also change the Endianness of data while transferring.
.DMA Demo Program
[TIP]
A DMA example program can be found in `sw/example/demo_dma`.
**Theory of Operation**
The DMA provides four memory-mapped interface registers: A status and control register `CTRL` and three registers for
configuring the actual DMA transfer. The base address of the source data is programmed via the `SRC_BASE` register.
Vice versa, the base address of the destination data is programmed via the `DST_BASE`. The third configuration register
`TTYPE` is use to configure the actual transfer type and the number of elements to transfer.
The DMA is enabled by setting the `DMA_CTRL_EN` bit of the control register. Manual trigger mode (i.e. the DMA transfer is
triggered by writing to the `TTYPE` register) is selected if `DMA_CTRL_AUTO` is cleared. Alternatively, the DMA transfer can
be triggered by a processor internal FIRQ signal if `DMA_CTRL_AUTO` is set (see section below).
The DMA uses a load-modify-write data transfer process. Data is read from the bus system, internally modified and then written
back to the bus system. This combination is implemented as an atomic progress, so canceling the current transfer by clearing the
`DMA_CTRL_EN` bit will stop the DMA right after the current load-modify-write operation.
If the DMA controller detects a bus error during operation, it will set either the `DMA_CTRL_ERROR_RD` (error during
last read access) or `DMA_CTRL_ERROR_WR` (error during last write access) and will terminate the current transfer.
Software can read the `SRC_BASE` or `DST_BASE` register to retrieve the address that caused the according error.
Alternatively, software can read back the `NUM` bits of the control register to determine the index of the element
that caused the error. The error bits are automatically cleared when starting a new transfer.
When the `DMA_CTRL_DONE` flag is set the DMA has actually executed a transfer. However, the `DMA_CTRL_ERROR_*` flags
should also be checked to verify that the executed transfer completed without errors. The `DMA_CTRL_DONE` flag is
automatically cleared when writing the `CTRL` register.
.DMA Access Privilege Level
[WARNING]
Transactions performed by the DMA are executed as bus transactions with elevated **machine-mode** privilege level.
Additionally, all physical memory protection rules (<<_pmp_isa_extension>>) defined by the CPU are **bypassed**.
**Transfer Configuration**
If the DMA is set to **manual trigger mode** (`DMA_CTRL_AUTO` = 0) writing the `TTRIG` register will start the
programmed DMA transfer. Once started, the DMA will read one data quantity from the source address, processes it internally
and then will write it back to the destination address. The `DMA_TTYPE_NUM` bits of the `TTYPE` register define how many
times this process is repeated by specifying the number of elements to transfer.
Optionally, the source and/or destination addresses can be increments according to the data quantities
automatically by setting the according `DMA_TTYPE_SRC_INC` and/or `DMA_TTYPE_DST_INC` bit.
Four different transfer quantities are available, which are configured via the `DMA_TTYPE_QSEL` bits:
* `00`: Read source data as byte, write destination data as byte
* `01`: Read source data as byte, write destination data as zero-extended word
* `10`: Read source data as byte, write destination data as sign-extended word
* `11`: Read source data as word, write destination data as word
Optionally, the DMA controller can automatically convert Endianness of the transferred data if the `DMA_TTYPE_ENDIAN`
bit is set.
.Address Alignment
[IMPORTANT]
Make sure to align the source and destination base addresses to the according transfer data quantities. For instance,
word-to-word transfers require that the two LSB of `SRC_BASE` and `DST_BASE` are cleared.
.Writing to IO Device
[IMPORTANT]
When writing data to IO / peripheral devices (for example to the <<_cyclic_redundancy_check_crc>>) the destination
data quantity has to be set to **word** (32-bit) since all IO registers can only be written in full 32-bit word mode.
**Automatic Trigger**
As an alternative to the manual trigger mode, the DMA can be configured to **automatic trigger mode** starting a pre-configured
transfer if a specific processor-internal peripheral issues an interrupt request. The automatic trigger mode is enabled by
setting the `CTRL` register's `DMA_CTRL_AUTO` bit. In this configuration _no_ transfer is started when writing to the DMA's
`TTYPE` register.
The actual trigger is configured via the control register `DMA_CTRL_FIRQ_MASK`. These bits reflect the state of the CPU's
<<_mip>> CSR showing any pending fast interrupt requests (for a full list see <<_neorv32_specific_fast_interrupt_requests>>).
The same bit definitions/locations as for the <<_mip>> and <<_mie>> CPU CSRs are used.
If any of the enabled sources issues an interrupt the DMA will start the pre-configured transfer (note that all enabled
sources are logically OR-ed).
.FIRQ Trigger
[NOTE]
The DMA transfer will start if a **rising edge** is detected on _any_ of the enabled FIRQ source channels.
**Memory Barrier / Fence Operation**
Optionally, the DMA can issue a FENCE request to the downstream memory system when a transfer has been completed
without errors. This can be used to re-sync caches (flush and reload) and buffers to maintain data coherency.
This automatic fencing is enabled by the setting the control register's `DMA_CTRL_FENCE` bit.
**DMA Interrupt**
The DMA features a single CPU interrupt that is triggered when the programmed transfer has completed. This
interrupt is also triggered if the DMA encounters a bus error during operation. An active DMA interrupt has to be
explicitly cleared again by writing zero to the according <<_mip>> CSR bit.
**Register Map**
.DMA Register Map (`struct NEORV32_DMA`)
[cols="<2,<1,<4,^1,<7"]
[options="header",grid="all"]
|=======================
| Address | Name [C] | Bit(s), Name [C] | R/W | Function
.10+<| `0xffffed00` .10+<| `CTRL` <|`0` `DMA_CTRL_EN` ^| r/w <| DMA module enable
<|`1` `DMA_CTRL_AUTO` ^| r/w <| Enable automatic mode (FIRQ-triggered)
<|`2` `DMA_CTRL_FENCE` ^| r/w <| Issue a downstream FENCE operation when DMA transfer completes (without errors)
<|`7:3` _reserved_ ^| r/- <| reserved, read as zero
<|`8` `DMA_CTRL_ERROR_RD` ^| r/- <| Error during read access, clears when starting a new transfer
<|`9` `DMA_CTRL_ERROR_WR` ^| r/- <| Error during write access, clears when starting a new transfer
<|`10` `DMA_CTRL_BUSY` ^| r/- <| DMA transfer in progress
<|`11` `DMA_CTRL_DONE` ^| r/c <| Set if a transfer was executed; auto-clears on write-access
<|`15:12` _reserved_ ^| r/- <| reserved, read as zero
<|`31:16` `DMA_CTRL_FIRQ_MASK_MSB : DMA_CTRL_FIRQ_MASK_LSB` ^| r/w <| FIRQ trigger mask (same bits as in <<_mip>>)
| `0xffffed04` | `SRC_BASE` |`31:0` | r/w | Source base address (shows the last-accessed source address when read)
| `0xffffed08` | `DST_BASE` |`31:0` | r/w | Destination base address (shows the last-accessed destination address when read)
.6+<| `0xffffed0c` .6+<| `TTYPE` <|`23:0` `DMA_TTYPE_NUM_MSB : DMA_TTYPE_NUM_LSB` ^| r/w <| Number of elements to transfer (shows the last-transferred element index when read)
<|`26:24` _reserved_ ^| r/- <| reserved, read as zero
<|`28:27` `DMA_TTYPE_QSEL_MSB : DMA_TTYPE_QSEL_LSB` ^| r/w <| Source data quantity select (`00` = byte, `01` = half-word, `10` = word)
<|`29` `DMA_TTYPE_SRC_INC` ^| r/w <| Constant (`0`) or incrementing (`1`) source address
<|`30` `DMA_TTYPE_DST_INC` ^| r/w <| Constant (`0`) or incrementing (`1`) destination address
<|`31` `DMA_TTYPE_ENDIAN` ^| r/w <| Swap Endianness when set
|=======================