Skip to content

Commit

Permalink
Merge pull request #89 from floooh/jsapi
Browse files Browse the repository at this point in the history
Add support for remote control via Javascript.
  • Loading branch information
floooh authored Jan 2, 2024
2 parents 7feee72 + 6f0533e commit 9a7f6d6
Show file tree
Hide file tree
Showing 19 changed files with 778 additions and 380 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
## What's New

* **02-Jan-2024**:

- Added a new 'web api' (a set of Javascript functions which allow controlling
the integrated debugger) for the emualators:
- KC85/2, /3 and /4
- C64
- CPC
- Debugger: added a 'stopwatch' window to count cycles between two breakpoints
- am40010.h: video ram data is now loaded into an internal 2-byte latch
in two steps at a 2 MHz interval

Also check out this new VSCode extension: https://marketplace.visualstudio.com/items?itemName=floooh.vscode-kcide

* **10-Jan-2023**: A big code cleanup session affecting all emulators,
and some minor emulation improvements:

Expand Down
93 changes: 43 additions & 50 deletions chips/am40010.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ typedef struct am40010_video_t {
uint8_t mode; // currently active mode updated at hsync
bool sync; // state of the sync output pin
bool intr; // interrupt flip-flop
uint8_t latch[2]; // store video ram bytes read at 2 MHz
} am40010_video_t;

