From a80210495182840ab1dac80bd70829560b21010f Mon Sep 17 00:00:00 2001 From: Ilari Liusvaara Date: Fri, 5 Mar 2010 20:48:41 +0200 Subject: [PATCH] FM Chip emulation/dumping Support FM chip emulation and dumping. --- org/jpc/modules/FMCard.java | 280 +++++++++++++++++++++++++++++++++++++++++ org/jpc/modulesaux/FMChip.java | 251 ++++++++++++++++++++++++++++++++++++ 2 files changed, 531 insertions(+) create mode 100644 org/jpc/modules/FMCard.java create mode 100644 org/jpc/modulesaux/FMChip.java diff --git a/org/jpc/modules/FMCard.java b/org/jpc/modules/FMCard.java new file mode 100644 index 0000000..c747f86 --- /dev/null +++ b/org/jpc/modules/FMCard.java @@ -0,0 +1,280 @@ +/* + JPC-RR: A x86 PC Hardware Emulator + Release 1 + + Copyright (C) 2007-2009 Isis Innovation Limited + Copyright (C) 2009-2010 H. Ilari Liusvaara + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as published by + the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + Based on JPC x86 PC Hardware emulator, + A project from the Physics Dept, The University of Oxford + + Details about original JPC can be found at: + + www-jpc.physics.ox.ac.uk + +*/ + +package org.jpc.modules; + +import org.jpc.emulator.*; +import org.jpc.modulesaux.*; +import org.jpc.emulator.motherboard.*; +import java.io.*; + +public class FMCard extends AbstractHardwareComponent implements IOPortCapable, TimerResponsive, SoundOutputDevice +{ + private boolean ioportRegistered; + private Timer timer; + private Clock clock; + private FMChip fmChip; + private int fmIndex; + private long fmNextAttention; + private SoundDigitalOut fmOutput; + + private int lastStatus; //Not saved. + + public static final long TIME_NEVER = 0x7FFFFFFFFFFFFFFFL; + + public void dumpSRPartial(SRDumper output) throws IOException + { + super.dumpSRPartial(output); + output.dumpBoolean(ioportRegistered); + output.dumpObject(clock); + output.dumpObject(timer); + output.dumpObject(fmChip); + output.dumpObject(fmOutput); + output.dumpInt(fmIndex); + output.dumpLong(fmNextAttention); + } + + + public FMCard(SRLoader input) throws IOException + { + super(input); + ioportRegistered = input.loadBoolean(); + clock = (Clock)input.loadObject(); + timer = (Timer)input.loadObject(); + fmChip = (FMChip)input.loadObject(); + fmOutput = (SoundDigitalOut)input.loadObject(); + fmIndex = input.loadInt(); + fmNextAttention = input.loadLong(); + lastStatus = -1; + } + + public FMCard() throws IOException + { + fmChip = new FMChip(); + fmOutput = null; + fmIndex = 0; + fmNextAttention = TIME_NEVER; + lastStatus = -1; + } + + public void dumpStatusPartial(StatusDumper output) + { + super.dumpStatusPartial(output); + output.println("\tioportRegistered " + ioportRegistered); + output.println("\tclock "); if(clock != null) clock.dumpStatus(output); + output.println("\ttimer "); if(timer != null) timer.dumpStatus(output); + output.println("\tfmIndex " + fmIndex); + output.println("\tfmNextAttention " + fmNextAttention); + output.println("\tfmChip "); if(fmChip != null) fmChip.dumpStatus(output); + output.println("\tfmOutput "); if(fmOutput != null) fmOutput.dumpStatus(output); + } + + public void dumpStatus(StatusDumper output) + { + if(output.dumped(this)) + return; + + output.println("#" + output.objectNumber(this) + ": FMCard:"); + dumpStatusPartial(output); + output.endObject(); + } + + public boolean initialised() + { + return ((clock != null) && ioportRegistered); + } + + public void acceptComponent(HardwareComponent component) + { + if((component instanceof Clock) && component.initialised()) { + clock = (Clock)component; + timer = clock.newTimer(this); + } + + if((component instanceof IOPortHandler) && component.initialised()) { + ((IOPortHandler)component).registerIOPortCapable(this); + ioportRegistered = true; + } + + } + + public int[] ioPortsRequested() + { + int[] ret; + ret = new int[4]; + ret[0] = 0x388; + ret[1] = 0x389; + ret[2] = 0x38A; + ret[3] = 0x38B; + return ret; + } + + public int getTimerType() + { + return 37; + } + + public void ioPortWriteWord(int address, int data) + { + ioPortWriteByte(address, data & 0xFF); + ioPortWriteByte(address + 1, (data >>> 8) & 0xFF); + } + + public void ioPortWriteLong(int address, int data) + { + ioPortWriteByte(address, data & 0xFF); + ioPortWriteByte(address + 1, (data >>> 8) & 0xFF); + ioPortWriteByte(address + 2, (data >>> 16) & 0xFF); + ioPortWriteByte(address + 3, (data >>> 24) & 0xFF); + } + + public int ioPortReadWord(int address) + { + return (ioPortReadByte(address) | (ioPortReadByte(address + 1) << 8)); + } + + public int ioPortReadLong(int address) + { + return ioPortReadByte(address) | (ioPortReadByte(address + 1) << 8) | + (ioPortReadByte(address + 2) << 16) | (ioPortReadByte(address + 3) << 24); + } + + public void ioPortWriteByte(int address, int data) + { + ioWrite(address - 0x388, data); + } + + public int ioPortReadByte(int address) + { + return ioRead(address - 0x388); + } + + public int requestedSoundChannels() + { + return 1; + } + + public void soundChannelCallback(SoundDigitalOut out) + { + if(fmOutput == null) { + fmChip.setOutput(out); + fmOutput = out; + } + } + + //Do I/O write. Possible offsets are 0-7 and 10-15. + public void ioWrite(int offset, int dataByte) + { + switch(offset) { + case 0: + fmIndex = dataByte; + return; + case 1: + writeFM(fmIndex, dataByte); + return; + case 2: + fmIndex = 256 + dataByte; + return; + case 3: + writeFM(fmIndex, dataByte); + return; + } + } + + //Do I/O read. Possible offsets are 0-7 and 10-15. + public int ioRead(int offset) + { + switch(offset) { + case 0: + case 2: + return readFMStatus(); + case 1: + case 3: + return 0; //Lets do like opl.cpp does... + default: + return -1; //Not readable. + } + } + + public void reset() + { + fmIndex = 0; + if(clock != null) + fmChip.resetCard(clock.getTime()); + else + fmChip.resetCard(0); + } + + //Read FM synth #1 status register. + private final int readFMStatus() + { + int status; + status = fmChip.status(clock.getTime()); + lastStatus = status; + return status; + } + + //Write FM synth #1 data register. + private final void writeFM(int reg, int data) + { + fmChip.write(clock.getTime(), reg, data); + updateTimer(); + } + + //Recompute value for timer expiry. + private final void updateTimer() + { + long nextTime = TIME_NEVER; + long tmp = fmChip.nextAttention(clock.getTime()); + if(tmp < nextTime) + nextTime = tmp; + if(nextTime != TIME_NEVER) { + if(timer != null) + timer.setExpiry(nextTime); + } else + if(timer != null) + timer.disable(); + } + + public void callback() + { + long timeNow = clock.getTime(); + boolean runAny = true; + while(runAny) { + runAny = false; + long tmp = fmChip.nextAttention(clock.getTime()); + if(tmp <= timeNow) { + fmChip.attention(clock.getTime()); + runAny = true; + } + if(runAny) + updateTimer(); + } + } +} diff --git a/org/jpc/modulesaux/FMChip.java b/org/jpc/modulesaux/FMChip.java new file mode 100644 index 0000000..c8910c3 --- /dev/null +++ b/org/jpc/modulesaux/FMChip.java @@ -0,0 +1,251 @@ +/* + JPC-RR: A x86 PC Hardware Emulator + Release 1 + + Copyright (C) 2007-2009 Isis Innovation Limited + Copyright (C) 2009-2010 H. Ilari Liusvaara + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as published by + the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + Based on JPC x86 PC Hardware emulator, + A project from the Physics Dept, The University of Oxford + + Details about original JPC can be found at: + + www-jpc.physics.ox.ac.uk + +*/ + +package org.jpc.modulesaux; + +import org.jpc.emulator.*; +import java.io.*; +import static org.jpc.modules.SoundCard.TIME_NEVER; + +public class FMChip implements SRDumpable +{ + private long timer1ExpiresAt; + private long timer2ExpiresAt; + private boolean timer1Masked; + private boolean timer2Masked; + private boolean timer1Expired; + private boolean timer2Expired; + private boolean timer1Enabled; + private boolean timer2Enabled; + private int reg2Value; + private int reg3Value; + private SoundDigitalOut output; + + private static final int REG1_WAVEFORM_SELECT = 0x20; + private static final int REG4_IRQ_RESET = 0x80; + private static final int REG4_TIMER1_MASK = 0x40; + private static final int REG4_TIMER2_MASK = 0x20; + private static final int REG4_TIMER2_CONTROL = 0x02; + private static final int REG4_TIMER1_CONTROL = 0x01; + + public void dumpStatus(StatusDumper output) + { + if(output.dumped(this)) + return; + + output.println("#" + output.objectNumber(this) + ": FMChip:"); + dumpStatusPartial(output); + output.endObject(); + } + + //Read Status register value. + public int status(long time) + { + int value = 0; //Bit 1 and 2 low for OPL3. + if(timer1Expired) + value |= 0xC0; + if(timer2Expired) + value |= 0xA0; + return value; + } + + public void setOutput(SoundDigitalOut out) + { + output = out; + } + + //Compute next time this chip needs attention. + public long nextAttention(long time) + { + long next = TIME_NEVER; + if(timer1ExpiresAt < next) + next = timer1ExpiresAt; + if(timer2ExpiresAt < next) + next = timer2ExpiresAt; + return next; + } + + //Ok, this chip wants attention and gets it. + public void attention(long time) + { + if(timer1ExpiresAt <= time) { + timer1ExpiresAt = TIME_NEVER; + if(!timer1Masked) + timer1Expired = true; + } + if(timer2ExpiresAt <= time) { + timer2ExpiresAt = TIME_NEVER; + if(!timer2Masked) + timer2Expired = true; + } + } + + private final void computeExpirations(long time, int mask) + { + if((mask & 1) != 0) { + long timer1CyclesNow = time / 80000; + long timer1CyclesAtExpiry = timer1CyclesNow + (256 - reg2Value); + if((mask & 4) == 0 || timer1Enabled) { + timer1ExpiresAt = timer1CyclesAtExpiry * 80000; + timer1Enabled = true; + } else + timer1ExpiresAt = TIME_NEVER; + } + if((mask & 2) != 0) { + long timer2CyclesNow = time / 320000; + long timer2CyclesAtExpiry = timer2CyclesNow + (256 - reg3Value); + if((mask & 4) == 0 || timer2Enabled) { + timer2ExpiresAt = timer2CyclesAtExpiry * 320000; + timer2Enabled = true; + } else + timer2ExpiresAt = TIME_NEVER; + } + } + + //Write to register. + public void write(long timeStamp, int reg, int data) + { + int origReg = reg; + if(reg == 2) { + reg2Value = data; + } else if(reg == 3) { + reg3Value = data; + } else if(reg == 4) { + if((data & REG4_IRQ_RESET) != 0) { + timer1Expired = false; + timer2Expired = false; + computeExpirations(timeStamp, 7); + return; + } + + if((data & REG4_TIMER1_MASK) != 0) { + timer1Masked = true; + timer1Expired = false; + } else { + timer1Masked = false; + } + + if((data & REG4_TIMER2_MASK) != 0) { + timer2Masked = true; + timer2Expired = false; + } else { + timer2Masked = false; + } + + if((data & REG4_TIMER1_CONTROL) != 0) { + computeExpirations(timeStamp, 1); + } else { + timer1Enabled = false; + timer1ExpiresAt = TIME_NEVER; + } + + if((data & REG4_TIMER2_CONTROL) != 0) { + computeExpirations(timeStamp, 2); + } else { + timer2Enabled = false; + timer2ExpiresAt = TIME_NEVER; + } + } else { + //Just dump the raw output data. + output.addSample(timeStamp, (short)reg, (short)data); + } + } + + //Reset the chip. + public void resetCard(long timeStamp) + { + timer1Expired = false; + timer2Expired = false; + timer1Enabled = false; + timer2Enabled = false; + timer1Masked = false; + timer2Masked = false; + timer1ExpiresAt = TIME_NEVER; + timer2ExpiresAt = TIME_NEVER; + reg2Value = 0; + reg3Value = 0; + if(output != null) + output.addSample(timeStamp, (short)512, (short)0); + } + + //Save instance variables. + public void dumpSRPartial(SRDumper out) throws IOException + { + out.dumpBoolean(timer1Enabled); + out.dumpBoolean(timer2Enabled); + out.dumpBoolean(timer1Expired); + out.dumpBoolean(timer2Expired); + out.dumpBoolean(timer1Masked); + out.dumpBoolean(timer2Masked); + out.dumpLong(timer1ExpiresAt); + out.dumpLong(timer2ExpiresAt); + out.dumpInt(reg2Value); + out.dumpInt(reg3Value); + out.dumpObject(output); + } + + //Load instance variables. + public FMChip(SRLoader input) throws IOException + { + input.objectCreated(this); + timer1Enabled = input.loadBoolean(); + timer2Enabled = input.loadBoolean(); + timer1Expired = input.loadBoolean(); + timer2Expired = input.loadBoolean(); + timer1Masked = input.loadBoolean(); + timer2Masked = input.loadBoolean(); + timer1ExpiresAt = input.loadLong(); + timer2ExpiresAt = input.loadLong(); + reg2Value = input.loadInt(); + reg3Value = input.loadInt(); + output = (SoundDigitalOut)input.loadObject(); + } + + //Constructor. + public FMChip() + { + resetCard(0); + } + + //Dump instance variables. + public void dumpStatusPartial(StatusDumper out) + { + out.println("\tTimer1Enabled " + timer1Enabled); + out.println("\tTimer2Enabled " + timer2Enabled); + out.println("\tTimer1Expired " + timer1Expired); + out.println("\tTimer2Expired " + timer2Expired); + out.println("\tTimer1Masked " + timer1Masked); + out.println("\tTimer2Masked " + timer2Masked); + out.println("\tTimer1ExpiresAt " + timer1ExpiresAt); + out.println("\tTimer2ExpiresAt " + timer2ExpiresAt); + out.println("\tReg2Value " + reg2Value); + out.println("\tReg3Value " + reg3Value); + out.println("\toutput "); if(output != null) output.dumpStatus(out); + } +} -- 2.11.4.GIT