Skip to main content

[io]

# =============================================================================
# IO (FRAMEWORK FILE) VERSION 2025-11-29
# =============================================================================
# Hybrix programs interact with the virtual computer's hardware using
# memory-mapped input/output (MMIO), which means reading/writing special
# memory addresses. For example, writing a 1 byte to $d0_0011 will enable
# the jamdac synthesizer, or reading from $d0_0300 can tell you the current
# background color. This file defines all the MMIO locations.

# -----------------------------------------------------------------------------
class io_theme # size 64
# Maps a palette index to a themed palette index
inset color_map: byte[size 64]
end class

# -----------------------------------------------------------------------------
class io_sprite # size 16
# Screen coordinates of upper-left corner of sprite
var x: pair, y: pair

# Possible values: 8, 16, 32, 48
var width: byte, height: byte

# Layer:
# 0 = in front of all tilemaps
# 1 = behind tilemap A, in front of B
# 2 = behind tilemap B, in front of C
# 3 = behind all tilemaps
#
# Transform:
# +4 = flip X (horizontal)
# +8 = flip Y (vertical)
# +16 = rotate clockwise 90 degrees, only if sprite is square
var flags: byte

# The theme is an index (0..31) into io::themes
var theme: byte

# The address of the pixel buffer. To hide the sprite, set pixels_address=0
var pixels_address: int

var reserved: int
end class

# -----------------------------------------------------------------------------
class io_tilemap # size 16
# Screen coordinates of upper-left corner of tilemap
var x: pair, y: pair

# Possible values: 16, 32, 64, 128, 256
var col_count: pair, row_count: pair

# A pointer to a grid of tile codes arranged in left-to-right rows,
# starting from the top row. the array index is row*col_count+col.
# Each tile code is a 16-bit pair:
# bits 0..9 encode the tile index (0..1023) from the tileset
# bits 10..14 encode the theme index (0..31) into io::themes
# bit 15 if set, io::matte_color replaces any clear pixels in this tile
var tile_codes_address: int

# 0 = wrap: the map wraps around, repeating infinitely in every direction
# 1 = clip: cells outside the map bounds are transparent
var edge_mode: byte

# ------------------
# Tileset properties

# 0 = tiles are 8 x 8 pixels
# 1 = tiles are 16 x 16 pixels
var jumbo: byte

# 0 < tile_count <= 1024
var tile_count: pair
end class

# -----------------------------------------------------------------------------
class io_input_event # size 4
# 0 = key down
# 1 = key repeat
# 2 = key up
# 3 = resync; all keys up
var kind: byte

# Identifies the keyboard button, or 0 for alternate input sources
var keyid: byte

# Identifies the translated HASCII, or 0 for keys that don't type anything.
# Right now the high byte is always 0, but in the future it will be used
# for UTF-16 characters.
var hascii: pair
end class

# -----------------------------------------------------------------------------
class io_hand_controller # size 8
# 0 = disabled
# 1 = gamepad
# 16 = mouse (x and y are rolling counters)
var kind: byte

# +1 = a / mouse main button
# +2 = b / mouse middle button
# +4 = c / mouse secondary button
# +8 = d
var buttons: byte

# Gamepad: (left) -1023 .. +1023 (right)
var x: pair

# Gamepad: (up) -1023 .. +1023 (down)
var y: pair

var reserved: pair
end class

# =============================================================================
# IO MODULE
# =============================================================================
# MMIO location mappings for all hardware interfaces.

# -----------------------------------------------------------------------------
module io
# Memory segment layout
var ram_end_address: int located at $d0_0000 # 001x_xxxx
var rom_end_address: int located at $d0_0004 # 00cx_xxxx
var io_end_address: int located at $d0_0008 # 00dx_xxxx

# Used with irq_wake_mask below. Whenever the specified event occurs,
# the hardware sets the corresponding bit. The program can clear a bit
# by setting it (the "write 1 to clear" or "W1C" convention).
# +1 = whenever palix paints a video frame (buffered or unbuffered)
# +2 = whenever jamdac advances an io_audio_queue::read_address
var irq_pending: byte located at $d0_0010

# To save electricity, the CPU can be temporary paused using the "sleep"
# instruction. The CPU will wake up again when irq_pending matches any of
# the irq_wake_mask bits (level triggering).
var irq_wake_mask: byte located at $d0_0012

# Custom handling for "fail", "trace", "request" and other CPU events
var cpu_event_flags: byte located at $d0_0013
var cpu_event_ip: int located at $d0_0014
var cpu_event_fp: int located at $d0_0018
var cpu_event_value: int located at $d0_001c
var cpu_fail_handler: int located at $d0_0020
var cpu_trace_handler: int located at $d0_0024
var cpu_request_handler: int located at $d0_0028

