Implement Hardware I/O Simulator (OUT/INP/WAIT/MOTOR, port emulation)
New portio.c module following the virmem.c dispatch-by-address pattern, emulating 8253 PIT channel 2 (speaker frequency), PPI port B (speaker on/off), CGA mode/color select registers, game port (joystick stub), COM1 serial (transmitter-ready stub), and floating bus default (0xFF). OUT/WAIT/MOTOR statements and INP()/STICK()/STRIG() functions now fully functional. Continuous tone generation via PulseAudio pthread worker for programs that drive the speaker through OUT &H43/&H42/&H61.
This commit is contained in:
+2
-1
@@ -38,6 +38,7 @@ set(SOURCES
|
||||
src/print_using.c
|
||||
src/graphics.c
|
||||
src/virmem.c
|
||||
src/portio.c
|
||||
src/sound.c
|
||||
src/tui.c
|
||||
platform/hal_posix.c
|
||||
@@ -49,5 +50,5 @@ target_link_libraries(gwbasic m)
|
||||
if(PULSEAUDIO_FOUND)
|
||||
target_compile_definitions(gwbasic PRIVATE HAVE_PULSEAUDIO)
|
||||
target_include_directories(gwbasic PRIVATE ${PULSEAUDIO_INCLUDE_DIRS})
|
||||
target_link_libraries(gwbasic ${PULSEAUDIO_LIBRARIES})
|
||||
target_link_libraries(gwbasic ${PULSEAUDIO_LIBRARIES} pthread)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef PORTIO_H
|
||||
#define PORTIO_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Read a byte from an I/O port */
|
||||
uint8_t portio_inp(uint16_t port);
|
||||
|
||||
/* Write a byte to an I/O port */
|
||||
void portio_out(uint16_t port, uint8_t value);
|
||||
|
||||
/* Reset all port state (called on NEW, CLEAR, RUN) */
|
||||
void portio_reset(void);
|
||||
|
||||
#endif
|
||||
@@ -8,5 +8,7 @@ void snd_beep(void);
|
||||
void snd_tone(int freq_hz, int duration_ticks);
|
||||
void snd_tone_sync(int freq_hz, int duration_ticks);
|
||||
void snd_play(const char *mml);
|
||||
void snd_start_continuous(int freq_hz);
|
||||
void snd_stop_continuous(void);
|
||||
|
||||
#endif
|
||||
|
||||
+20
-2
@@ -2,6 +2,7 @@
|
||||
#include "tui.h"
|
||||
#include "graphics.h"
|
||||
#include "virmem.h"
|
||||
#include "portio.h"
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -700,9 +701,26 @@ static gw_value_t eval_function(uint8_t prefix, uint8_t func_tok)
|
||||
return v;
|
||||
}
|
||||
|
||||
case FUNC_INP:
|
||||
case FUNC_INP: {
|
||||
gw_expect('(');
|
||||
uint16_t port = gw_eval_uint16();
|
||||
gw_expect_rparen();
|
||||
v.type = VT_INT;
|
||||
v.ival = portio_inp(port);
|
||||
return v;
|
||||
}
|
||||
|
||||
case FUNC_STICK: {
|
||||
gw_expect('(');
|
||||
int arg = gw_eval_int();
|
||||
gw_expect_rparen();
|
||||
if (arg < 0 || arg > 3) gw_error(ERR_FC);
|
||||
v.type = VT_INT;
|
||||
v.ival = 128; /* center position */
|
||||
return v;
|
||||
}
|
||||
|
||||
case FUNC_PEN:
|
||||
case FUNC_STICK:
|
||||
case FUNC_STRIG:
|
||||
gw_expect('(');
|
||||
gw_eval();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "tui.h"
|
||||
#include "graphics.h"
|
||||
#include "virmem.h"
|
||||
#include "portio.h"
|
||||
#include "sound.h"
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
@@ -1375,6 +1376,7 @@ void gw_exec_stmt(void)
|
||||
if (tok == TOK_NEW) {
|
||||
gw_chrget();
|
||||
gfx_shutdown();
|
||||
portio_reset();
|
||||
gw_free_program();
|
||||
gw_vars_clear();
|
||||
gw_arrays_clear();
|
||||
@@ -1399,6 +1401,7 @@ void gw_exec_stmt(void)
|
||||
/* CLEAR */
|
||||
if (tok == TOK_CLEAR) {
|
||||
gw_chrget();
|
||||
portio_reset();
|
||||
gw_vars_clear();
|
||||
gw_arrays_clear();
|
||||
gw_file_close_all();
|
||||
@@ -1452,6 +1455,7 @@ void gw_exec_stmt(void)
|
||||
|
||||
if (!start) return;
|
||||
|
||||
portio_reset();
|
||||
gw_vars_clear();
|
||||
gw_arrays_clear();
|
||||
memset(gw.fn_defs, 0, sizeof(gw.fn_defs));
|
||||
@@ -2494,6 +2498,49 @@ void gw_exec_stmt(void)
|
||||
return;
|
||||
}
|
||||
|
||||
/* OUT port, value */
|
||||
if (tok == TOK_OUT) {
|
||||
gw_chrget();
|
||||
uint16_t port = gw_eval_uint16();
|
||||
gw_skip_spaces();
|
||||
gw_expect(',');
|
||||
int val = gw_eval_int();
|
||||
if (val < 0 || val > 255) gw_error(ERR_FC);
|
||||
portio_out(port, (uint8_t)val);
|
||||
return;
|
||||
}
|
||||
|
||||
/* WAIT port, mask [, xor_mask] */
|
||||
if (tok == TOK_WAIT) {
|
||||
gw_chrget();
|
||||
uint16_t port = gw_eval_uint16();
|
||||
gw_skip_spaces();
|
||||
gw_expect(',');
|
||||
int mask = gw_eval_int();
|
||||
if (mask < 0 || mask > 255) gw_error(ERR_FC);
|
||||
int xor_mask = 0;
|
||||
gw_skip_spaces();
|
||||
if (gw_chrgot() == ',') {
|
||||
gw_chrget();
|
||||
xor_mask = gw_eval_int();
|
||||
if (xor_mask < 0 || xor_mask > 255) gw_error(ERR_FC);
|
||||
}
|
||||
while (((portio_inp(port) ^ xor_mask) & mask) == 0) {
|
||||
if (tui.active)
|
||||
tui_check_break();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* MOTOR [n] — silently ignored */
|
||||
if (tok == TOK_MOTOR) {
|
||||
gw_chrget();
|
||||
gw_skip_spaces();
|
||||
if (gw_chrgot() && gw_chrgot() != ':' && gw_chrgot() != 0)
|
||||
gw_eval(); /* consume optional argument */
|
||||
return;
|
||||
}
|
||||
|
||||
/* WIDTH */
|
||||
if (tok == TOK_WIDTH) {
|
||||
gw_chrget();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "gwbasic.h"
|
||||
#include "tui.h"
|
||||
#include "sound.h"
|
||||
#include "portio.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -169,6 +170,7 @@ int main(int argc, char **argv)
|
||||
gw_hal->init();
|
||||
gw_init();
|
||||
snd_init();
|
||||
portio_reset();
|
||||
|
||||
int interactive = isatty(fileno(stdin));
|
||||
|
||||
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
#include "portio.h"
|
||||
#include "sound.h"
|
||||
#include "graphics.h"
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Hardware I/O port emulation.
|
||||
*
|
||||
* Dispatch-by-port pattern (cf. virmem.c dispatch-by-address).
|
||||
* Default: reads return 0xFF (floating bus), writes silently discarded.
|
||||
*/
|
||||
|
||||
/* --- 8253 PIT Channel 2 (ports 0x42-0x43) --- */
|
||||
|
||||
#define PIT_CLOCK 1193180 /* 8253 input clock frequency */
|
||||
|
||||
static uint8_t pit_ctrl; /* last control word written to 0x43 */
|
||||
static uint16_t pit_divisor; /* frequency divisor (lobyte | hibyte<<8) */
|
||||
static int pit_latch; /* 0 = expecting lobyte, 1 = expecting hibyte */
|
||||
|
||||
static uint8_t pit_inp(uint16_t port)
|
||||
{
|
||||
if (port == 0x42)
|
||||
return (uint8_t)(pit_divisor & 0xFF);
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
static void pit_out(uint16_t port, uint8_t value)
|
||||
{
|
||||
if (port == 0x43) {
|
||||
pit_ctrl = value;
|
||||
pit_latch = 0;
|
||||
return;
|
||||
}
|
||||
/* port 0x42: channel 2 data */
|
||||
if (pit_latch == 0) {
|
||||
pit_divisor = (pit_divisor & 0xFF00) | value;
|
||||
pit_latch = 1;
|
||||
} else {
|
||||
pit_divisor = (pit_divisor & 0x00FF) | ((uint16_t)value << 8);
|
||||
pit_latch = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- PPI Port B (port 0x61) --- */
|
||||
|
||||
static uint8_t ppi_portb;
|
||||
|
||||
static void ppi_update_speaker(void)
|
||||
{
|
||||
if ((ppi_portb & 0x03) == 0x03 && pit_divisor > 0)
|
||||
snd_start_continuous(PIT_CLOCK / pit_divisor);
|
||||
else
|
||||
snd_stop_continuous();
|
||||
}
|
||||
|
||||
static uint8_t ppi_inp(void)
|
||||
{
|
||||
return ppi_portb;
|
||||
}
|
||||
|
||||
static void ppi_out(uint8_t value)
|
||||
{
|
||||
uint8_t old = ppi_portb;
|
||||
ppi_portb = value;
|
||||
if ((old & 0x03) != (value & 0x03))
|
||||
ppi_update_speaker();
|
||||
}
|
||||
|
||||
/* --- CGA Mode Control Register (port 0x3D8) --- */
|
||||
|
||||
static uint8_t cga_mode;
|
||||
|
||||
static void cga_mode_out(uint8_t value)
|
||||
{
|
||||
uint8_t old_bits = cga_mode & 0x12; /* bits 1 and 4 */
|
||||
cga_mode = value;
|
||||
uint8_t new_bits = value & 0x12;
|
||||
|
||||
if (old_bits == new_bits)
|
||||
return;
|
||||
|
||||
if (new_bits & 0x10)
|
||||
gfx_init(2); /* 640x200 */
|
||||
else if (new_bits & 0x02)
|
||||
gfx_init(1); /* 320x200 */
|
||||
else
|
||||
gfx_shutdown(); /* text mode */
|
||||
}
|
||||
|
||||
/* --- CGA Color Select Register (port 0x3D9) --- */
|
||||
|
||||
static uint8_t cga_color;
|
||||
|
||||
static void cga_color_out(uint8_t value)
|
||||
{
|
||||
cga_color = value;
|
||||
/* Background color (bits 0-3) → palette attribute 0 */
|
||||
gfx_palette_set(0, value & 0x0F);
|
||||
/* Palette select (bit 5): 0 = green/red/yellow, 1 = cyan/magenta/white */
|
||||
if (value & 0x20) {
|
||||
gfx_palette_set(1, 3); /* cyan */
|
||||
gfx_palette_set(2, 5); /* magenta */
|
||||
gfx_palette_set(3, 7); /* white */
|
||||
} else {
|
||||
gfx_palette_set(1, 2); /* green */
|
||||
gfx_palette_set(2, 4); /* red */
|
||||
gfx_palette_set(3, 6); /* yellow/brown */
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Game Port (port 0x201) --- */
|
||||
|
||||
#define GAME_PORT_NO_JOYSTICK 0xF0 /* bits 4-7 set = no buttons */
|
||||
|
||||
/* --- COM1 Serial (ports 0x3F8-0x3FE) --- */
|
||||
|
||||
static uint8_t com1_regs[7]; /* 0x3F8..0x3FE */
|
||||
|
||||
static uint8_t com1_inp(uint16_t port)
|
||||
{
|
||||
int reg = port - 0x3F8;
|
||||
if (reg == 5) /* LSR: transmitter ready */
|
||||
return 0x60;
|
||||
return com1_regs[reg];
|
||||
}
|
||||
|
||||
static void com1_out(uint16_t port, uint8_t value)
|
||||
{
|
||||
int reg = port - 0x3F8;
|
||||
if (reg >= 0 && reg < 7)
|
||||
com1_regs[reg] = value;
|
||||
}
|
||||
|
||||
/* --- Public API --- */
|
||||
|
||||
uint8_t portio_inp(uint16_t port)
|
||||
{
|
||||
/* PIT channel 2 */
|
||||
if (port == 0x42 || port == 0x43)
|
||||
return pit_inp(port);
|
||||
|
||||
/* PPI port B */
|
||||
if (port == 0x61)
|
||||
return ppi_inp();
|
||||
|
||||
/* CGA registers */
|
||||
if (port == 0x3D8)
|
||||
return cga_mode;
|
||||
if (port == 0x3D9)
|
||||
return cga_color;
|
||||
|
||||
/* Game port */
|
||||
if (port == 0x201)
|
||||
return GAME_PORT_NO_JOYSTICK;
|
||||
|
||||
/* COM1 */
|
||||
if (port >= 0x3F8 && port <= 0x3FE)
|
||||
return com1_inp(port);
|
||||
|
||||
/* Floating bus */
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void portio_out(uint16_t port, uint8_t value)
|
||||
{
|
||||
/* PIT channel 2 */
|
||||
if (port == 0x42 || port == 0x43) {
|
||||
pit_out(port, value);
|
||||
return;
|
||||
}
|
||||
|
||||
/* PPI port B */
|
||||
if (port == 0x61) {
|
||||
ppi_out(value);
|
||||
return;
|
||||
}
|
||||
|
||||
/* CGA registers */
|
||||
if (port == 0x3D8) {
|
||||
cga_mode_out(value);
|
||||
return;
|
||||
}
|
||||
if (port == 0x3D9) {
|
||||
cga_color_out(value);
|
||||
return;
|
||||
}
|
||||
|
||||
/* COM1 */
|
||||
if (port >= 0x3F8 && port <= 0x3FE) {
|
||||
com1_out(port, value);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Default: silently discard */
|
||||
}
|
||||
|
||||
void portio_reset(void)
|
||||
{
|
||||
snd_stop_continuous();
|
||||
|
||||
pit_ctrl = 0;
|
||||
pit_divisor = 0;
|
||||
pit_latch = 0;
|
||||
|
||||
ppi_portb = 0;
|
||||
|
||||
cga_mode = 0;
|
||||
cga_color = 0;
|
||||
|
||||
memset(com1_regs, 0, sizeof(com1_regs));
|
||||
}
|
||||
+63
@@ -3,10 +3,12 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef HAVE_PULSEAUDIO
|
||||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#define SAMPLE_RATE 44100
|
||||
@@ -55,6 +57,7 @@ void snd_init(void)
|
||||
|
||||
void snd_shutdown(void)
|
||||
{
|
||||
snd_stop_continuous();
|
||||
#ifdef HAVE_PULSEAUDIO
|
||||
if (pa_conn) {
|
||||
pa_simple_drain(pa_conn, NULL);
|
||||
@@ -309,3 +312,63 @@ void snd_play(const char *mml)
|
||||
pa_drain();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* --- Continuous tone for PPI speaker emulation --- */
|
||||
|
||||
#ifdef HAVE_PULSEAUDIO
|
||||
static volatile bool cont_running;
|
||||
static pthread_t cont_thread;
|
||||
static int cont_freq;
|
||||
static bool cont_active;
|
||||
|
||||
static void *cont_worker(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
int16_t buf[4096];
|
||||
double phase = 0.0;
|
||||
double phase_inc = 2.0 * M_PI * cont_freq / SAMPLE_RATE;
|
||||
|
||||
while (cont_running) {
|
||||
for (int i = 0; i < 4096; i++) {
|
||||
buf[i] = (int16_t)(sin(phase) * 24000.0);
|
||||
phase += phase_inc;
|
||||
if (phase >= 2.0 * M_PI)
|
||||
phase -= 2.0 * M_PI;
|
||||
}
|
||||
pa_simple_write(pa_conn, buf, sizeof(buf), NULL);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
void snd_start_continuous(int freq_hz)
|
||||
{
|
||||
#ifdef HAVE_PULSEAUDIO
|
||||
snd_stop_continuous();
|
||||
|
||||
if (freq_hz < 37)
|
||||
return;
|
||||
|
||||
pa_open();
|
||||
if (!pa_conn)
|
||||
return;
|
||||
|
||||
cont_freq = freq_hz;
|
||||
cont_running = true;
|
||||
cont_active = true;
|
||||
pthread_create(&cont_thread, NULL, cont_worker, NULL);
|
||||
#else
|
||||
(void)freq_hz;
|
||||
#endif
|
||||
}
|
||||
|
||||
void snd_stop_continuous(void)
|
||||
{
|
||||
#ifdef HAVE_PULSEAUDIO
|
||||
if (!cont_active)
|
||||
return;
|
||||
cont_running = false;
|
||||
pthread_join(cont_thread, NULL);
|
||||
cont_active = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
INP(0)= 255
|
||||
INP(&H201)= 240
|
||||
COM1 LSR= 96
|
||||
PPI= 0
|
||||
CGA MODE= 0
|
||||
MOTOR OK
|
||||
STICK(0)= 128
|
||||
STRIG(0)= 0
|
||||
DONE
|
||||
@@ -0,0 +1 @@
|
||||
SPEAKER OK
|
||||
@@ -0,0 +1,25 @@
|
||||
10 REM Test OUT/INP/WAIT/MOTOR port I/O
|
||||
20 REM --- INP from unhandled port returns 0xFF (floating bus)
|
||||
30 PRINT "INP(0)="; INP(0)
|
||||
40 REM --- INP from game port returns 0xF0
|
||||
50 PRINT "INP(&H201)="; INP(&H201)
|
||||
60 REM --- OUT to unhandled port does not crash
|
||||
70 OUT 0, 0
|
||||
80 REM --- COM1 LSR reports transmitter ready
|
||||
90 PRINT "COM1 LSR="; INP(&H3FD)
|
||||
100 REM --- PPI port B read/write
|
||||
110 OUT &H61, 0
|
||||
120 PRINT "PPI="; INP(&H61)
|
||||
130 REM --- CGA mode register
|
||||
140 OUT &H3D8, 0
|
||||
150 PRINT "CGA MODE="; INP(&H3D8)
|
||||
160 REM --- MOTOR is silently accepted
|
||||
170 MOTOR
|
||||
180 MOTOR 0
|
||||
190 MOTOR 1
|
||||
200 PRINT "MOTOR OK"
|
||||
210 REM --- STICK and STRIG return defaults
|
||||
220 PRINT "STICK(0)="; STICK(0)
|
||||
230 PRINT "STRIG(0)="; STRIG(0)
|
||||
240 PRINT "DONE"
|
||||
250 SYSTEM
|
||||
@@ -0,0 +1,9 @@
|
||||
10 REM Test speaker via OUT (PIT + PPI)
|
||||
20 OUT &H43, &HB6
|
||||
30 OUT &H42, &HD3
|
||||
40 OUT &H42, &H04
|
||||
50 OUT &H61, INP(&H61) OR 3
|
||||
60 REM Speaker on briefly
|
||||
70 OUT &H61, INP(&H61) AND &HFC
|
||||
80 PRINT "SPEAKER OK"
|
||||
90 SYSTEM
|
||||
Reference in New Issue
Block a user