Last active
June 30, 2024 20:25
-
-
Save leiradel/1496386e64ff422d75028e0cfa2e44df to your computer and use it in GitHub Desktop.
ULA commands for frame generation and CPU control
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
/*# | |
# ula.h | |
A ZX Spectrum ULA emulator in a C header. | |
Do this: | |
~~~C | |
#define CHIPS_IMPL | |
~~~ | |
before you include this file in *one* C or C++ file to create the | |
implementation. | |
Optionally provide the following macros with your own implementation | |
~~~C | |
CHIPS_ASSERT(c) | |
~~~ | |
your own assert macro (default: assert(c)) | |
You need to include the following headers before including ula.h: | |
- chips/chips_common.h | |
- chips/beeper.h | |
- chips/kbd.h | |
- chips/zx.h | |
## MIT license | |
Copyright (c) 2024 Andre Leiradella | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the “Software”), to | |
deal in the Software without restriction, including without limitation the | |
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
sell copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
IN THE SOFTWARE. | |
#*/ | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include <stddef.h> | |
#include <stdalign.h> | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
// framebuffer is the entire area that the ULA draws to, it's bigger than the | |
// actual displayed image on the TV | |
#define ULA_FRAMEBUFFER_WIDTH (352) | |
#define ULA_FRAMEBUFFER_HEIGHT (296) | |
#define ULA_FRAMEBUFFER_NUM_PIXELS (ULA_FRAMEBUFFER_WIDTH * ULA_FRAMEBUFFER_HEIGHT) | |
// the size of the image that is actually displayed | |
#define ULA_DISPLAY_WIDTH (320) | |
#define ULA_DISPLAY_HEIGHT (240) | |
#define ULA_DISPLAY_NUM_PIXELS (ULA_DISPLAY_WIDTH * ULA_DISPLAY_HEIGHT) | |
// bits returned by ula_tick() | |
#define ULA_USING_BUS (1 << 0) // whether to clock the CPU or not | |
#define ULA_INTERRUPT_CPU (1 << 1) // if the CPU must be interrupted | |
#define ULA_VSYNC (1 << 2) // if the current frame has ended | |
#define ULA_BEEPER_SAMPLE (1 << 3) // if a beeper sample is available | |
// ULA models | |
typedef enum { | |
ULA_TYPE_5C112E, // ZX Spectrum 48K Issue 3 | |
ULA_TYPE_7K010E_5 // ZX Spectrum 128 | |
} | |
ula_type_t; | |
// callback to read from memory | |
typedef uint8_t (*ula_read_callback_t)(uint8_t bank, uint16_t address, void* user_data); | |
// config parameters for ula_init() | |
typedef struct { | |
ula_type_t type; // the ULA type to emulate | |
struct { | |
ula_read_callback_t callback; // function to read from memory | |
void* user_data; // user data for the function above | |
} | |
memory; | |
struct { | |
int cpu_clock; // CPU clock to use with the beeper | |
int sample_rate; // output sample rate (i.e. 44100) | |
float beeper_volume; // beeper volume to output samples | |
} | |
audio; | |
} | |
ula_desc_t; | |
// ULA emulator state | |
typedef struct { | |
bool valid; // true if valid | |
ula_type_t type; // ULA type | |
ula_read_callback_t read_cb; // function to read from memory | |
void* read_ud; // user data for the read callback | |
uint8_t last_fe_out; // last value written to port 0xfe | |
uint8_t blink_counter; // flash | |
uint8_t border_color; // color to use with the border | |
uint8_t display_bank; // which RAM bank to read from | |
int beam; // current position in the framebuffer | |
int address; // pixel address (y * 32 + col) | |
int row; // current row (0-31) | |
int y; // current line (0-191) | |
uint16_t masks; // combined latched pixel masks | |
uint8_t attrs[2]; // latched attributes | |
uint16_t mask_bit; // counter to select a bit in the masks | |
int line; // current line in _ula_line_t | |
int rle_count; // how many line repetitions left | |
int command; // what the ULA is doing this T-state | |
kbd_t kbd; // the keyboard | |
beeper_t beeper; // the beeper | |
uint8_t ear; // the bit in the EAR input | |
alignas(64) uint8_t fb[ULA_FRAMEBUFFER_NUM_PIXELS]; // the framebuffer | |
} | |
ula_t; | |
// initialize a new ULA instance | |
void ula_init(ula_t* ula, ula_desc_t const* desc); | |
// discard an ULA Spectrum instance | |
void ula_discard(ula_t* ula); | |
// reset an ULA instance | |
void ula_reset(ula_t* ula); | |
// query information about display requirements, can be called with nullptr | |
chips_display_info_t ula_display_info(ula_t* ula); | |
// run the ULA instance for two clock cycles, returns whether the CPU has to be | |
// clocked or interrupted | |
uint8_t ula_tick(ula_t* ula, uint8_t ear); | |
// I/O | |
uint8_t ula_read_port(ula_t* ula, uint16_t addr); | |
void ula_write_port(ula_t* ula, uint16_t addr, uint8_t data); | |
// 128K display RAM bank select | |
void ula_set_display_bank(ula_t* ula, uint8_t bank); | |
// set the border color (for when loading a snapshot) | |
void ula_set_border_color(ula_t* ula, uint8_t color); | |
// keyboard control | |
void ula_kbd_update(ula_t* ula, uint32_t frame_time_us); | |
void ula_key_down(ula_t* ula, int key_code); | |
void ula_key_up(ula_t* ula, int key_code); | |
#ifdef __cplusplus | |
} // extern "C" | |
#endif | |
/*-- IMPLEMENTATION ----------------------------------------------------------*/ | |
//#ifdef CHIPS_IMPL | |
#if 1 | |
#include <string.h> | |
#ifndef CHIPS_ASSERT | |
#include <assert.h> | |
#define CHIPS_ASSERT(c) assert(c) | |
#endif | |
/* | |
commands to drive the ULA operation | |
(space) ULA is doing nothing | |
i ULA is holding /INT low | |
. ULA is generating two border pixels | |
P ULA is generating two pixels | |
P ULA is generating two pixels, and holding CLK high | |
m ULA is fetching the 1st pixel mask from memory, holding CLK high, and generating two border pixels | |
a ULA is fetching the 1st attribute from memory, holding CLK high, and generating two border pixels | |
n ULA is fetching the 2nd pixel mask from memory, holding CLK high, and generating two border pixels | |
b ULA is fetching the 2nd attribute from memory, holding CLK high, and generating two border pixels | |
M \ | |
A \ Same as 'm', 'a', 'n', and 'b', but generates pixels instead of border | |
N / | |
B / | |
*/ | |
// commands to use for an entire horizontal run, including hblank | |
typedef struct { | |
int rle_count; // how many times to repeat this line | |
char const* const commands; // commands to drive this line | |
} | |
_ula_line_t; | |
typedef struct { | |
int line_count; // number of lines below | |
_ula_line_t const* lines; // the lines that drive the ULA | |
} | |
_ula_frame_t; | |
/*-- data for the 5C112E -----------------------------------------------------*/ | |
// vblank with interrupt, 1 line | |
static char const* const _ula_5c112e_vblank_int = | |
"iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii "; | |
// vblank, 15 lines | |
static char const* const _ula_5c112e_vblank = | |
" "; | |
// top border, 48 lines | |
static char const* const _ula_5c112e_top_border = | |
"................................................................................................................................................................................ "; | |
// screen, 192 lines | |
static char const* const _ula_5c112e_screen = | |
"......................maNBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPpppp........................ "; | |
// bottom border, 56 lines | |
static char const* const _ula_5c112e_bottom_border = _ula_5c112e_top_border; | |
// rle data to drive the ULA | |
static _ula_line_t const _ula_5c112e_lines[] = { | |
{ 1, _ula_5c112e_vblank_int}, | |
{ 15, _ula_5c112e_vblank}, | |
{ 48, _ula_5c112e_top_border}, | |
{192, _ula_5c112e_screen}, | |
{ 56, _ula_5c112e_bottom_border}, | |
}; | |
/*-- data for the 7K010E-5 ---------------------------------------------------*/ | |
// vblank with interrupt, 1 line | |
static char const* const _ula_7k010e5_vblank_int = | |
" iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii "; | |
// vblank, 15 lines | |
static char const* const _ula_7k010e5_vblank = | |
" "; | |
// top border, 47 lines | |
static char const* const _ula_7k010e5_top_border = | |
"................................................................................................................................................................................ "; | |
// screen, 192 lines | |
static char const* const _ula_7k010e5_screen = | |
"......................maNBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPppMANBPPpppp........................ "; | |
// bottom border, 56 lines | |
static char const* const _ula_7k010e5_bottom_border = | |
"................................................................................................................................................................................ "; | |
// rle data to drive the ULA | |
static _ula_line_t const _ula_7k010e5_lines[] = { | |
{ 1, _ula_7k010e5_vblank_int}, | |
{ 15, _ula_7k010e5_vblank}, | |
{ 47, _ula_7k010e5_top_border}, | |
{192, _ula_7k010e5_screen}, | |
{ 56, _ula_7k010e5_bottom_border}, | |
}; | |
static _ula_frame_t const _ula_frames[] = { | |
{(int)(sizeof(_ula_5c112e_lines) / sizeof(_ula_5c112e_lines[0])), _ula_5c112e_lines}, | |
{(int)(sizeof(_ula_7k010e5_lines) / sizeof(_ula_7k010e5_lines[0])), _ula_7k010e5_lines} | |
}; | |
// lookup table to translate line numbers to 0-based screen addresses | |
static uint16_t const _ula_y_pixels[192] = { | |
0x0000, 0x0100, 0x0200, 0x0300, 0x0400, 0x0500, 0x0600, 0x0700, | |
0x0020, 0x0120, 0x0220, 0x0320, 0x0420, 0x0520, 0x0620, 0x0720, | |
0x0040, 0x0140, 0x0240, 0x0340, 0x0440, 0x0540, 0x0640, 0x0740, | |
0x0060, 0x0160, 0x0260, 0x0360, 0x0460, 0x0560, 0x0660, 0x0760, | |
0x0080, 0x0180, 0x0280, 0x0380, 0x0480, 0x0580, 0x0680, 0x0780, | |
0x00a0, 0x01a0, 0x02a0, 0x03a0, 0x04a0, 0x05a0, 0x06a0, 0x07a0, | |
0x00c0, 0x01c0, 0x02c0, 0x03c0, 0x04c0, 0x05c0, 0x06c0, 0x07c0, | |
0x00e0, 0x01e0, 0x02e0, 0x03e0, 0x04e0, 0x05e0, 0x06e0, 0x07e0, | |
0x0800, 0x0900, 0x0a00, 0x0b00, 0x0c00, 0x0d00, 0x0e00, 0x0f00, | |
0x0820, 0x0920, 0x0a20, 0x0b20, 0x0c20, 0x0d20, 0x0e20, 0x0f20, | |
0x0840, 0x0940, 0x0a40, 0x0b40, 0x0c40, 0x0d40, 0x0e40, 0x0f40, | |
0x0860, 0x0960, 0x0a60, 0x0b60, 0x0c60, 0x0d60, 0x0e60, 0x0f60, | |
0x0880, 0x0980, 0x0a80, 0x0b80, 0x0c80, 0x0d80, 0x0e80, 0x0f80, | |
0x08a0, 0x09a0, 0x0aa0, 0x0ba0, 0x0ca0, 0x0da0, 0x0ea0, 0x0fa0, | |
0x08c0, 0x09c0, 0x0ac0, 0x0bc0, 0x0cc0, 0x0dc0, 0x0ec0, 0x0fc0, | |
0x08e0, 0x09e0, 0x0ae0, 0x0be0, 0x0ce0, 0x0de0, 0x0ee0, 0x0fe0, | |
0x1000, 0x1100, 0x1200, 0x1300, 0x1400, 0x1500, 0x1600, 0x1700, | |
0x1020, 0x1120, 0x1220, 0x1320, 0x1420, 0x1520, 0x1620, 0x1720, | |
0x1040, 0x1140, 0x1240, 0x1340, 0x1440, 0x1540, 0x1640, 0x1740, | |
0x1060, 0x1160, 0x1260, 0x1360, 0x1460, 0x1560, 0x1660, 0x1760, | |
0x1080, 0x1180, 0x1280, 0x1380, 0x1480, 0x1580, 0x1680, 0x1780, | |
0x10a0, 0x11a0, 0x12a0, 0x13a0, 0x14a0, 0x15a0, 0x16a0, 0x17a0, | |
0x10c0, 0x11c0, 0x12c0, 0x13c0, 0x14c0, 0x15c0, 0x16c0, 0x17c0, | |
0x10e0, 0x11e0, 0x12e0, 0x13e0, 0x14e0, 0x15e0, 0x16e0, 0x17e0, | |
}; | |
static uint16_t const _ula_y_attribute[192] = { | |
0x1800, 0x1800, 0x1800, 0x1800, 0x1800, 0x1800, 0x1800, 0x1800, | |
0x1820, 0x1820, 0x1820, 0x1820, 0x1820, 0x1820, 0x1820, 0x1820, | |
0x1840, 0x1840, 0x1840, 0x1840, 0x1840, 0x1840, 0x1840, 0x1840, | |
0x1860, 0x1860, 0x1860, 0x1860, 0x1860, 0x1860, 0x1860, 0x1860, | |
0x1880, 0x1880, 0x1880, 0x1880, 0x1880, 0x1880, 0x1880, 0x1880, | |
0x18a0, 0x18a0, 0x18a0, 0x18a0, 0x18a0, 0x18a0, 0x18a0, 0x18a0, | |
0x18c0, 0x18c0, 0x18c0, 0x18c0, 0x18c0, 0x18c0, 0x18c0, 0x18c0, | |
0x18e0, 0x18e0, 0x18e0, 0x18e0, 0x18e0, 0x18e0, 0x18e0, 0x18e0, | |
0x1900, 0x1900, 0x1900, 0x1900, 0x1900, 0x1900, 0x1900, 0x1900, | |
0x1920, 0x1920, 0x1920, 0x1920, 0x1920, 0x1920, 0x1920, 0x1920, | |
0x1940, 0x1940, 0x1940, 0x1940, 0x1940, 0x1940, 0x1940, 0x1940, | |
0x1960, 0x1960, 0x1960, 0x1960, 0x1960, 0x1960, 0x1960, 0x1960, | |
0x1980, 0x1980, 0x1980, 0x1980, 0x1980, 0x1980, 0x1980, 0x1980, | |
0x19a0, 0x19a0, 0x19a0, 0x19a0, 0x19a0, 0x19a0, 0x19a0, 0x19a0, | |
0x19c0, 0x19c0, 0x19c0, 0x19c0, 0x19c0, 0x19c0, 0x19c0, 0x19c0, | |
0x19e0, 0x19e0, 0x19e0, 0x19e0, 0x19e0, 0x19e0, 0x19e0, 0x19e0, | |
0x1a00, 0x1a00, 0x1a00, 0x1a00, 0x1a00, 0x1a00, 0x1a00, 0x1a00, | |
0x1a20, 0x1a20, 0x1a20, 0x1a20, 0x1a20, 0x1a20, 0x1a20, 0x1a20, | |
0x1a40, 0x1a40, 0x1a40, 0x1a40, 0x1a40, 0x1a40, 0x1a40, 0x1a40, | |
0x1a60, 0x1a60, 0x1a60, 0x1a60, 0x1a60, 0x1a60, 0x1a60, 0x1a60, | |
0x1a80, 0x1a80, 0x1a80, 0x1a80, 0x1a80, 0x1a80, 0x1a80, 0x1a80, | |
0x1aa0, 0x1aa0, 0x1aa0, 0x1aa0, 0x1aa0, 0x1aa0, 0x1aa0, 0x1aa0, | |
0x1ac0, 0x1ac0, 0x1ac0, 0x1ac0, 0x1ac0, 0x1ac0, 0x1ac0, 0x1ac0, | |
0x1ae0, 0x1ae0, 0x1ae0, 0x1ae0, 0x1ae0, 0x1ae0, 0x1ae0, 0x1ae0, | |
}; | |
static void _ula_init_keyboard_matrix(ula_t* ula) { | |
// setup keyboard matrix | |
kbd_init(&ula->kbd, 1); | |
// caps-shift is column 0, line 0 | |
kbd_register_modifier(&ula->kbd, 0, 0, 0); | |
// sym-shift is column 7, line 1 | |
kbd_register_modifier(&ula->kbd, 1, 7, 1); | |
// alpha-numeric keys | |
static char const* const keymap = | |
/* no shift */ | |
" zxcv" // A8 shift,z,x,c,v | |
"asdfg" // A9 a,s,d,f,g | |
"qwert" // A10 q,w,e,r,t | |
"12345" // A11 1,2,3,4,5 | |
"09876" // A12 0,9,8,7,6 | |
"poiuy" // A13 p,o,i,u,y | |
" lkjh" // A14 enter,l,k,j,h | |
" mnb" // A15 space,symshift,m,n,b | |
// shift | |
" ZXCV" // A8 | |
"ASDFG" // A9 | |
"QWERT" // A10 | |
" " // A11 | |
" " // A12 | |
"POIUY" // A13 | |
" LKJH" // A14 | |
" MNB" // A15 | |
// symshift | |
" : ?/" // A8 | |
" " // A9 | |
" <>" // A10 | |
"!@#$%" // A11 | |
"_)('&" // A12 | |
"\"; " // A13 | |
" =+-^" // A14 | |
" .,*"; // A15 | |
for (int layer = 0; layer < 3; layer++) { | |
for (int column = 0; column < 8; column++) { | |
for (int line = 0; line < 5; line++) { | |
const uint8_t c = keymap[layer*40 + column*5 + line]; | |
if (c != 0x20) { | |
kbd_register_key(&ula->kbd, c, column, line, (layer>0) ? (1<<(layer-1)) : 0); | |
} | |
} | |
} | |
} | |
// special keys | |
kbd_register_key(&ula->kbd, ' ', 7, 0, 0); // Space | |
kbd_register_key(&ula->kbd, 0x0F, 7, 1, 0); // SymShift | |
kbd_register_key(&ula->kbd, 0x08, 3, 4, 1); // Cursor Left (Shift+5) | |
kbd_register_key(&ula->kbd, 0x0A, 4, 4, 1); // Cursor Down (Shift+6) | |
kbd_register_key(&ula->kbd, 0x0B, 4, 3, 1); // Cursor Up (Shift+7) | |
kbd_register_key(&ula->kbd, 0x09, 4, 2, 1); // Cursor Right (Shift+8) | |
kbd_register_key(&ula->kbd, 0x07, 3, 0, 1); // Edit (Shift+1) | |
kbd_register_key(&ula->kbd, 0x0C, 4, 0, 1); // Delete (Shift+0) | |
kbd_register_key(&ula->kbd, 0x0D, 6, 0, 0); // Enter | |
} | |
void ula_init(ula_t* ula, ula_desc_t const* desc) { | |
CHIPS_ASSERT(ula != 0 && desc != 0); | |
memset(ula, 0, sizeof(*ula)); | |
ula->type = desc->type; | |
ula->read_cb = desc->memory.callback; | |
ula->read_ud = desc->memory.user_data; | |
ula->display_bank = 5; | |
ula->mask_bit = 0x8000; | |
ula->rle_count = _ula_frames[ula->type].lines[0].rle_count; | |
_ula_init_keyboard_matrix(ula); | |
beeper_init(&ula->beeper, &(beeper_desc_t){ | |
.tick_hz = (int)desc->audio.cpu_clock, | |
.sound_hz = desc->audio.sample_rate != 0 ? desc->audio.sample_rate : 44100, | |
.base_volume = desc->audio.beeper_volume != 0.0f ? desc->audio.beeper_volume : 0.25f | |
}); | |
ula->valid = true; | |
} | |
void ula_discard(ula_t* ula) { | |
CHIPS_ASSERT(ula != 0 && ula->valid); | |
ula->valid = false; | |
} | |
void ula_reset(ula_t* ula) { | |
CHIPS_ASSERT(ula != 0 && ula->valid); | |
ula->last_fe_out = 0; | |
ula->blink_counter = 0; | |
ula->border_color = 0; | |
ula->display_bank = 5; | |
ula->beam = 0; | |
ula->address = 0; | |
ula->line = 0; | |
ula->masks = 0; | |
ula->attrs[0] = 0; | |
ula->attrs[1] = 0; | |
ula->mask_bit = 0x8000; | |
ula->rle_count = _ula_frames[ula->type].lines[0].rle_count; | |
ula->command = 0; | |
beeper_reset(&ula->beeper); | |
} | |
chips_display_info_t ula_display_info(ula_t* ula) { | |
static const uint32_t palette[16] = { | |
0xff000000, // black | |
0xffbc0000, // blue | |
0xff0000bc, // red | |
0xffbc00bc, // magenta | |
0xff00bc00, // green | |
0xffbcbc00, // cyan | |
0xff00bcbc, // yellow | |
0xffbcbcbc, // white | |
0xff000000, // bright black | |
0xfff60000, // bright blue | |
0xff0000f6, // bright red | |
0xfff600f6, // bright magenta | |
0xff00f600, // bright green | |
0xfff6f600, // bright cyan | |
0xff00f6f6, // bright yellow | |
0xffffffff // bright white | |
}; | |
CHIPS_ASSERT(ula != 0 && ula->valid); | |
chips_display_info_t const res = { | |
.frame = { | |
.dim = { | |
.width = ULA_FRAMEBUFFER_WIDTH, | |
.height = ULA_FRAMEBUFFER_HEIGHT, | |
}, | |
.buffer = { | |
.ptr = ula->fb, | |
.size = ULA_FRAMEBUFFER_NUM_PIXELS, | |
}, | |
.bytes_per_pixel = 1, | |
}, | |
.screen = { | |
.x = 16, | |
.y = 23 + (ula->type == ULA_TYPE_5C112E), | |
.width = ULA_DISPLAY_WIDTH, | |
.height = ULA_DISPLAY_HEIGHT, | |
}, | |
.palette = { | |
.ptr = (void*)palette, | |
.size = sizeof(palette), | |
} | |
}; | |
return res; | |
} | |
static void _ula_draw_pixels(ula_t* ula) { | |
uint8_t fg = 0, bg = 0; | |
uint8_t const attr = ula->attrs[ula->mask_bit <= 0x0080]; | |
bool const blink = (ula->blink_counter & 0x10) != 0; | |
if ((attr & (1 << 7)) && blink) { | |
fg = (attr >> 3) & 7; | |
bg = attr & 7; | |
} | |
else { | |
fg = attr & 7; | |
bg = (attr >> 3) & 7; | |
} | |
// color bit 6: standard vs bright | |
fg |= (attr & (1 << 6)) >> 3; | |
bg |= (attr & (1 << 6)) >> 3; | |
ula->fb[ula->beam++] = (ula->masks & ula->mask_bit) ? fg : bg; | |
ula->fb[ula->beam++] = (ula->masks & (ula->mask_bit >> 1)) ? fg : bg; | |
ula->mask_bit = (ula->mask_bit & 2) << 14 | ula->mask_bit >> 2; | |
} | |
uint8_t ula_tick(ula_t* ula, uint8_t ear) { | |
CHIPS_ASSERT(ula != 0 && ula->valid); | |
ula->ear = ear; | |
uint8_t cpu_ctrl = 0; | |
char const* const commands = _ula_frames[ula->type].lines[ula->line].commands; | |
switch (commands[ula->command++]) { | |
case ' ': | |
break; | |
case 'i': | |
cpu_ctrl |= ULA_INTERRUPT_CPU; | |
break; | |
case '.': { | |
ula->fb[ula->beam++] = ula->border_color; | |
ula->fb[ula->beam++] = ula->border_color; | |
break; | |
} | |
case 'p': { | |
_ula_draw_pixels(ula); | |
break; | |
} | |
case 'P': { | |
_ula_draw_pixels(ula); | |
cpu_ctrl |= ULA_USING_BUS; | |
break; | |
} | |
case 'm': { | |
ula->fb[ula->beam++] = ula->border_color; | |
ula->fb[ula->beam++] = ula->border_color; | |
uint16_t const address = _ula_y_pixels[ula->address >> 5] + (ula->address & 31); | |
ula->masks &= 0x00ff; | |
ula->masks |= ula->read_cb(ula->display_bank, address, ula->read_ud) << 8; | |
cpu_ctrl |= ULA_USING_BUS; | |
break; | |
} | |
case 'a': { | |
ula->fb[ula->beam++] = ula->border_color; | |
ula->fb[ula->beam++] = ula->border_color; | |
uint16_t const address = _ula_y_attribute[ula->address >> 5] + (ula->address & 31); | |
ula->address++; | |
ula->attrs[0] = ula->read_cb(ula->display_bank, address, ula->read_ud); | |
cpu_ctrl |= ULA_USING_BUS; | |
break; | |
} | |
case 'n': { | |
ula->fb[ula->beam++] = ula->border_color; | |
ula->fb[ula->beam++] = ula->border_color; | |
uint16_t const address = _ula_y_pixels[ula->address >> 5] + (ula->address & 31); | |
ula->masks &= 0xff00; | |
ula->masks |= ula->read_cb(ula->display_bank, address, ula->read_ud); | |
cpu_ctrl |= ULA_USING_BUS; | |
break; | |
} | |
case 'b': { | |
ula->fb[ula->beam++] = ula->border_color; | |
ula->fb[ula->beam++] = ula->border_color; | |
uint16_t const address = _ula_y_attribute[ula->address >> 5] + (ula->address & 31); | |
ula->address++; | |
ula->attrs[1] = ula->read_cb(ula->display_bank, address, ula->read_ud); | |
cpu_ctrl |= ULA_USING_BUS; | |
break; | |
} | |
case 'M': { | |
_ula_draw_pixels(ula); | |
uint16_t const address = _ula_y_pixels[ula->address >> 5] + (ula->address & 31); | |
ula->masks &= 0x00ff; | |
ula->masks |= ula->read_cb(ula->display_bank, address, ula->read_ud) << 8; | |
cpu_ctrl |= ULA_USING_BUS; | |
break; | |
} | |
case 'A': { | |
_ula_draw_pixels(ula); | |
uint16_t const address = _ula_y_attribute[ula->address >> 5] + (ula->address & 31); | |
ula->address++; | |
ula->attrs[0] = ula->read_cb(ula->display_bank, address, ula->read_ud); | |
cpu_ctrl |= ULA_USING_BUS; | |
break; | |
} | |
case 'N': { | |
_ula_draw_pixels(ula); | |
uint16_t const address = _ula_y_pixels[ula->address >> 5] + (ula->address & 31); | |
ula->masks &= 0xff00; | |
ula->masks |= ula->read_cb(ula->display_bank, address, ula->read_ud); | |
cpu_ctrl |= ULA_USING_BUS; | |
break; | |
} | |
case 'B': { | |
_ula_draw_pixels(ula); | |
uint16_t const address = _ula_y_attribute[ula->address >> 5] + (ula->address & 31); | |
ula->address++; | |
ula->attrs[1] = ula->read_cb(ula->display_bank, address, ula->read_ud); | |
cpu_ctrl |= ULA_USING_BUS; | |
break; | |
} | |
} | |
if (commands[ula->command] == 0) { | |
ula->command = 0; | |
ula->rle_count--; | |
if (ula->rle_count == 0) { | |
ula->line++; | |
if (ula->line == _ula_frames[ula->type].line_count) { | |
ula->line = 0; | |
ula->beam = 0; | |
ula->address = 0; | |
ula->blink_counter++; | |
cpu_ctrl |= ULA_VSYNC; | |
} | |
ula->rle_count = _ula_frames[ula->type].lines[ula->line].rle_count; | |
} | |
} | |
if (beeper_tick(&ula->beeper)) { | |
cpu_ctrl |= ULA_BEEPER_SAMPLE; | |
} | |
return cpu_ctrl; | |
} | |
uint8_t ula_read_port(ula_t* ula, uint16_t addr) { | |
CHIPS_ASSERT(ula != 0 && ula->valid); | |
uint8_t data = (1 << 7) | (1 << 5); | |
// MIC/EAR flags -> bit 6 | |
if ((ula->last_fe_out & ((1 << 3) | (1 << 4))) != 0) { | |
data |= 1 << 6; | |
} | |
// keyboard matrix bits are encoded in the upper 8 bit of the port address | |
uint16_t const column_mask = (~addr >> 8) & 0xff; | |
uint16_t const kbd_lines = kbd_test_lines(&ula->kbd, column_mask); | |
data |= (~kbd_lines) & 0x1f; | |
return data; | |
} | |
void ula_write_port(ula_t* ula, uint16_t addr, uint8_t data) { | |
CHIPS_ASSERT(ula != 0 && ula->valid); | |
// FIXME: bit 3: MIC output (CAS SAVE, 0=On, 1=Off) | |
ula->last_fe_out = data; | |
ula->border_color = data & 7; | |
beeper_set(&ula->beeper, ula->ear != 0 || (data & (1 << 4)) != 0); | |
} | |
void ula_set_display_bank(ula_t* ula, uint8_t bank) { | |
CHIPS_ASSERT(ula != 0 && ula->valid); | |
ula->display_bank = bank; | |
} | |
void ula_set_border_color(ula_t* ula, uint8_t color) { | |
CHIPS_ASSERT(ula != 0 && ula->valid); | |
ula->border_color = color; | |
} | |
void ula_kbd_update(ula_t* ula, uint32_t frame_time_us) { | |
CHIPS_ASSERT(ula != 0 && ula->valid); | |
kbd_update(&ula->kbd, frame_time_us); | |
} | |
void ula_key_down(ula_t* ula, int key_code) { | |
CHIPS_ASSERT(ula != 0 && ula->valid); | |
kbd_key_down(&ula->kbd, key_code); | |
} | |
void ula_key_up(ula_t* ula, int key_code) { | |
CHIPS_ASSERT(ula != 0 && ula->valid); | |
kbd_key_up(&ula->kbd, key_code); | |
} | |
#endif // CHIPS_IMPL |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment