Rigel is a C library for classic Amiga chipset and peripheral emulation.
The library provides a deterministic, single-threaded hardware-facing core. The host owns the CPU, memory map, ROM, and presentation layer; Rigel owns the chipset behavior.
cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure
./build/test_blitter # run a single testOptional: the Musashi integration harness — a real 68k machine around Rigel, used for timing verification and for booting Kickstart against the chipset.
./run.shThat fetches and patches Musashi, builds the harness and the launcher, and
opens a TUI to pick the ROM, the disk and the machine options. Drop images into
media/roms and media/disks (created on first run), or point MEDIA_DIR
somewhere else.
./run.sh --headless --frames 600 --screenshot boot.ppm # options pass through
./run.sh test # every suite
KICKSTART=kick13.rom ADF=wb13.adf ./run.sh # skip the TUISee harness/README.md for the memory map, the full option
list, and the manual CMake path.
Bare-metal or freestanding-style hosts can disable the default stderr log sink:
cmake -S . -B build-bare -DRIGEL_ENABLE_STDIO_LOG=OFF -DRIGEL_BUILD_TESTS=OFF
cmake --build build-bareSIMD pixel/buffer helpers are enabled by default where Rigel has a backend (SSE2 on x86_64, NEON on AArch64). Disable them with:
cmake -S . -B build-nosimd -DRIGEL_ENABLE_SIMD=OFF
cmake --build build-nosimdAll headers are included via <rigel/rigel.h>.
Lifecycle:
RigelContext *rigel_create(const rigel_config_t *config);
void rigel_destroy(RigelContext *ctx);
void rigel_reset(RigelContext *ctx);Temporal API — scheduling and synchronization:
rigel_cycle_t rigel_get_time(const RigelContext *ctx);
rigel_cycle_t rigel_get_next_deadline(const RigelContext *ctx);
rigel_step_result_t rigel_step(RigelContext *ctx, rigel_cycle_t cycles);
rigel_step_result_t rigel_step_until(RigelContext *ctx, rigel_cycle_t target_time);rigel_step_result_t carries the current time and a bitmask of what changed
(RIGEL_EVENT_IRQ_CHANGED, RIGEL_EVENT_FRAME_READY, RIGEL_EVENT_BLIT_DONE, etc.).
Bus observation — contention and wait states:
rigel_bus_state_t rigel_get_bus_state(const RigelContext *ctx);
rigel_cycle_t rigel_get_next_bus_change(const RigelContext *ctx);
bool rigel_cpu_can_access_chip_ram(const RigelContext *ctx);
rigel_cycle_t rigel_get_cpu_resume_time(const RigelContext *ctx);Custom register MMIO:
rigel_u16 rigel_custom_read16(const RigelContext *ctx, rigel_u32 offset);
void rigel_custom_write16(RigelContext *ctx, rigel_u32 offset, rigel_u16 value);IRQ state:
rigel_u16 rigel_get_intreq(const RigelContext *ctx);
rigel_u16 rigel_get_intena(const RigelContext *ctx);
rigel_u8 rigel_get_ipl(const RigelContext *ctx);Video output: rigel_get_frame(), rigel_get_scanline(), and
rigel_denise_get_video_desc() expose the completed frame, visible window, and
debug mode flags. Current mode flags cover lores/hires distinction, interlace
intent, dual-playfield, HAM6, and EHB. ECS identity, DIWHIGH, and PAL/NTSC
BEAMCON0 switching are present; ECS SuperHires/Productivity/Euro/Dbl modes are
tracked as planned work and are not advertised as supported modes yet.
Peripherals: rigel_floppy_* supports DF0-DF3 media/status and selected-drive
DMA routing; rigel_input_*, rigel_rtc_*, rigel_serial_*, and
rigel_audio_* expose the other integrated devices.
Host logging: set rigel_config_t.log_fn to receive internal log messages.
With RIGEL_ENABLE_STDIO_LOG=OFF, a NULL log callback becomes a no-op so Rigel
does not require stderr.
SIMD: the public API remains scalar and deterministic. Optional internal SIMD
helpers accelerate video buffer fills/copies when available, with scalar fallback
for unsupported targets or RIGEL_ENABLE_SIMD=OFF.
while (running) {
rigel_cycle_t until = rigel_get_next_deadline(rigel);
cpu_run_until(cpu, until);
rigel_step_result_t r = rigel_step_until(rigel, cpu_get_time(cpu));
if (r.events & RIGEL_EVENT_IRQ_CHANGED)
cpu_set_ipl(cpu, rigel_get_ipl(rigel));
if (r.events & RIGEL_EVENT_FRAME_READY)
host_present_frame(rigel_get_frame(rigel));
}For fine-grained bus integration (PiStorm/Emu68 style), also consult
rigel_get_bus_state() and rigel_get_next_bus_change().
#include <rigel/rigel.h>
static rigel_u16 chip_ram_read16(void *opaque, rigel_u32 addr)
{
rigel_u16 *ram = opaque;
return ram[addr >> 1];
}
static void chip_ram_write16(void *opaque, rigel_u32 addr, rigel_u16 value)
{
rigel_u16 *ram = opaque;
ram[addr >> 1] = value;
}
int main(void)
{
rigel_u16 chip_ram[256 * 1024] = {0};
rigel_config_t config = {
.clock_hz = 7093790,
.chip_ram_size = sizeof(chip_ram),
.chip_ram = { .opaque = chip_ram,
.read16 = chip_ram_read16,
.write16 = chip_ram_write16 },
};
RigelContext *ctx = rigel_create(&config);
rigel_custom_write16(ctx, 0x096, 0x8200); /* DMACON */
rigel_custom_write16(ctx, 0x09a, 0x8020); /* INTENA */
rigel_step_result_t r = rigel_step(ctx, 227); /* one scanline */
if (r.events & RIGEL_EVENT_IRQ_CHANGED)
handle_ipl(rigel_get_ipl(ctx));
rigel_destroy(ctx);
return 0;
}See docs/ for detailed documentation:
architecture.md— layers, domains, chipset compositionintegration.md— host loop, memory-map forwarding, bus observationtiming_model.md— DMA slot sequence, Temporal API, frame pacingvideo_output.md— video pipeline, frame struct, pixel formats, dirty trackingirq_model.md— interrupt sources, INTREQ/INTENA, host deliverymemory_map.md— custom register offsets