# Real time clock
var clock_year: pair located at $d0_0030
var clock_month: byte located at $d0_0032
var clock_day: byte located at $d0_0033
var clock_day_of_week: byte located at $d0_0034
var clock_hours: byte located at $d0_0035
var clock_minutes: byte located at $d0_0036
var clock_seconds: byte located at $d0_0037

# Starts from 0, incrementing every millisecond. Your program can modify it.
var clock_ms: int located at $d0_0038

# +1 = floating point add-on
var product_features: byte located at $d0_003c

# 0 = disabled; 1 = stack fault when sp > gp
var stack_guard: byte located at $d0_003d

# 0 = disable Jamdac; 1 = enable Jamdac, rebooting its CPU
var jamdac_enable: byte located at $d0_003e

# (input_queue_end_index is at $d0_003f)

# Serial port output
#
# (see status codes below)
var transmit_status: byte located at $d0_0040
# 0 = cancel transmit (can be set before a final status is received)
# 1 = start transmit
var transmit_command: byte located at $d0_0041
var transmit_channel: byte located at $d0_0042
var transmit_next_write_index: byte located at $d0_0043
var transmit_next_read_index: byte located at $d0_0044

# Serial port input
#
# (see status codes below)
var receive_status: byte located at $d0_0048
# 0 = cancel listening (can be set before a final status is received)
# 1 = start listening
var receive_command: byte located at $d0_0049
var receive_channel: byte located at $d0_004a
var receive_next_write_index: byte located at $d0_004b
var receive_next_read_index: byte located at $d0_004c

# Storage controller
#
# (see status codes below)
var drive_status: byte located at $d0_0050
# 1 = get info
# 2 = read sector
# 3 = write sector
var drive_command: byte located at $d0_0051
# 0 = flash chip
var drive_volume: byte located at $d0_0052
# 1 = flash chip, sector size is 1024, 4 sectors total per cartridge
var drive_info_type: byte located at $d0_0053
var drive_info_total_sectors: int located at $d0_0054
var drive_sector_num: int located at $d0_0058
var drive_dma_address: int located at $d0_005c

# Status codes reported by: transmit_status, receive_status, drive_status
#
# 0 = done
# 8 = error: device not present
# 9 = error: device not present, reached end
# 10 = error: invalid command
# 11 = error: invalid sector number
# 12 = error: memory fault
#
# 128 = start command - written by program to start an operation
# 129 = busy - will change to a "ready code" (< 128) after the work is done

inset gamepad_0: io_hand_controller located at $d0_0060
inset gamepad_1: io_hand_controller located at $d0_0068
inset gamepads: io_hand_controller[inset 2] located at $d0_0060

inset mouse: io_hand_controller located at $d0_0078

# $d0_0080 - $d0_00bf are reserved for kernel globals
var kernel_heap_start_address: int located at $d0_0080
# Conventionally, the heap end is the stack start
var kernel_stack_start_address: int located at $d0_0084
var kernel_stack_end_address: int located at $d0_0088

# Keyboard
var input_queue_end_index: byte located at $d0_003f
inset input_queue: io_input_event[inset 16] located at $d0_00c0 # ..$d0_00ff

# Serial buffers
inset transmit_buffer: byte[size 256] located at $d0_0100
inset receive_buffer: byte[size 256] located at $d0_0200

# Palix video system
var background_color: byte located at $d0_0300
var matte_color: byte located at $d0_0301

# Increments for each video frame, even if painting is skipped.
# The refresh rate is 30 frames/sec = 33.333 ms/frame
var frame_counter: pair located at $d0_0302

# 0 = unbuffered
# 1 = buffered
# 3 = buffered, paint requested; afterwards the value reverts to 1
var paint_mode: byte located at $d0_0304

inset tilemap_a: io_tilemap located at $d0_0310 # ..$d0_031f
inset tilemap_b: io_tilemap located at $d0_0320 # ..$d0_032f
inset tilemap_c: io_tilemap located at $d0_0330 # ..$d0_033f

inset sprites: io_sprite[inset 64] located at $d0_0400 # ..$d0_07ff

inset themes: io_theme[inset 32] located at $d0_0800 # ..$d0_0fff

inset tileset_a_addresses: int[size 1024] located at $d0_1000 # ..$d0_1fff
inset tileset_b_addresses: int[size 1024] located at $d0_2000 # ..$d0_2fff
inset tileset_c_addresses: int[size 1024] located at $d0_3000 # ..$d0_3fff

# Jamdac
inset audio_queues: io_audio_queue[inset 6] located at $d0_4000 # ..$d0_4bff
end module