// CRT beam tracking
Expand Down Expand Up @@ -386,8 +387,7 @@ static void _am40010_init_hwcolors(am40010_t* ga) {
for (size_t i = 0; i < 32; i++) {
ga->hw_colors[i] = _am40010_cpc_colors[i];
}
}
else {
} else {
// KC Compact colors
for (size_t i = 0; i < 32; i++) {
uint32_t c = 0xFF000000;
Expand All @@ -414,8 +414,7 @@ static void _am40010_init_hwcolors(am40010_t* ga) {
r = 0xFF;
g = 0xFF;
b = 0xFF;
}
else {
} else {
if (i & 1) {
r = 0x55; // HS set
}
Expand Down Expand Up @@ -494,8 +493,7 @@ void am40010_iorq(am40010_t* ga, uint64_t pins) {
case (1<<6):
if (ga->regs.inksel & (1<<4)) {
ga->regs.border = data & 0x1F;
}
else {
} else {
ga->regs.ink[ga->regs.inksel] = data & 0x1F;
}
break;
Expand Down Expand Up @@ -585,8 +583,7 @@ static void _am40010_crt_tick(am40010_t* ga, bool sync) {
crt->h_pos++;
if (crt->h_pos == _AM40010_CRT_H_DISPLAY_START) {
crt->h_blank = false;
}
else if (crt->h_pos == 64) {
} else if (crt->h_pos == 64) {
// no hsync on this line
new_line = true;
}
Expand All @@ -602,8 +599,7 @@ static void _am40010_crt_tick(am40010_t* ga, bool sync) {
crt->v_pos++;
if (crt->v_pos == _AM40010_CRT_V_DISPLAY_START) {
crt->v_blank = false;
}
else if (crt->v_pos == 312) {
} else if (crt->v_pos == 312) {
// no vsync on this frame
new_frame = true;
}
Expand All @@ -625,8 +621,7 @@ static void _am40010_crt_tick(am40010_t* ga, bool sync) {
crt->visible = true;
crt->pos_x = crt->h_pos - _AM40010_CRT_VIS_X0;
crt->pos_y = crt->v_pos - _AM40010_CRT_VIS_Y0;
}
else {
} else {
crt->visible = false;
}
}
Expand Down Expand Up @@ -686,9 +681,8 @@ static bool _am40010_sync_irq(am40010_t* ga, uint64_t crtc_pins) {
// if HSYNC is off, force the clkcnt counter to 0
if (0 == (crtc_pins & AM40010_HS)) {
clkcnt = 0;
}
// FIXME: figure out why this "< 8" is needed (otherwise purple left column in Demoizart)
else if (clkcnt < 8) {
} else if (clkcnt < 8) {
// FIXME: figure out why this "< 8" is needed (otherwise purple left column in Demoizart)
clkcnt++;
}
// v_sync is on as long as hscount is < 4
Expand All @@ -702,20 +696,24 @@ static bool _am40010_sync_irq(am40010_t* ga, uint64_t crtc_pins) {
return ga->video.sync;
}

static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst, uint64_t crtc_pins) {
static uint8_t _am40010_vid_read(am40010_t* ga, uint64_t crtc_pins, uint16_t cclk) {
/*
compute the source address from current CRTC ma (memory address)
and ra (raster address) like this:
|ma13|ma12|ra2|ra1|ra0|ma9|ma8|ma7|ma6|ma5|ma4|ma3|ma2|ma1|ma0|0|
|ma13|ma12|ra2|ra1|ra0|ma9|ma8|ma7|ma6|ma5|ma4|ma3|ma2|ma1|ma0|cclk|
Bits ma13 and m12 point to the 16 KByte page, and all
other bits are the index into that page.
*/
const uint16_t addr = ((crtc_pins & 0x3000) << 2) | // MA13,MA12
((crtc_pins & 0x3FF) << 1) | // MA9..MA0
(((crtc_pins>>48) & 7) << 11); // RA0..RA2
const uint8_t* src = &(ga->ram[addr]);
(((crtc_pins>>48) & 7) << 11) | // RA0..RA2
cclk;
return ga->ram[addr];
}

static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst) {
uint8_t c;
uint8_t p;
switch (ga->video.mode) {
Expand All @@ -727,7 +725,7 @@ static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst, uint64_t crtc_pi
1: |0|4|2|6|
*/
for (size_t i = 0; i < 2; i++) {
c = *src++;
c = ga->video.latch[i];
p = ga->regs.ink[((c>>7)&0x1)|((c>>2)&0x2)|((c>>3)&0x4)|((c<<2)&0x8)];
*dst++ = p; *dst++ = p; *dst++ = p; *dst++ = p;
p = ga->regs.ink[((c>>6)&0x1)|((c>>1)&0x2)|((c>>2)&0x4)|((c<<3)&0x8)];
Expand All @@ -744,7 +742,7 @@ static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst, uint64_t crtc_pi
3: |0|4|
*/
for (size_t i = 0; i < 2; i++) {
c = *src++;
c = ga->video.latch[i];
p = ga->regs.ink[((c>>2)&2)|((c>>7)&1)];
*dst++ = p; *dst++ = p;
p = ga->regs.ink[((c>>1)&2)|((c>>6)&1)];
Expand All @@ -758,7 +756,7 @@ static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst, uint64_t crtc_pi
case 2:
// 640x200 @ 2 colors (8 pixels per byte)
for (size_t i = 0; i < 2; i++) {
c = *src++;
c = ga->video.latch[i];
*dst++ = ga->regs.ink[(c>>7)&1];
*dst++ = ga->regs.ink[(c>>6)&1];
*dst++ = ga->regs.ink[(c>>5)&1];
Expand All @@ -777,7 +775,7 @@ static void _am40010_decode_pixels(am40010_t* ga, uint8_t* dst, uint64_t crtc_pi
1: |x|x|2|6|
*/
for (size_t i = 0; i < 2; i++) {
c = *src++;
c = ga->video.latch[i];
p = ga->regs.ink[((c>>7)&0x1)|((c>>2)&0x2)];
*dst++ = p; *dst++ = p; *dst++ = p; *dst++ = p;
p = ga->regs.ink[((c>>6)&0x1)|((c>>1)&0x2)];
Expand All @@ -798,8 +796,7 @@ static void _am40010_decode_video(am40010_t* ga, uint64_t crtc_pins) {
uint8_t* prev_dst;
if (dst == ga->fb) {
prev_dst = dst;
}
else {
} else {
prev_dst = dst - 16;
}
uint8_t c = 0x20;
Expand All @@ -816,55 +813,43 @@ static void _am40010_decode_video(am40010_t* ga, uint64_t crtc_pins) {
c |= 0x08;
}
if (crtc_pins & AM40010_DE) {
_am40010_decode_pixels(ga, dst, crtc_pins);
_am40010_decode_pixels(ga, dst);
for (size_t i = 0; i < 16; i++) {
if (0 == (i & 2)) {
dst[i] = c ^ 0x10;
prev_dst[i] ^= 0x10;
}
}
}
else {
} else {
for (size_t i = 0; i < 16; i++) {
if (0 == (i & 2)) {
dst[i] = c ^ 0x10;
prev_dst[i] ^= 0x10;
}
else {
} else {
dst[i] = 63;
}
}
}
}
}
else if (ga->crt.visible) {
} else if (ga->crt.visible) {
size_t dst_x = ga->crt.pos_x * 16;
size_t dst_y = ga->crt.pos_y;
bool black = ga->video.sync;
uint8_t* dst = &ga->fb[dst_x + dst_y * AM40010_FRAMEBUFFER_WIDTH];
if (crtc_pins & AM40010_DE) {
_am40010_decode_pixels(ga, dst, crtc_pins);
}
else if (black) {
_am40010_decode_pixels(ga, dst);
} else if (black) {
for (int i = 0; i < 16; i++) {
*dst++ = 63; // special 'pure black' hw color
}
}
else {
} else {
for (int i = 0; i < 16; i++) {
*dst++ = ga->regs.border;
}
}
}
}

// the actions which need to happen on CCLK (1 MHz frequency)
static inline void _am40010_do_cclk(am40010_t* ga, uint64_t crtc_pins) {
bool sync = _am40010_sync_irq(ga, crtc_pins);
_am40010_crt_tick(ga, sync);
_am40010_decode_video(ga, crtc_pins);
}

// the tick function must be called at 4 MHz
uint64_t am40010_tick(am40010_t* ga, uint64_t pins) {
/* The hardware has a 'main sequencer' with a rotating bit
Expand All @@ -889,21 +874,29 @@ uint64_t am40010_tick(am40010_t* ga, uint64_t pins) {
NOTE: Logon's Run crashes on rdy:1 and rdy:2
*/
const bool rdy = 0 != (ga->seq_tick_count & 3);
const bool cclk = 1 == (ga->seq_tick_count & 3);
const bool rdy = 0 != (ga->seq_tick_count & 3);
const bool cclk1 = 1 == (ga->seq_tick_count & 3);
const bool cclk0 = 3 == (ga->seq_tick_count & 3);
if (rdy) {
// READY is connected to Z80 WAIT, this sets the WAIT pin
// in 3 out of 4 CPU clock cycles
pins |= AM40010_READY;
}
else {
} else {
pins &= ~AM40010_READY;
}
if (cclk) {
if (cclk0) {
// read first video ram byte
uint64_t crtc_pins = ga->cclk_cb(ga->user_data);
_am40010_do_cclk(ga, crtc_pins);
ga->video.latch[0] = _am40010_vid_read(ga, crtc_pins, 0);
bool sync = _am40010_sync_irq(ga, crtc_pins);
_am40010_crt_tick(ga, sync);
ga->crtc_pins = crtc_pins;
}
if (cclk1) {
// read second video ram byte
ga->video.latch[1] = _am40010_vid_read(ga, ga->crtc_pins, 1);
_am40010_decode_video(ga, ga->crtc_pins);
}

// perform the per-4Mhz-tick actions, the AM40010_READY pin is also the Z80_WAIT pin
if ((ga->regs.config & AM40010_CONFIG_IRQRESET) != 0) {
Expand Down
53 changes: 53 additions & 0 deletions systems/c64.h
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,14 @@ bool c64_is_tape_motor_on(c64_t* sys);
uint32_t c64_save_snapshot(c64_t* sys, c64_t* dst);
// load a snapshot, returns false if snapshot versions don't match
bool c64_load_snapshot(c64_t* sys, uint32_t version, c64_t* src);
// perform a RUN BASIC call
void c64_basic_run(c64_t* sys);
// perform a LOAD BASIC call
void c64_basic_load(c64_t* sys);
// perform a SYS xxxx BASIC call via the BASIC input buffer
void c64_basic_syscall(c64_t* sys, uint16_t addr);
// returns the SYS call return address (can be used to set a breakpoint)
uint16_t c64_syscall_return_addr(void);

#ifdef __cplusplus
} // extern "C"
Expand Down Expand Up @@ -1163,4 +1171,49 @@ bool c64_load_snapshot(c64_t* sys, uint32_t version, c64_t* src) {
return true;
}

void c64_basic_run(c64_t* sys) {
CHIPS_ASSERT(sys);
// write RUN into the keyboard buffer
uint16_t keybuf = 0x277;
mem_wr(&sys->mem_cpu, keybuf++, 'R');
mem_wr(&sys->mem_cpu, keybuf++, 'U');
mem_wr(&sys->mem_cpu, keybuf++, 'N');
mem_wr(&sys->mem_cpu, keybuf++, 0x0D);
// write number of characters, this kicks off evaluation
mem_wr(&sys->mem_cpu, 0xC6, 4);
}

void c64_basic_load(c64_t* sys) {
CHIPS_ASSERT(sys);
// write LOAD
uint16_t keybuf = 0x277;
mem_wr(&sys->mem_cpu, keybuf++, 'L');
mem_wr(&sys->mem_cpu, keybuf++, 'O');
mem_wr(&sys->mem_cpu, keybuf++, 'A');
mem_wr(&sys->mem_cpu, keybuf++, 'D');
mem_wr(&sys->mem_cpu, keybuf++, 0x0D);
// write number of characters, this kicks off evaluation
mem_wr(&sys->mem_cpu, 0xC6, 5);
}

void c64_basic_syscall(c64_t* sys, uint16_t addr) {
CHIPS_ASSERT(sys);
// write SYS xxxx[Return] into the keyboard buffer (up to 10 chars)
uint16_t keybuf = 0x277;
mem_wr(&sys->mem_cpu, keybuf++, 'S');
mem_wr(&sys->mem_cpu, keybuf++, 'Y');
mem_wr(&sys->mem_cpu, keybuf++, 'S');
mem_wr(&sys->mem_cpu, keybuf++, ((addr / 10000) % 10) + '0');
mem_wr(&sys->mem_cpu, keybuf++, ((addr / 1000) % 10) + '0');
mem_wr(&sys->mem_cpu, keybuf++, ((addr / 100) % 10) + '0');
mem_wr(&sys->mem_cpu, keybuf++, ((addr / 10) % 10) + '0');
mem_wr(&sys->mem_cpu, keybuf++, ((addr / 1) % 10) + '0');
mem_wr(&sys->mem_cpu, keybuf++, 0x0D);
// write number of characters, this kicks off evaluation
mem_wr(&sys->mem_cpu, 0xC6, 9);
}

uint16_t c64_syscall_return_addr(void) {
return 0xA7EA;
}
#endif /* CHIPS_IMPL */
Loading

0 comments on commit 9a7f6d6

Please sign in to comment.