Clean up the CPU/ChipSet interface, and get the PC8080 Video component ready to actually do something
This commit is contained in:
parent
ac3dc76285
commit
8c5ab8d73b
19 changed files with 1210 additions and 993 deletions
|
|
@ -46,11 +46,11 @@ The idea is to make [PC8080](/modules/pc8080/) sufficiently configurable so that
|
|||
[Space Invaders](/devices/pc8080/machine/invaders/)), as well as simpler terminal-based systems, like the CP/M-based
|
||||
systems of old.
|
||||
|
||||
In fact, as soon as Space Invaders is working, my next planned adaptation is a DEC VT100 terminal emulator (itself
|
||||
an 8080-based machine) which can then be "wired up" to other PCjs machine simulations. This will not be yet-another
|
||||
VT100-compatible emulation -- which, like 8080 emulators, has been done to death -- but rather a simulation
|
||||
of the original VT100 hardware, building on [Adam Mayer's](https://github.com/phooky) work
|
||||
[reverse-engineering the VT100](https://github.com/phooky/VT100-Hax).
|
||||
In fact, as soon as [Space Invaders](/devices/pc8080/machine/invaders/) is working, my next planned adaptation
|
||||
is a DEC VT100 terminal emulator (itself an 8080-based machine) which can then be "wired up" to other PCjs machine
|
||||
simulations. This will not be yet-another VT100-compatible emulation -- which, like 8080 emulators, has been done to
|
||||
death -- but rather a simulation of the original VT100 hardware, building on [Adam Mayer's](https://github.com/phooky)
|
||||
work [reverse-engineering the VT100](https://github.com/phooky/VT100-Hax).
|
||||
|
||||
*[@jeffpar](http://twitter.com/jeffpar)*
|
||||
*April 30, 2016*
|
||||
|
|
|
|||
|
|
@ -24,5 +24,64 @@ Assorted [Space Invaders Hardware Notes](#space-invaders-hardware-notes) are col
|
|||
Space Invaders Hardware Notes
|
||||
---
|
||||
|
||||
### Memory Map
|
||||
|
||||
0000-1FFF r ROM code
|
||||
2000-23FF rw RAM
|
||||
2400-3FFF rw bitmapped screen (224x256)
|
||||
|
||||
### I/O Map
|
||||
|
||||
00 r
|
||||
01 r control inputs
|
||||
bit 7
|
||||
bit 6 right
|
||||
bit 5 left
|
||||
bit 4 fire
|
||||
bit 3
|
||||
bit 2 1 player start
|
||||
bit 1 2 player start
|
||||
bit 0 coin slot
|
||||
02 r control inputs
|
||||
bit 7 0=display coin info
|
||||
bit 6 right 2
|
||||
bit 5 left 2
|
||||
bit 4 fire 2
|
||||
bit 3 bonus (1500,1000) / preset mode
|
||||
bit 2 1=tilt
|
||||
bit 0-1 initial lives (3,4,5,6) / (3,4)
|
||||
02 w shift count (0-7)
|
||||
03 r shifted value (low then high)
|
||||
03 w sound
|
||||
bit 4 bonus base
|
||||
bit 3 invader hit
|
||||
bit 2 base hit
|
||||
bit 1 base fire
|
||||
bit 0 saucer
|
||||
04 w value to shift
|
||||
05 w sound
|
||||
bit 5 flip video
|
||||
bit 4 saucer hit
|
||||
bit 3 invader movement 4
|
||||
bit 2 invader movement 3
|
||||
bit 1 invader movement 2
|
||||
bit 0 invader movement 1
|
||||
06 w watchdog timer clear
|
||||
|
||||
Note that, unlike most emulators, PC8080 (like PCjs) has a *[Bus](/modules/pc8080/lib/bus.js)* architecture,
|
||||
allowing components to "plug in" different kinds of memory or memory-mapped devices at different addresses,
|
||||
and to register specific functions for specific I/O ports.
|
||||
|
||||
For example, Space Invaders has 8Kb of ROM at addresses 0x0000 through 0x1FFF, which means that if any 8080 code
|
||||
attempts to write to those addresses, nothing should happen. Unfortunately, most emulators treat the entire address
|
||||
space as one contiguous array of bytes. Which means either that the ROM is susceptible to corruption *or* that every
|
||||
write operation must check the address to determine its validity, which hurts the performance of *all* writes.
|
||||
|
||||
In PC8080 (and PCjs), all writes are equally fast, and all ROMs are fully protected. An exception is made for the
|
||||
Debugger, which allows you to use the "e" command to modify ("patch") ROM code on the fly, but that is completely
|
||||
outside and independent of the 8080 code being emulated.
|
||||
|
||||
### Other Online References
|
||||
|
||||
See [Computer Archeology](http://www.computerarcheology.com/Arcade/SpaceInvaders/) for an excellent collection
|
||||
of materials on the original Space Invaders, including commented ROM disassemblies.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
<rom id="romF" addr="0x1000" size="0x0800" file="/devices/pc8080/rom/invaders/INVADERS-F.json"/>
|
||||
<rom id="romE" addr="0x1800" size="0x0800" file="/devices/pc8080/rom/invaders/INVADERS-E.json"/>
|
||||
<ram id="ram" addr="0x2000" size="0x2000"/>
|
||||
<video id="video" screenWidth="224" screenHeight="256" frameBuffer="0x2400" interruptRate="120" rotation="90" width="40%" pos="left" padding="8px">
|
||||
<chipset id="chipset" model="SI1978"/>
|
||||
<video id="video" screenWidth="224" screenHeight="256" bufferAddr="0x2400" bufferCols="256" bufferRows="224" bufferBits="1" interruptRate="120" rotation="90" width="40%" pos="left" padding="8px">
|
||||
<menu>
|
||||
<title>224x256 Screen (Rotated)</title>
|
||||
</menu>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -68,7 +68,7 @@ function ChipSet(parmsChipSet)
|
|||
Component.notice("Unrecognized ChipSet model: " + model);
|
||||
}
|
||||
|
||||
this.model = ChipSet.MODELS[model];
|
||||
this.model = ChipSet.MODELS[model] || ChipSet.SI_1978.MODEL;
|
||||
|
||||
this.bSwitches = this.parseDIPSwitches(parmsChipSet['swDIP']);
|
||||
|
||||
|
|
@ -276,7 +276,7 @@ ChipSet.prototype.reset = function()
|
|||
this.bStatus0 = 0;
|
||||
this.bStatus1 = 0;
|
||||
this.bStatus2 = 0;
|
||||
this.bShiftData = 0;
|
||||
this.wShiftData = 0;
|
||||
this.bShiftCount = 0;
|
||||
this.bSound1 = this.bSound2 = 0;
|
||||
};
|
||||
|
|
@ -293,7 +293,7 @@ ChipSet.prototype.save = function()
|
|||
{
|
||||
var state = new State(this);
|
||||
if (this.model == ChipSet.SI_1978.MODEL) {
|
||||
state.set(0, [this.bStatus0, this.bStatus1, this.bStatus2, this.bShiftData, this.bShiftCount, this.bSound1, this.bSound2]);
|
||||
state.set(0, [this.bStatus0, this.bStatus1, this.bStatus2, this.wShiftData, this.bShiftCount, this.bSound1, this.bSound2]);
|
||||
}
|
||||
return state.data();
|
||||
};
|
||||
|
|
@ -315,7 +315,7 @@ ChipSet.prototype.restore = function(data)
|
|||
this.bStatus0 = a[0];
|
||||
this.bStatus1 = a[1];
|
||||
this.bStatus2 = a[2];
|
||||
this.bShiftData = a[3];
|
||||
this.wShiftData = a[3];
|
||||
this.bShiftCount = a[4];
|
||||
this.bSound1 = a[5];
|
||||
this.bSound2 = a[6];
|
||||
|
|
@ -323,6 +323,34 @@ ChipSet.prototype.restore = function(data)
|
|||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* start()
|
||||
*
|
||||
* Notification from the CPU that it's starting.
|
||||
*
|
||||
* @this {ChipSet}
|
||||
*/
|
||||
ChipSet.prototype.start = function()
|
||||
{
|
||||
/*
|
||||
* Currently, all we (may) do with this notification is allow the speaker to make noise.
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
* stop()
|
||||
*
|
||||
* Notification from the CPU that it's stopping.
|
||||
*
|
||||
* @this {ChipSet}
|
||||
*/
|
||||
ChipSet.prototype.stop = function()
|
||||
{
|
||||
/*
|
||||
* Currently, all we (may) do with this notification is prevent the speaker from making noise.
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
* inSIStatus0(port, addrFrom)
|
||||
*
|
||||
|
|
@ -378,7 +406,7 @@ ChipSet.prototype.inSIStatus2 = function(port, addrFrom)
|
|||
*/
|
||||
ChipSet.prototype.inSIShiftResult = function(port, addrFrom)
|
||||
{
|
||||
var b = ((this.bShiftData << this.bShiftCount) >> 8) & 0xff;
|
||||
var b = (this.wShiftData >> (8 - this.bShiftCount)) & 0xff;
|
||||
this.printMessageIO(port, null, addrFrom, "SHIFT.RESULT", b, true);
|
||||
return b;
|
||||
};
|
||||
|
|
@ -422,7 +450,7 @@ ChipSet.prototype.outSISound1 = function(port, b, addrFrom)
|
|||
ChipSet.prototype.outSIShiftData = function(port, b, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, b, addrFrom, "SHIFT.DATA", null, true);
|
||||
this.bShiftData = b;
|
||||
this.wShiftData = (b << 8) | (this.wShiftData >> 8);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -439,6 +467,19 @@ ChipSet.prototype.outSISound2 = function(port, b, addrFrom)
|
|||
this.bSound2 = b;
|
||||
};
|
||||
|
||||
/**
|
||||
* outSIWatchdog(port, b, addrFrom)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} port (0x06)
|
||||
* @param {number} b
|
||||
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
|
||||
*/
|
||||
ChipSet.prototype.outSIWatchdog = function(port, b, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, b, addrFrom, "WATCHDOG", null, true);
|
||||
};
|
||||
|
||||
/*
|
||||
* Port input notification tables
|
||||
*/
|
||||
|
|
@ -456,7 +497,8 @@ ChipSet.aPortOutput = {
|
|||
0x02: ChipSet.prototype.outSIShiftCount,
|
||||
0x03: ChipSet.prototype.outSISound1,
|
||||
0x04: ChipSet.prototype.outSIShiftData,
|
||||
0x05: ChipSet.prototype.outSISound2
|
||||
0x05: ChipSet.prototype.outSISound2,
|
||||
0x06: ChipSet.prototype.outSIWatchdog
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1047,7 +1047,7 @@ CPU.prototype.startCPU = function(fUpdateFocus)
|
|||
if (this.cmp) this.cmp.start(this.aCounts.msStartRun, this.getCycles());
|
||||
this.flags.fRunning = true;
|
||||
this.flags.fStarting = true;
|
||||
if (this.chipset) this.chipset.setSpeaker();
|
||||
if (this.chipset) this.chipset.start();
|
||||
var controlRun = this.bindings["run"];
|
||||
if (controlRun) controlRun.textContent = "Halt";
|
||||
if (this.cmp) {
|
||||
|
|
@ -1091,7 +1091,7 @@ CPU.prototype.stopCPU = function(fComplete)
|
|||
this.nRunCycles = 0;
|
||||
if (this.flags.fRunning) {
|
||||
this.flags.fRunning = false;
|
||||
if (this.chipset) this.chipset.setSpeaker();
|
||||
if (this.chipset) this.chipset.stop();
|
||||
var controlRun = this.bindings["run"];
|
||||
if (controlRun) controlRun.textContent = "Run";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2362,7 +2362,7 @@ CPUDef.opJNC = function()
|
|||
CPUDef.opOUT = function()
|
||||
{
|
||||
var port = this.getPCByte();
|
||||
this.bus.checkPortOutputNotify(port, 1, this.regA);
|
||||
this.bus.checkPortOutputNotify(port, 1, this.regA, this.offPC(-2));
|
||||
this.nStepCycles -= 10;
|
||||
};
|
||||
|
||||
|
|
@ -2450,7 +2450,7 @@ CPUDef.opJC = function()
|
|||
CPUDef.opIN = function()
|
||||
{
|
||||
var port = this.getPCByte();
|
||||
this.regA = this.bus.checkPortInputNotify(port, 1) & 0xff;
|
||||
this.regA = this.bus.checkPortInputNotify(port, 1, this.offPC(-2)) & 0xff;
|
||||
this.nStepCycles -= 10;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -450,6 +450,18 @@ CPUSim.prototype.getPC = function()
|
|||
return this.regPC;
|
||||
};
|
||||
|
||||
/**
|
||||
* offPC()
|
||||
*
|
||||
* @this {CPUSim}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
CPUSim.prototype.offPC = function(off)
|
||||
{
|
||||
return (this.regPC + off) & 0xffff;
|
||||
};
|
||||
|
||||
/**
|
||||
* setPC(off)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1578,6 +1578,9 @@ if (DEBUGGER) {
|
|||
/**
|
||||
* messageIO(component, port, bOut, addrFrom, name, bIn, bitsMessage)
|
||||
*
|
||||
* Most (if not all) port handlers should provide a name for their respective ports, so if no name is provided,
|
||||
* we assume this is an unknown port, and display a message by default.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {Component} component
|
||||
* @param {number} port
|
||||
|
|
@ -1590,7 +1593,7 @@ if (DEBUGGER) {
|
|||
Debugger.prototype.messageIO = function(component, port, bOut, addrFrom, name, bIn, bitsMessage)
|
||||
{
|
||||
bitsMessage |= Messages.PORT;
|
||||
if (addrFrom == null || (this.bitsMessage & bitsMessage) == bitsMessage) {
|
||||
if (name == null || (this.bitsMessage & bitsMessage) == bitsMessage) {
|
||||
this.message(component.idComponent + '.' + (bOut != null? "outPort" : "inPort") + '(' + str.toHexWord(port) + ',' + (name? name : "unknown") + (bOut != null? ',' + str.toHexByte(bOut) : "") + ')' + (bIn != null? (": " + str.toHexByte(bIn)) : "") + (addrFrom != null? (" at " + this.toHexOffset(addrFrom)) : ""));
|
||||
}
|
||||
};
|
||||
|
|
@ -4499,7 +4502,11 @@ if (DEBUGGER) {
|
|||
{
|
||||
if (fSave) {
|
||||
if (!sCmd) {
|
||||
if (this.fAssemble) {
|
||||
sCmd = "end";
|
||||
} else {
|
||||
sCmd = this.aPrevCmds[this.iPrevCmd+1];
|
||||
}
|
||||
} else {
|
||||
if (this.iPrevCmd < 0 && this.aPrevCmds.length) {
|
||||
this.iPrevCmd = 0;
|
||||
|
|
|
|||
|
|
@ -50,11 +50,19 @@ if (NODE) {
|
|||
* screenWidth: width of the screen canvas, in pixels
|
||||
* screenHeight: height of the screen canvas, in pixels
|
||||
* screenColor: background color of the screen canvas (default is black)
|
||||
* aspectRatio
|
||||
* frameBuffer
|
||||
* interruptRate (eg, 120)
|
||||
* refreshRate (eg, 60)
|
||||
* rotation (eg, 90)
|
||||
* screenRotation: the amount of counter-clockwise rotation required (eg, 90)
|
||||
* aspectRatio (eg, 1.33)
|
||||
* bufferAddr: the starting address of the frame buffer (eg, 0x2400)
|
||||
* bufferCols: the width of a single frame buffer row, in pixels (eg, 256)
|
||||
* bufferRows: the number of frame buffer rows (eg, 224)
|
||||
* bufferBits: the number of bits per pixel (eg, 1)
|
||||
* interruptRate: normally the same as (or some multiple of) the refreshRate (eg, 120)
|
||||
* refreshRate: how many times updateScreen() should be called per second (eg, 60)
|
||||
*
|
||||
* We record all the above values now, but we defer creation of the frame buffer until our initBus()
|
||||
* handler is called. At that point, we will also compute the extent of the frame buffer, determine the
|
||||
* appropriate "cell" size (ie, the number of pixels that updateScreen() will fetch and process at once),
|
||||
* and allocate our cell cache.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ function ChipSet(parmsChipSet)
|
|||
Component.notice("Unrecognized ChipSet model: " + model);
|
||||
}
|
||||
|
||||
this.model = model && ChipSet.MODELS[model] || ChipSet.MODEL_5150_OTHER;
|
||||
this.model = ChipSet.MODELS[model] || ChipSet.MODEL_5150_OTHER;
|
||||
|
||||
var bSwitches;
|
||||
this.aDIPSwitches = [];
|
||||
|
|
@ -2126,6 +2126,36 @@ ChipSet.prototype.restore = function(data)
|
|||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* start()
|
||||
*
|
||||
* Notification from the CPU that it's starting.
|
||||
*
|
||||
* @this {ChipSet}
|
||||
*/
|
||||
ChipSet.prototype.start = function()
|
||||
{
|
||||
/*
|
||||
* Currently, all we do with this notification is allow the speaker to make noise.
|
||||
*/
|
||||
this.setSpeaker();
|
||||
};
|
||||
|
||||
/**
|
||||
* stop()
|
||||
*
|
||||
* Notification from the CPU that it's stopping.
|
||||
*
|
||||
* @this {ChipSet}
|
||||
*/
|
||||
ChipSet.prototype.stop = function()
|
||||
{
|
||||
/*
|
||||
* Currently, all we do with this notification is prevent the speaker from making noise.
|
||||
*/
|
||||
this.setSpeaker();
|
||||
};
|
||||
|
||||
ChipSet.aDMAControllerInit = [0, null, null, 0, new Array(4), 0];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1052,7 +1052,7 @@ CPU.prototype.startCPU = function(fUpdateFocus)
|
|||
if (this.cmp) this.cmp.start(this.aCounts.msStartRun, this.getCycles());
|
||||
this.flags.fRunning = true;
|
||||
this.flags.fStarting = true;
|
||||
if (this.chipset) this.chipset.setSpeaker();
|
||||
if (this.chipset) this.chipset.start();
|
||||
var controlRun = this.bindings["run"];
|
||||
if (controlRun) controlRun.textContent = "Halt";
|
||||
if (this.cmp) {
|
||||
|
|
@ -1096,7 +1096,7 @@ CPU.prototype.stopCPU = function(fComplete)
|
|||
this.nRunCycles = 0;
|
||||
if (this.flags.fRunning) {
|
||||
this.flags.fRunning = false;
|
||||
if (this.chipset) this.chipset.setSpeaker();
|
||||
if (this.chipset) this.chipset.stop();
|
||||
var controlRun = this.bindings["run"];
|
||||
if (controlRun) controlRun.textContent = "Run";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,18 +52,17 @@ if (NODE) {
|
|||
*
|
||||
* model: model (eg, "mda" for Monochrome Display Adapter)
|
||||
* mode: initial video mode (default is null, which selects a mode based on model)
|
||||
* memory: amount of installed memory (ignored for MDA/CGA)
|
||||
* screenWidth: width of the screen canvas, in pixels
|
||||
* screenHeight: height of the screen canvas, in pixels
|
||||
* screenColor: background color of the screen canvas (default is black)
|
||||
* scale: true for font scaling, false (default) to center the display on the screen
|
||||
* charCols: number of character columns
|
||||
* charRows: number of character rows
|
||||
* fontROM: path to .rom file (or a JSON representation) containing the character set
|
||||
* screenColor: background color of the screen canvas (default is black)
|
||||
* touchScreen: string specifying desired touch-screen support (default is none)
|
||||
* autoLock: true to (attempt to) auto-lock the mouse to the canvas (default is false)
|
||||
*
|
||||
* An EGA may specify the following additional properties:
|
||||
* An EGA/VGA may specify the following additional properties:
|
||||
*
|
||||
* switches: string representing EGA switches (see "SW1-SW4" documentation below)
|
||||
* memory: the size of the EGA's on-board memory (overrides EGA's Video.cardSpecs)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue