DMA Controller
16.1 Overview
The DMA (Direct Memory Access) Controller enables the host CPU to initiate efficient data transfers anywhere within the 22-bit (4MB) physical address space.
The transfers are described by a 22-bit address that points to an array of descriptor. Each descriptor defines a source, destination, and size for an individual transfer, allowing multiple sequential operations without further CPU intervention.
The DMA uses Z80-style bus arbitration signals (BUS_REQ and BUS_ACK) to take control of the system bus during transfers. This means the CPU is temporarily put on hold while the DMA performs data movement, ensuring safe and atomic block transfers.
This simple yet effective design offloads data movement tasks from the CPU, improving overall system performance while maintaining straightforward configuration.
Like other controllers, the DMA Controller can be mapped into the device bank, starting at address 0xA0 on the I/O port. Its device index is 4.
16.2 Decscriptors
Organization
The DMA Controller relies on an array of descriptors located anywhere in the 4MB memory to define each transfer operation.
Each descriptor specifies the source and destination addresses, the transfer length, and additional control flags. It has a size of 12 bytes.
In C, a single descriptor can be represented as:
struct dma_descriptor_t {
uint16_t rd_addr_lo; // Source address low 16 bits
uint8_t rd_addr_hi; // Source address high 6 bits (within 22-bit space)
uint16_t wr_addr_lo; // Destination address low 16 bits
uint8_t wr_addr_hi; // Destination address high 6 bits (within 22-bit space)
uint16_t length; // Number of bytes to transfer
uint8_t flags; // Control flags for the transfer
uint8_t reserved[3]; // Reserved for padding or future use
};
Where uint16_t represents an unsigned 16-bit value and uint8_t represents an unsigned 8-bit value.
These descriptors can be arranged consecutively in an array in memory. The DMA Controller processes each descriptor in order, performing the specified transfers one by one, until it encounters a descriptor with the last flag set. At that point, the DMA transfer sequence completes. As such, only the first descriptor's physical address (22-bit) shall be given to the DMA controller via the DESC_ADDR0 (LSB), DESC_ADDR1 and DESC_ADDR2 (MSB) registers.
This design allows batching multiple transfers efficiently without further CPU intervention.
Flags
The flags field in each DMA descriptor is 5 bits wide and controls the behavior of the transfer:
-
Bit 0 (
last): Set to1if this descriptor is the last in the array, signaling the DMA Controller to stop after completing this transfer. -
Bits 1-2 (
rd_op): Specify the arithmetic operation applied to the read/source address after each byte is transferred. -
Bits 3-4 (
wr_op): Specify the arithmetic operation applied to the write/destination address after each byte is transferred.
The operations are encoded as follows:
| Operation Code | Behavior |
|---|---|
| 0 | Increment |
| 1 | Decrement |
| 2 | No operation (address remains unchanged) |
| 3 | Reserved (should not be used) |
These operations enable flexible data transfer patterns, such as copying memory forwards, backwards, or writing/reading repeatedly to/from a fixed location.
16.3 Transfer Frequency
The DMA Controller is clocked by the global 50 MHz system clock. As such, both read and write operations during a DMA transfer are synchronized to this clock, which corresponds to a 20ns period.
Since this timing is typically too fast for many components in an 8-bit system, the controller provides configurable wait states to slow down memory transactions. Each wait state adds an additional 20ns delay, giving external components more time to output or receive the data being transferred.
These wait states are specified using the CLK_DIV register:
- The lower nibble (4 bits),
READ_WAIT, sets the number of wait states inserted during memory reads by the DMA Controller. - The upper nibble (4 bits),
WRITE_WAITsets the number of wait states inserted during memory writes by the DMA Controller.
Adjusting these wait states ensures reliable data transfers by accommodating slower memory or peripheral timings.
The READ_WAIT value also determines the speed at which the DMA Controller reads descriptors from memory. It is not possible to set different wait states for reading descriptors and performing the actual data transfer — both use the same READ_WAIT setting.
⚠️ Although a single clock cycle (20 ns) is sufficient to write to any part of the video board memory, it is recommended to configure at least 1 wait state to avoid potential internal write conflicts.
16.4 Transfer Process
To perform a DMA transfer, the steps are as follows:
-
Set the DMA wait states (Optinal)
- Choose the desired wait states for reads and writes by setting the
CLK_DIVregister.
- Choose the desired wait states for reads and writes by setting the
-
Prepare the descriptor(s)
- Write a 12-byte structure as described above, with the source address, the destination address, and the number of bytes that needs to be transfered.
- Repeat this operation as much as needed, and mark the last descriptor with the
lastbit inflags. - Write the first descriptor physical address (22-bit) to registers
DESC_ADDR0(LSB),DESC_ADDR1andDESC_ADDR2(MSB) registers.
-
Start the transfer(s)
- Set the
STARTbit in theCTRLregister. - The CPU should be halted automatically after pulling the
BUS_ACKline low.
- Set the
-
Done
- As soon as the CPU resumes executing code, it means the transfer succeeded, no need to check any BUSY or IDLE flag.
16.5 Sequence Diagram
The following diagram explains the whole process that is involved for a DMA transfert. Of course it is simplified.
16.6 Registers Summary
| Name | Description | Base-relative address | Access |
| CTRL | DMA control register | 0x00 | WO |
| DESC_ADDR0 | Descriptor address byte 0 (LSB) | 0x01 | WO |
| DESC_ADDR1 | Descriptor address byte 1 | 0x02 | WO |
| DESC_ADDR2 | Descriptor address byte 2 (MSB) | 0x03 | WO |
| CLK_DIV | DMA wait-state configuration for memory bus access. Controls how many clock cycles to wait before completing a read or write. | 0x09 | RW |
16.7 Registers
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 1 | 0 | 1 | 0 | 1 | 1 | 0 |