UART and console¶
The APBUART peripheral is the only path between guest software and the host. This page covers the four common patterns.
Default: stdout / stdin¶
By default, Emulator::initialize() wires a StdoutCharDevice into
the APBUart peripheral context. That means:
- Every byte the guest writes to the APBUart TDR appears on the
host's
stdout. - Every byte the host types is enqueued in the RX FIFO and made available via the RDR.
This is what lince-emu uses out of the box, with no configuration.
Capturing into a string¶
For test rigs and CI:
#include "tests/support/capturing_char_device.hpp"
auto cap = std::make_unique<lince::test_support::CapturingCharDevice>();
auto* ref = cap.get();
emu->set_character_device(std::move(cap));
emu->initialize();
emu->run_for(lince::SimTimeNs{2'000'000'000ULL});
std::string output = ref->captured();
REQUIRE(output.find("*** END OF TEST") != std::string::npos);
The CapturingCharDevice is part of the test support tree
(tests/support/); it is intentionally not part of
lince_defaults so production binaries do not link an unused
collector.
Suppressing output¶
If you don't care what the guest prints (e.g. you're benchmarking the core only), inject a sink:
class NullCharDevice : public lince::ICharacterDevice {
void transmit(uint8_t) override {}
bool receive(uint8_t&) override { return false; }
};
emu->set_character_device(std::make_unique<NullCharDevice>());
Output is discarded; RX is permanently empty. Cheaper than
/dev/null because the byte never leaves the APBUart.
Bridging to a real serial port¶
For interactive RTEMS sessions, redirect the UART through a host PTY:
class PtyCharDevice : public lince::ICharacterDevice {
public:
explicit PtyCharDevice(int fd) : fd_{fd} {}
void transmit(uint8_t b) override { ::write(fd_, &b, 1); }
bool receive(uint8_t& out) override {
return ::read(fd_, &out, 1) == 1;
}
private:
int fd_;
};
Pair this with posix_openpt / grantpt / unlockpt to get a PTY
your terminal can attach to (screen /dev/pts/N, etc.).
TX enable bit¶
A subtle hardware detail: the APBUart drops bytes silently unless
CTRL.TE = 1. RTEMS sets it during early boot and you typically don't
notice. But raw assembly tests must set it explicitly:
sethi %hi(0x80000108), %g1 ! CTRL register address
or %g1, %lo(0x80000108), %g1
mov 1, %g2
st %g2, [%g1] ! CTRL.TE = 1
This bit Decision 22 in Design decisions;
the test fixture tests/asm/hello_uart.S is the canonical example.
What the FIFO does (and doesn't) model¶
- RX FIFO: 8 entries, modelled with
std::queue<uint8_t>. Status bit 0 (DR, data ready) is set when non-empty; the queue is shared by the APBUart peripheral and the injectedICharacterDevice::receivehook. - TX FIFO: not modelled.
transmit()drains immediately to the injectedICharacterDevice. Status bit 31 (FA, FIFO available) always reads as 1 (Decision 18). - Baud rate, parity, stop bits: the registers exist but produce no observable timing change. RTEMS programs them and Lince stores the bytes; nothing else happens.
If you need a more accurate UART (cycle-accurate baud, hardware flow-control, framing errors), wrap the APBUart with a custom peripheral and intercept the data path. The architecture supports it without changing the core.