Skip to content

Demo DMA device

The reference custom peripheral lives under examples/demo-dma/. It performs a DMA read, XORs the payload against a configurable mask, writes the result back, and raises an IRQ. It is fully wired for MMIO, DMA, and interrupts — about 200 lines of C++.

What it does

  1. The CPU writes a source address, an XOR mask, and a destination address into MMIO registers.
  2. The CPU writes START to the command register.
  3. The device DMA-reads 4 bytes from the source address via IBusMaster.
  4. It XORs each byte against the corresponding byte of the mask in big-endian wire order.
  5. It DMA-writes the mutated bytes to the destination address.
  6. It sets the DONE flag in the status register and raises an IRQ.
  7. If any bus access fails, it sets ERROR and raises an IRQ.

Register map

The device occupies 16 bytes at its base address (default 0x80000800). All registers are 32-bit; non-word accesses return AlignmentError.

Offset Name Access Description
0x00 SOURCE R/W Physical address to read from
0x04 XORVAL R/W 32-bit XOR mask
0x08 DEST R/W Physical address to write to
0x0C CMD R/W Command / status register

Command / status register bits

Bit Name Access Description
0 START W1S Write 1 to trigger the DMA transaction
1 DONE R/W1C Set by hardware on completion; write 1 to clear
2 ERROR R/W1C Set by hardware on bus error; write 1 to clear

Writing 1 to DONE or ERROR clears that bit. When both are clear, the IRQ line is lowered.

DMA flow in detail

The transaction is driven by trigger_dma(), called when the CPU writes kCmdStart:

std::array<std::byte, sizeof(std::uint32_t)> buf{};

// 1. Read from guest memory
auto read_res = ctx_.bus->dma_read(PhysAddr{source_addr_}, buf);
if (!read_res) { set_error(); return; }

// 2. Endianness-safe byte-wise XOR
for (std::size_t i = 0; i < buf.size(); ++i) {
    const auto shift = static_cast<unsigned>((buf.size() - 1U - i) * 8U);
    const auto mask_byte = static_cast<std::uint8_t>((xor_val_ >> shift) & 0xFFU);
    buf[i] ^= std::byte{mask_byte};
}

// 3. Write back to guest memory
auto write_res = ctx_.bus->dma_write(PhysAddr{dest_addr_}, buf);
if (!write_res) { set_error(); return; }

// 4. Signal completion
status_ |= kCmdDone;
if (ctx_.irq) ctx_.irq->raise();

Why the byte-wise XOR matters

The original implementation memcpy'd the 4-byte buffer into a host uint32_t and XORed the whole word. That produced different results on little-endian vs big-endian hosts because the in-memory byte order of the uint32_t depends on the host. The corrected version operates on each byte individually, aligning the most-significant byte of xor_val_ with the byte at addr+0 (the big-endian wire order). This makes the result identical to what a SPARC ld / xor / st sequence would produce (Decision 35).

IRQ semantics

The device uses a level-sensitive interrupt line:

  • Success: DONE set, ERROR clear → raise().
  • Failure: ERROR set, DONE clear → raise().
  • Reset: lower().
  • Clear: Writing 1 to DONE clears it. If both DONE and ERROR are now clear, lower() is called.

The runtime's IrqBridge translates raise() / lower() into the correct IrqMP::external_assert / external_clear bit.

Building and running the example

The demo device is not built by default. Enable the example target:

cmake -S . -B build -G Ninja -DLINCE_BUILD_EXAMPLES=ON
cmake --build build --target demo_dma_device

The integration test that exercises it is always built when tests are enabled:

./build/tests/lince_tests "[integration]" "DemoDmaDevice"

There are two test cases:

  1. Happy path: A payload 0xCAFEBABE is placed in RAM at 0x40001000, the device is programmed with XOR mask 0x12345678, and the result read back from 0x40001100 is verified as 0xCAFEBABE ^ 0x12345678.
  2. Error path: The source address is set to an unmapped location (0xDEAD0000). The test asserts that ERROR is set and DONE is not.

Source files

File Purpose
examples/demo-dma/demo_dma_device.hpp Class declaration and register constants
examples/demo-dma/demo_dma_device.cpp Implementation
examples/demo-dma/README.md One-page summary
tests/integration/test_demo_dma_device.cpp End-to-end integration tests