Skip to content

Instantly share code, notes, and snippets.

@leiradel
Last active June 30, 2024 20:25
Show Gist options
  • Save leiradel/1496386e64ff422d75028e0cfa2e44df to your computer and use it in GitHub Desktop.
Save leiradel/1496386e64ff422d75028e0cfa2e44df to your computer and use it in GitHub Desktop.
ULA commands for frame generation and CPU control
#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