ScriptedAmigaEmulator/sae/cia.js
Rupert Hausberger 0c5b018866 update 0.7.0
2012-12-22 19:39:33 +01:00

738 lines
16 KiB
JavaScript

/**************************************************************************
* SAE - Scripted Amiga Emulator
*
* https://github.com/naTmeg/ScriptedAmigaEmulator
*
* ©2012 Rupert Hausberger
* Commercial use is prohibited.
*
**************************************************************************/
function CIA_8520(type) {
const STARTCYCLESHI = 3;
const STARTCYCLESCRA = 2;
this.type = type;
this.icr = 0;
this.icrReg = 0;
this.iMask = 0;
this.iFlag = type == CIA_A ? INTF_PORTS : INTF_EXTER; /* 0x0008 : 0x2000 */
this.cra = 0;
this.crb = 0;
this.dra = 0;
this.drb = 0;
this.pra = 0;
this.prb = 0;
this.sdr = 0;
this.sdrCnt = 0;
this.ta = 0;
this.tb = 0;
this.taPassed = 0;
this.tbPassed = 0;
this.la = 0;
this.lb = 0;
this.starta = 0;
this.startb = 0;
this.todOn = false;
this.tod = 0;
this.tol = 0;
this.tlatch = 0;
this.alarm = 0;
this.reset = function() {
this.icr = this.iMask = 0;
this.cra = this.crb = 0x4;
this.dra = this.drb = 0;
this.pra = this.type == CIA_A ? 0 : 0x8c;
this.sdr = 0;
this.sdrCnt = 0;
this.la = this.lb = this.ta = this.tb = 0xffff;
this.taPassed = this.tbPassed = 0;
this.tod = 0;
this.todOn = false;
this.tlatch = 0;
this.alarm = 0;
}
this.dump = function() {
BUG.info('CIA_8520.dump() %s | pra $%02x prb $%02x | dra $%02x drb $%02x | ta %5d (la %5d) tb %5d (lb %5d) | tod %5d (tol %5d) alarm %5d latch %d | sdr $%02x | icr $%02x iMask $%02x | cra $%02x crb $%02x',
this.type == CIA_A ? 'A' : 'B', this.pra, this.prb, this.dra, this.drb, this.ta, this.la, this.tb, this.lb, this.tod, this.tol, this.alarm, this.latch ? 1 : 0, this.sdr, this.icr, this.iMask, this.cra, this.crb);
}
this.setClr = function (val) {
if (val & 0x80) this.iMask |= val & 0x7f;
else this.iMask &= ~val;
}
this.setICR = function (icr, sdr) {
if (sdr !== null)
this.sdr = sdr;
this.icr |= icr;
this.reThink();
}
this.ICR = function() {
if (this.iMask & this.icr) {
this.icr |= 0x80;
AMIGA.INTREQ_0(INTF_SETCLR | this.iFlag);
}
this.icrReg |= this.icr;
}
this.reThink = function() {
if (this.icr) {
if (AMIGA.config.cpu.exact)
AMIGA.events.event2_newevent_xx(-1, CIA_RETHINK_DELAY, this.type, function(v) { AMIGA.cia.ICR(v); });
else
this.ICR();
}
}
this.updateCheck = function (ciaclocks) {
var ovfla = false;
var ovflb = false;
var sp = false;
var needReThink = false;
if ((this.cra & 0x21) == 0x01) {
//BUG.info('CIA.updateCheck() ciaclocks %d, starta %d, ta %d', ciaclocks, this.starta, this.ta);
var check = true;
var cc = ciaclocks;
if (this.starta > 0) {
if (cc > this.starta) {
cc -= this.starta;
this.starta = 0;
} else {
this.starta -= cc;
check = false;
}
}
if (check) {
//assert ((this.ta + 1) >= cc);
if ((this.ta + 1) == cc) {
if ((this.cra & 0x48) == 0x40 && this.sdrCnt > 0 && --this.sdrCnt == 0) sp = true;
ovfla = true;
if ((this.crb & 0x61) == 0x41 || (this.crb & 0x61) == 0x61) {
if (this.tb-- == 0) ovflb = true;
}
}
this.ta -= cc;
}
}
if ((this.crb & 0x61) == 0x01) {
//BUG.info('CIA.updateCheck() ciaclocks %d, startb %d, tb %d', ciaclocks, this.startb, this.tb);
var check = true;
var cc = ciaclocks;
if (this.startb > 0) {
if (cc > this.startb) {
cc -= this.startb;
this.startb = 0;
} else {
this.startb -= cc;
check = false;
}
}
if (check) {
//assert ((this.tb + 1) >= cc);
if ((this.tb + 1) == cc) ovflb = true;
this.tb -= cc;
}
}
if (ovfla) {
this.icr |= 1;
needReThink = true;
this.ta = this.la;
if (this.cra & 0x8) {
this.cra &= ~1;
}
}
if (ovflb) {
this.icr |= 2;
needReThink = true;
this.tb = this.lb;
if (this.crb & 0x8) {
this.crb &= ~1;
}
}
if (sp) {
this.icr |= 8;
needReThink = true;
}
return needReThink;
}
this.calcTimers = function (div10diff) {
var time = [-1, -1];
if ((this.cra & 0x21) == 0x01) time[0] = div10diff + DIV10 * (this.ta + this.starta);
if ((this.crb & 0x61) == 0x01) time[1] = div10diff + DIV10 * (this.tb + this.startb);
//if (time[0] != -1 || time[1] != -1) BUG.info('CIA.calcTimers() div10diff %d, timea %d, timeb %d', div10diff, time[0], time[1]);
return time;
}
this.checkAlarm = function (inc) {
var alarm;
if (this.tod == this.alarm) alarm = true;
else if (!inc) alarm = false;
else if (this.tod & 0x000fff) alarm = false;
else if (((this.tod - 1) & 0xfff000) == this.alarm) alarm = true;
else alarm = false;
if (alarm) {
this.icr |= 4;
this.reThink();
}
}
this.calcPassedTime = function() {
var ccount = AMIGA.events.currcycle - AMIGA.events.eventtab[EV_CIA].oldcycles + AMIGA.cia.div10;
var ciaclocks = Math.floor(ccount / DIV10);
this.taPassed = this.tbPassed = 0;
if ((this.cra & 0x21) == 0x01) {
var cc = ciaclocks;
if (cc > this.starta) cc -= this.starta;
else cc = 0;
//assert ((this.ta + 1) >= cc);
this.taPassed = cc;
}
if ((this.crb & 0x61) == 0x01) {
var cc = ciaclocks;
if (cc > this.startb) cc -= this.startb;
else cc = 0;
//assert ((this.tb + 1) >= cc);
this.tbPassed = cc;
}
}
this.read = function (addr) {
var reg = addr & 15;
var tmp;
this.calcPassedTime();
switch (reg) {
case 0: {
if (this.type == CIA_A) {
tmp = AMIGA.disk.status() & 0x3c;
if (AMIGA.config.ports[0].type == SAEV_Config_Ports_Type_Mouse) {
if (!AMIGA.input.mouse.button[0]) tmp |= 0x40;
if (this.dra & 0x40) tmp = (tmp & ~0x40) | (this.pra & 0x40);
} else if (AMIGA.config.ports[0].type == SAEV_Config_Ports_Type_Joy0) {
if (!AMIGA.input.joystick[0].button[0]) tmp |= 0x40;
if (this.dra & 0x40) tmp = (tmp & ~0x40) | (this.pra & 0x40);
} else tmp |= 0x40;
if (AMIGA.config.ports[1].type == SAEV_Config_Ports_Type_Joy1) {
if (!AMIGA.input.joystick[1].button[0]) tmp |= 0x80;
if (this.dra & 0x80) tmp = (tmp & ~0x80) | (this.pra & 0x80);
} else tmp |= 0x80;
tmp |= (this.pra | (this.dra ^ 3)) & 3;
} else {
//tmp = 0xf8;
tmp = AMIGA.serial.readStatus(this.dra);
}
return tmp;
}
case 1: {
if (this.type == CIA_A) {
tmp = 0;
} else {
tmp = this.prb;
}
if (this.crb & 2) {
var pb7 = 0;
if (this.crb & 4) pb7 = this.crb & 1;
tmp &= ~0x80;
tmp |= pb7 ? 0x80 : 0;
}
if (this.cra & 2) {
var pb6 = 0;
if (this.cra & 4) pb6 = this.cra & 1;
tmp &= ~0x40;
tmp |= pb6 ? 0x40 : 0;
}
return tmp;
}
case 2:
return this.dra;
case 3:
return this.drb;
case 4:
return ((this.ta - this.taPassed) & 0xff);
case 5:
return ((this.ta - this.taPassed) >> 8) & 0xff;
case 6:
return ((this.tb - this.tbPassed) & 0xff);
case 7:
return ((this.tb - this.tbPassed) >> 8) & 0xff;
case 8: {
if (this.tlatch) {
this.tlatch = 0;
return this.tol & 0xff;
} else return this.tod & 0xff;
}
case 9: {
if (this.tlatch) return (this.tol >> 8) & 0xff;
else return (this.tod >> 8) & 0xff;
}
case 10: {
if (!this.tlatch) {
if (!(this.crb & 0x80)) this.tlatch = 1;
this.tol = this.tod;
}
return (this.tol >>> 16) & 0xff;
}
case 12:
return this.sdr;
case 13: {
tmp = this.icrReg;
this.icr &= ~this.icrReg;
this.icrReg = 0;
this.reThink();
return tmp;
}
case 14:
return this.cra;
case 15:
return this.crb;
}
}
this.write = function (addr, val) {
var reg = addr & 15;
switch (reg) {
case 0: {
if (this.type == CIA_A) {
this.pra = (this.pra & 0x3c) | (val & 0xc3);
AMIGA.cia.handlePowerLED(this.pra, this.dra);
} else {
this.pra = val;
AMIGA.serial.writeStatus(this.pra, this.dra);
}
break;
}
case 1: {
if (this.type == CIA_A) {
this.prb = val;
} else {
this.prb = val;
AMIGA.disk.select(val);
}
break;
}
case 2: {
if (this.type == CIA_A) {
this.dra = val;
AMIGA.cia.handlePowerLED(this.pra, this.dra);
} else {
this.dra = val;
AMIGA.serial.writeStatus(this.pra, this.dra);
}
break;
}
case 3: {
if (this.type == CIA_A) {
this.drb = val;
} else {
this.drb = val;
}
break;
}
case 4: {
AMIGA.cia.update();
this.la = (this.la & 0xff00) | val;
AMIGA.cia.calcTimers();
break;
}
case 5: {
AMIGA.cia.update();
this.la = (this.la & 0x00ff) | (val << 8);
if ((this.cra & 1) == 0) this.ta = this.la;
if (this.cra & 8) {
this.ta = this.la;
this.cra |= 1;
this.starta = STARTCYCLESHI;
}
AMIGA.cia.calcTimers();
break;
}
case 6: {
AMIGA.cia.update();
this.lb = (this.lb & 0xff00) | val;
AMIGA.cia.calcTimers();
break;
}
case 7: {
AMIGA.cia.update();
this.lb = (this.lb & 0x00ff) | (val << 8);
if ((this.crb & 1) == 0) this.tb = this.lb;
if (this.crb & 8) {
this.tb = this.lb;
this.crb |= 1;
this.startb = STARTCYCLESHI;
}
AMIGA.cia.calcTimers();
break;
}
case 8: {
if (this.crb & 0x80) {
this.alarm = (this.alarm & 0xffff00) | val;
} else {
this.tod = (this.tod & 0xffff00) | val;
this.todOn = true;
this.checkAlarm(0);
}
break;
}
case 9: {
if (this.crb & 0x80) {
this.alarm = (this.alarm & 0xff00ff) | (val << 8);
} else {
this.tod = (this.tod & 0xff00ff) | (val << 8);
}
break;
}
case 10: {
if (this.crb & 0x80) {
this.alarm = (this.alarm & 0x00ffff) | (val << 16);
} else {
this.tod = (this.tod & 0x00ffff) | (val << 16);
this.todOn = false;
}
break;
}
case 12: {
AMIGA.cia.update();
this.sdr = val;
if (this.type == CIA_B && (this.cra & 0x40) == 0) this.sdrCnt = 0;
if ((this.cra & 0x41) == 0x41 && this.sdrCnt == 0) this.sdrCnt = 8 * 2;
AMIGA.cia.calcTimers();
break;
}
case 13: {
this.setClr(val);
break;
}
case 14: {
AMIGA.cia.update();
val &= 0x7f;
if ((val & 1) && !(this.cra & 1)) this.starta = STARTCYCLESCRA;
if (this.type == CIA_A && (val & 0x40) != (this.cra & 0x40)) {
AMIGA.input.keyboard.lostsynccnt = 0;
//BUG.info('KB_ACK %02x to %02x', val, this.cra);
}
this.cra = val;
if (this.cra & 0x10) {
this.cra &= ~0x10;
this.ta = this.la;
}
AMIGA.cia.calcTimers();
break;
}
case 15: {
AMIGA.cia.update();
if ((val & 1) && !(this.crb & 1)) this.startb = STARTCYCLESCRA;
this.crb = val;
if (this.crb & 0x10) {
this.crb &= ~0x10;
this.tb = this.lb;
}
AMIGA.cia.calcTimers();
break;
}
}
}
}
function CIA() {
this.A = new CIA_8520(CIA_A);
this.B = new CIA_8520(CIA_B);
this.div10 = 0;
var led = true;
var todHack = {
active: true,
enabled: false,
rate: 0.0,
tv: 0.0
};
this.setup = function() {
if (todHack.active) todHack.rate = 1000.0 / AMIGA.events.hz;
}
this.reset = function() {
this.A.reset();
this.B.reset();
this.div10 = 0;
this.calcTimers();
led = true;
AMIGA.disk.select_set(this.B.prb);
if (todHack.active) {
todHack.enabled = AMIGA.events.maxvpos * AMIGA.events.hz * 10;
todHack.tv = 0.0;
}
}
this.dump = function() {
this.A.dump();
this.B.dump();
}
this.handlePowerLED = function (pra, dra) {
var v = pra | (~dra & 0xff);
var newled = (v & 2) ? 0 : 1;
if (led != newled) {
led = newled;
AMIGA.audio.filter.led_filter_on = led;
AMIGA.config.hooks.power_led(led);
}
}
this.setICR = function (type, icr, sdr) {
if (type == CIA_A)
this.A.setICR(icr, sdr);
else
this.B.setICR(icr, sdr);
}
this.ICR = function(type) {
if (type == CIA_A)
this.A.ICR();
else
this.B.ICR();
}
this.reThink = function() {
this.A.reThink();
this.B.reThink();
}
this.update = function() {
var ccount = AMIGA.events.currcycle - AMIGA.events.eventtab[EV_CIA].oldcycles + this.div10;
var ciaclocks = Math.floor(ccount / DIV10);
this.div10 = ccount % DIV10;
//BUG.info('CIA.update() %d %d', ciaclocks, this.div10);
var a = this.A.updateCheck(ciaclocks);
var b = this.B.updateCheck(ciaclocks);
if (a) this.A.reThink();
if (b) this.B.reThink();
}
this.calcTimers = function() {
AMIGA.events.eventtab[EV_CIA].oldcycles = AMIGA.events.currcycle;
var div10diff = DIV10 - this.div10;
var a = this.A.calcTimers(div10diff);
var b = this.B.calcTimers(div10diff);
AMIGA.events.eventtab[EV_CIA].active = (a[0] != -1 || a[1] != -1 || b[0] != -1 || b[1] != -1);
if (AMIGA.events.eventtab[EV_CIA].active) {
var evtime = 0xffffffff;
if (a[0] != -1) evtime = a[0];
if (a[1] != -1 && a[1] < evtime) evtime = a[1];
if (b[0] != -1 && b[0] < evtime) evtime = b[0];
if (b[1] != -1 && b[1] < evtime) evtime = b[1];
AMIGA.events.eventtab[EV_CIA].evtime = evtime + AMIGA.events.currcycle;
//BUG.info('CIA.calcTimers() evtime %d', evtime);
}
AMIGA.events.schedule();
}
this.handler = function() {
this.update();
this.calcTimers();
}
this.handleTodHack = function() {
if (todHack.enabled > 1) {
todHack.enabled--;
if (todHack.enabled == 1) {
todHack.tv = new Date().getTime();
BUG.info('CIA.handleTodHack() enabled');
}
return;
}
if (new Date().getTime() - todHack.tv >= todHack.rate) {
this.A.tod++;
this.A.tod &= 0xffffff;
this.A.checkAlarm(0);
todHack.tv += todHack.rate;
}
}
this.hSyncPre = function() {
}
this.hSyncPost = function (doTod) {
if (this.B.todOn && doTod) {
this.B.tod++;
this.B.tod &= 0xffffff;
this.B.checkAlarm(1);
}
if (todHack.active && this.A.todOn) this.handleTodHack();
AMIGA.input.keyboard.hsync();
}
this.vSyncPre = function() {
this.handler();
AMIGA.input.keyboard.vsync();
}
this.vSyncPost = function (doTod) {
if (todHack.active && todHack.enabled == 1) return;
if (this.A.todOn && doTod) {
this.A.tod++;
this.A.tod &= 0xffffff;
this.A.checkAlarm(1);
}
}
this.waitPre = function() {
//if (AMIGA.config.cpu.cachesize) return;
//#ifndef CUSTOM_SIMPLE
/*var div = (AMIGA.events.currcycle - AMIGA.events.eventtab[EV_CIA].oldcycles) % DIV10;
var div2 = Math.floor(DIV10 * ECLOCK_DATA_CYCLE / 10);
var cycles;
if (div >= div2)
cycles = DIV10 - div + div2;
else if (div)
cycles = DIV10 + div2 - div;
else
cycles = div2 - div;
//BUG.info('CIA.waitPre() %d %d', div, cycles);
if (cycles) {
if (AMIGA.config.cpu.exact)
AMIGA.events.cycle(cycles);
else
AMIGA.events.cycle(cycles);
}*/
//#endif
}
this.waitPost = function () {
AMIGA.events.cycle(6 * CYCLE_UNIT / 2);
/*if (AMIGA.config.cpu.cachesize)
AMIGA.events.cycle(8 * CYCLE_UNIT /2);
else*/ {
var c = 6 * CYCLE_UNIT / 2;
if (AMIGA.config.cpu.exact)
AMIGA.events.cycle(c);
else
AMIGA.events.cycle(c);
}
}
this.load8 = function (addr) {
var v, r = (addr & 0xf00) >> 8;
this.waitPre();
switch ((addr >> 12) & 3) {
case 0:
v = (addr & 1) ? this.A.read(r) : this.B.read(r);
break;
case 1:
v = (addr & 1) ? 0xff : this.B.read(r);
break;
case 2:
v = (addr & 1) ? this.A.read(r) : 0xff;
break;
case 3: {
v = 0xff;
//if (AMIGA.config.cpu.model == 68000 && AMIGA.config.cpu.compatible) v = (addr & 1) ? regs.irc : regs.irc >> 8;
BUG.info('CIA.load8() unknown CIA address $%08x', addr);
break;
}
}
this.waitPost();
return v;
}
this.load16 = function (addr) {
var v, r = (addr & 0xf00) >> 8;
this.waitPre();
switch ((addr >> 12) & 3) {
case 0:
v = (this.B.read(r) << 8) | this.A.read(r);
break;
case 1:
v = (this.B.read(r) << 8) | 0xff;
break;
case 2:
v = (0xff << 8) | this.A.read(r);
break;
case 3: {
v = 0xffff;
//if (AMIGA.config.cpu.model == 68000 && AMIGA.config.cpu.compatible) v = regs.irc;
BUG.info('CIA.load16() unknown CIA address $%08x', addr);
break;
}
}
this.waitPost();
return v;
}
this.load32 = function (addr) {
return ((this.load16(addr) << 16) | this.load16(addr + 2)) >>> 0;
}
this.store8 = function (addr, value) {
var r = (addr & 0xf00) >> 8;
this.waitPre();
if ((addr & 0x3000) != 0) {
if ((addr & 0x2000) == 0) this.B.write(r, value);
if ((addr & 0x1000) == 0) this.A.write(r, value);
}
this.waitPost();
}
this.store16 = function (addr, value) {
var r = (addr & 0xf00) >> 8;
this.waitPre();
if ((addr & 0x3000) != 0) {
if ((addr & 0x2000) == 0) this.B.write(r, value >> 8);
if ((addr & 0x1000) == 0) this.A.write(r, value & 0xff);
}
this.waitPost();
}
this.store32 = function (addr, value) {
this.store16(addr, value >>> 16);
this.store16(addr + 2, value & 0xffff);
}
}