Implemented TMS9918 emulation

master
Marco Maccaferri 2020-06-08 15:25:23 +02:00
rodzic 68e8a95dd3
commit aa575f1235
9 zmienionych plików z 1302 dodań i 68 usunięć

Wyświetl plik

@ -131,6 +131,9 @@ public class Application {
DebugTerminal debugTerminal;
Emulator emulator;
TMS9918 tms9918;
DebugTMS9918 debugTMS9918;
Z80 proc;
MemIoOps memIoOps;
SourceMap sourceMap;
@ -359,6 +362,10 @@ public class Application {
debugTerminal.dispose();
debugTerminal = null;
}
if (debugTMS9918 != null) {
debugTMS9918.dispose();
debugTMS9918 = null;
}
try {
preferences.removePropertyChangeListener(preferencesChangeListner);
@ -1111,6 +1118,36 @@ public class Application {
new MenuItem(menu, SWT.SEPARATOR);
item = new MenuItem(menu, SWT.PUSH);
item.setText("Open terminal window");
item.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event e) {
try {
handleOpenDebugTerminal();
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
item = new MenuItem(menu, SWT.PUSH);
item.setText("Open TMS9918 window");
item.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event e) {
try {
handleOpenTMS9918Window();
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
new MenuItem(menu, SWT.SEPARATOR);
item = new MenuItem(menu, SWT.PUSH);
item.setText("Reset\tF5");
item.setAccelerator(SWT.F5);
@ -2398,6 +2435,12 @@ public class Application {
return;
}
tms9918 = new TMS9918();
if (debugTMS9918 != null) {
debugTMS9918.setTMS9918(tms9918);
debugTMS9918.redraw();
}
memIoOps = new MemIoOps(65536, 256) {
public final static int SIOA_D = 0x81;
@ -2413,7 +2456,6 @@ public class Application {
@Override
public int inPort(int port) {
int result = super.inPort(port & 0xFF);
switch (port & 0xFF) {
case SIOA_C: {
try {
@ -2438,7 +2480,15 @@ public class Application {
case SIOB_C:
return 0x04; // Always return TX buffer empty
}
return result;
if ((port & 0xFF) == preferences.getTms9918Ram()) {
return tms9918.inRam();
}
if ((port & 0xFF) == preferences.getTms9918Register()) {
return tms9918.inReg();
}
return super.inPort(port & 0xFF);
}
@Override
@ -2456,6 +2506,12 @@ public class Application {
out.write(value);
break;
}
if ((port & 0xFF) == preferences.getTms9918Ram()) {
tms9918.outRam(value);
}
if ((port & 0xFF) == preferences.getTms9918Register()) {
tms9918.outReg(value);
}
super.outPort(port & 0xFF, value);
}
@ -2647,6 +2703,8 @@ public class Application {
}
}
tms9918.reset();
memIoOps.reset();
memory.clearUpdates();
viewer.getControl().setFocus();
@ -2774,6 +2832,11 @@ public class Application {
memory.update();
registers.updateRegisters(proc);
tms9918.redrawFrame();
if (debugTMS9918 != null) {
debugTMS9918.redraw();
}
LineEntry lineEntry = sourceMap.getLineAtAddress(proc.getRegPC());
if (lineEntry != null) {
viewer.gotToLineColumn(lineEntry.lineNumber, 0);
@ -2795,6 +2858,22 @@ public class Application {
debugTerminal.setFocus();
}
private void handleOpenTMS9918Window() {
if (debugTMS9918 == null) {
debugTMS9918 = new DebugTMS9918();
debugTMS9918.setTMS9918(tms9918);
debugTMS9918.open();
debugTMS9918.getShell().addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
debugTMS9918 = null;
}
});
}
debugTMS9918.setFocus();
}
private void handleCompileAndCopyIntelHexToClipboard() throws Exception {
CTabItem tabItem = tabFolder.getSelection();
if (tabItem == null) {

Wyświetl plik

@ -0,0 +1,299 @@
/*
* Copyright (c) 2018 Marco Maccaferri and others.
* All rights reserved.
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License v1.0 which accompanies this
* distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.maccasoft.tools;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import com.maccasoft.tools.internal.ImageRegistry;
public class DebugTMS9918 {
Display display;
Shell shell;
Memory memory;
Canvas canvas;
Text[] reg;
Label[] regPtr;
TMS9918 tms9918;
ImageData imageData;
Image image;
Font font;
final Runnable redrawRunnable = new Runnable() {
@Override
public void run() {
if (canvas == null || canvas.isDisposed()) {
return;
}
memory.getControl().redraw();
if (image != null) {
image.dispose();
}
image = new Image(display, imageData);
GC gc = new GC(canvas);
try {
Rectangle canvasBounds = canvas.getBounds();
gc.setAdvanced(false);
gc.setAntialias(SWT.OFF);
gc.setInterpolation(SWT.NONE);
gc.drawImage(image, 0, 0, imageData.width, imageData.height, 0, 0, canvasBounds.width, canvasBounds.height);
} finally {
gc.dispose();
}
for (int i = 0; i < reg.length; i++) {
reg[i].setText(String.format("%02X", tms9918.getReg(i)));
if (i >= 2 && i <= 6) {
regPtr[i].setText(String.format("%04X", tms9918.getRegAddr(i)));
}
}
}
};
public DebugTMS9918() {
}
public void open() {
shell = new Shell();
shell.setText("Debug TMS9918");
shell.setData(this);
display = shell.getDisplay();
Image[] images = new Image[] {
ImageRegistry.getImageFromResources("app128.png"),
ImageRegistry.getImageFromResources("app64.png"),
ImageRegistry.getImageFromResources("app48.png"),
ImageRegistry.getImageFromResources("app32.png"),
ImageRegistry.getImageFromResources("app16.png"),
};
shell.setImages(images);
shell.setLayout(new GridLayout(1, false));
Control control = createContents(shell);
control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Point size = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
Rectangle screen = display.getBounds();
Rectangle rect = shell.computeTrim(0, 0, size.x, size.y);
rect.x = (screen.width - rect.width) / 2;
rect.y = (screen.height - rect.height) / 2;
if (rect.y < 0) {
rect.height += rect.y * 2;
rect.y = 0;
}
shell.setBounds(rect);
shell.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
if (image != null) {
image.dispose();
}
image = null;
if (font != null) {
font.dispose();
}
font = null;
}
});
redrawRunnable.run();
shell.open();
}
protected Control createContents(Composite parent) {
Composite container = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(3, false);
layout.marginWidth = layout.marginHeight = 0;
container.setLayout(layout);
container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
createMemoryPanel(container);
createCanvas(container);
createRegistersGroup(container);
container.setTabList(new Control[] {
canvas
});
return container;
}
void createMemoryPanel(Composite parent) {
memory = new Memory(parent);
memory.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
memory.setData(tms9918.getRam());
}
void createCanvas(Composite parent) {
canvas = new Canvas(parent, SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND);
canvas.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
canvas.setBounds(canvas.computeTrim(0, 0, imageData.width * 2, imageData.height * 2));
canvas.addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
if (image == null || image.isDisposed()) {
return;
}
Rectangle canvasBounds = canvas.getBounds();
e.gc.setAdvanced(false);
e.gc.setAntialias(SWT.OFF);
e.gc.setInterpolation(SWT.NONE);
e.gc.drawImage(image, 0, 0, imageData.width, imageData.height, 0, 0, canvasBounds.width, canvasBounds.height);
}
});
Rectangle rect = canvas.getBounds();
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.widthHint = rect.width;
gridData.heightHint = rect.height;
canvas.setLayoutData(gridData);
}
void createRegistersGroup(Composite parent) {
Composite container = new Composite(parent, SWT.BORDER);
GridLayout layout = new GridLayout(3, false);
container.setLayout(layout);
container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
if ("win32".equals(SWT.getPlatform())) {
font = new Font(display, "Courier New", 9, SWT.NONE);
}
else {
font = new Font(display, "mono", 9, SWT.NONE);
}
reg = new Text[8];
regPtr = new Label[reg.length];
for (int i = 0; i < reg.length; i++) {
Label label = new Label(container, SWT.NONE);
label.setText("R" + i);
label.setFont(font);
reg[i] = new Text(container, SWT.BORDER);
reg[i].setLayoutData(new GridData(20, SWT.DEFAULT));
reg[i].setFont(font);
reg[i].setData(i);
reg[i].addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
updateRegister((Text) e.widget);
}
@Override
public void focusGained(FocusEvent e) {
((Text) e.widget).selectAll();
}
});
reg[i].addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.CR) {
updateRegister((Text) e.widget);
}
}
});
if (i >= 2 && i <= 6) {
regPtr[i] = new Label(container, SWT.NONE);
regPtr[i].setLayoutData(new GridData(40, SWT.DEFAULT));
regPtr[i].setFont(font);
}
else {
new Label(container, SWT.NONE);
}
}
}
void updateRegister(Text widget) {
try {
String text = widget.getText();
int value = Integer.parseInt(text.toUpperCase(), 16);
if (value != tms9918.getReg((Integer) widget.getData())) {
tms9918.setReg((Integer) widget.getData(), value);
tms9918.redrawFrame();
display.asyncExec(redrawRunnable);
}
} catch (Exception ex) {
// Do nothing
}
}
public void setFocus() {
canvas.setFocus();
}
public void dispose() {
shell.dispose();
}
public Shell getShell() {
return shell;
}
public void setTMS9918(TMS9918 tms9918) {
this.tms9918 = tms9918;
this.imageData = tms9918.getImageData();
if (this.memory != null) {
this.memory.setData(tms9918.getRam());
}
}
public void redraw() {
display.asyncExec(redrawRunnable);
}
}

Wyświetl plik

@ -14,13 +14,13 @@ import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
@ -33,10 +33,10 @@ import org.eclipse.swt.widgets.Shell;
import com.maccasoft.tools.internal.ImageRegistry;
public class DebugTerminal extends Window {
public class DebugTerminal {
Display display;
Composite container;
Shell shell;
Terminal term;
Combo cursorKeys;
@ -45,12 +45,15 @@ public class DebugTerminal extends Window {
PipedInputStream is;
public DebugTerminal() {
super((Shell) null);
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
public void open() {
shell = new Shell();
shell.setText("Debug Terminal");
shell.setData(this);
display = shell.getDisplay();
Image[] images = new Image[] {
ImageRegistry.getImageFromResources("app128.png"),
@ -59,17 +62,33 @@ public class DebugTerminal extends Window {
ImageRegistry.getImageFromResources("app32.png"),
ImageRegistry.getImageFromResources("app16.png"),
};
newShell.setImages(images);
newShell.setText("Debug Terminal");
shell.setImages(images);
newShell.setData(this);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = layout.marginHeight = 0;
shell.setLayout(layout);
Control control = createContents(shell);
control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Point size = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
Rectangle screen = display.getBounds();
Rectangle rect = shell.computeTrim(0, 0, size.x, size.y);
rect.x = (screen.width - rect.width) / 2;
rect.y = (screen.height - rect.height) / 2;
if (rect.y < 0) {
rect.height += rect.y * 2;
rect.y = 0;
}
shell.setBounds(rect);
shell.open();
}
@Override
protected Control createContents(Composite parent) {
display = parent.getDisplay();
container = new Composite(parent, SWT.NONE);
Composite container = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = layout.marginHeight = 0;
container.setLayout(layout);
@ -165,7 +184,11 @@ public class DebugTerminal extends Window {
}
public void dispose() {
getShell().dispose();
shell.dispose();
}
public Shell getShell() {
return shell;
}
public void write(int b) {

Wyświetl plik

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-19 Marco Maccaferri and others.
* Copyright (c) 2018-20 Marco Maccaferri and others.
* All rights reserved.
*
* This program and the accompanying materials are made available under
@ -29,6 +29,7 @@ import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
@ -59,6 +60,9 @@ public class Emulator {
Composite container;
Terminal term;
Shell tms9918Shell;
TMS9918Terminal tms9918term;
Combo cursorKeys;
PipedOutputStream os;
@ -72,52 +76,8 @@ public class Emulator {
}
public void open() {
display = Display.getDefault();
preferences = Preferences.getInstance();
shell = new Shell(display);
shell.setText(Application.APP_TITLE);
shell.setData(this);
Image[] images = new Image[] {
ImageRegistry.getImageFromResources("app128.png"),
ImageRegistry.getImageFromResources("app64.png"),
ImageRegistry.getImageFromResources("app48.png"),
ImageRegistry.getImageFromResources("app32.png"),
ImageRegistry.getImageFromResources("app16.png"),
};
shell.setImages(images);
Menu menu = new Menu(shell, SWT.BAR);
createFileMenu(menu);
createEditMenu(menu);
createHelpMenu(menu);
shell.setMenuBar(menu);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = layout.marginHeight = 0;
shell.setLayout(layout);
Control control = createContents(shell);
control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Rectangle screen = display.getClientArea();
Point size = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
Rectangle rect = shell.computeTrim(0, 0, size.x, size.y);
rect.x = (screen.width - rect.width) / 2;
rect.y = (screen.height - rect.height) / 2;
if (rect.y < 0) {
rect.height += rect.y * 2;
rect.y = 0;
}
shell.setLocation(rect.x, rect.y);
shell.setSize(rect.width, rect.height);
shell.open();
machine = new Machine() {
@Override
@ -205,17 +165,112 @@ public class Emulator {
super.outPort(port, value);
}
@Override
protected void onTMS9918VSync() {
if (tms9918term != null) {
tms9918term.redraw();
}
super.onTMS9918VSync();
}
};
machine.tmsRam = preferences.getTms9918Ram();
machine.tmsReg = preferences.getTms9918Register();
String s = preferences.getCompactFlashImage();
if (s != null && !"".equals(s)) {
machine.setCompactFlash(new File(s));
}
createTerminalShell();
if (preferences.isOpenTMS9918Window()) {
tms9918Shell = createTMS9918Shell();
Rectangle screen = display.getClientArea();
Rectangle rect = new Rectangle(0, 0, shell.getSize().x + 5 + tms9918Shell.getSize().x, shell.getSize().y);
rect.x = (screen.width - rect.width) / 2;
if (rect.x < 0) {
rect.x = 0;
}
rect.y = (screen.height - rect.height) / 2;
if (rect.y < 0) {
rect.height += rect.y * 2;
rect.y = 0;
}
shell.setLocation(rect.x, rect.y);
tms9918Shell.setLocation(rect.x + shell.getSize().x + 5, rect.y);
tms9918Shell.open();
}
shell.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
if (tms9918Shell != null) {
tms9918Shell.dispose();
}
tms9918Shell = null;
}
});
shell.open();
machine.reset();
machine.start();
}
protected Shell createTerminalShell() {
display = Display.getDefault();
shell = new Shell(display);
shell.setText(Application.APP_TITLE);
shell.setData(this);
Image[] images = new Image[] {
ImageRegistry.getImageFromResources("app128.png"),
ImageRegistry.getImageFromResources("app64.png"),
ImageRegistry.getImageFromResources("app48.png"),
ImageRegistry.getImageFromResources("app32.png"),
ImageRegistry.getImageFromResources("app16.png"),
};
shell.setImages(images);
Menu menu = new Menu(shell, SWT.BAR);
createFileMenu(menu);
createEditMenu(menu);
createWindowMenu(menu);
createHelpMenu(menu);
shell.setMenuBar(menu);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = layout.marginHeight = 0;
shell.setLayout(layout);
Control control = createContents(shell);
control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Rectangle screen = display.getClientArea();
Point size = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
Rectangle rect = shell.computeTrim(0, 0, size.x, size.y);
rect.x = (screen.width - rect.width) / 2;
rect.y = (screen.height - rect.height) / 2;
if (rect.y < 0) {
rect.height += rect.y * 2;
rect.y = 0;
}
shell.setLocation(rect.x, rect.y);
shell.setSize(rect.width, rect.height);
return shell;
}
void createFileMenu(Menu parent) {
final Menu menu = new Menu(parent.getParent(), SWT.DROP_DOWN);
@ -273,6 +328,28 @@ public class Emulator {
});
}
void createWindowMenu(Menu parent) {
Menu menu = new Menu(parent.getParent(), SWT.DROP_DOWN);
MenuItem item = new MenuItem(parent, SWT.CASCADE);
item.setText("&Window");
item.setMenu(menu);
item = new MenuItem(menu, SWT.PUSH);
item.setText("Open TMS9918 Window");
item.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event e) {
try {
handleOpenTMS9918Window();
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
}
void createHelpMenu(Menu parent) {
Menu menu = new Menu(parent.getParent(), SWT.DROP_DOWN);
@ -588,6 +665,85 @@ public class Emulator {
return shell;
}
private void handleOpenTMS9918Window() {
if (tms9918term != null) {
tms9918term.setFocus();
return;
}
tms9918Shell = createTMS9918Shell();
Rectangle bounds = shell.getBounds();
tms9918Shell.setLocation(bounds.x + bounds.width + 5, bounds.y);
tms9918Shell.open();
shell.setFocus();
}
protected Shell createTMS9918Shell() {
final Shell shell = new Shell(display);
shell.setText("TMS9918");
Image[] images = new Image[] {
ImageRegistry.getImageFromResources("app128.png"),
ImageRegistry.getImageFromResources("app64.png"),
ImageRegistry.getImageFromResources("app48.png"),
ImageRegistry.getImageFromResources("app32.png"),
ImageRegistry.getImageFromResources("app16.png"),
};
shell.setImages(images);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = layout.marginHeight = 0;
shell.setLayout(layout);
Control control = createTMS9918Contents(shell);
control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Point size = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
Rectangle rect = shell.computeTrim(0, 0, size.x * 2, size.y * 2);
Rectangle screen = getShell().getBounds();
shell.setLocation(screen.x + screen.width + 10, screen.y);
shell.setSize(rect.width, rect.height);
return shell;
}
protected Control createTMS9918Contents(Composite parent) {
Composite container = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = layout.marginHeight = 0;
container.setLayout(layout);
container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
tms9918term = new TMS9918Terminal(container) {
@Override
protected ImageData getImageData() {
return machine.tms9918.getImageData();
}
};
Rectangle rect = tms9918term.getBounds();
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.widthHint = rect.width;
gridData.heightHint = rect.height;
tms9918term.setLayoutData(gridData);
container.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
tms9918term = null;
}
});
return container;
}
static {
System.setProperty("SWT_GTK3", "0");
}
@ -603,6 +759,8 @@ public class Emulator {
Emulator emulator = new Emulator();
emulator.open();
emulator.getShell().setFocus();
while (display.getShells().length != 0) {
if (!display.readAndDispatch()) {
display.sleep();

Wyświetl plik

@ -54,16 +54,33 @@ public class Machine extends MemIoOps {
Z80 proc;
Thread thread;
double clockNs;
long clockPeriodNs;
long clockTimeNs;
int tmsRam;
int tmsReg;
TMS9918 tms9918;
public Machine() {
rom = new byte[16384];
ram = new byte[65536];
clockNs = 1000.0 / 7.3728;
clockPeriodNs = (long) (1000.0 / 7.3728);
clockTimeNs = 0;
proc = new Z80(this, null);
tms9918 = new TMS9918() {
@Override
protected void onVSync() {
Machine.this.onTMS9918VSync();
}
};
tmsRam = 0x40;
tmsReg = 0x41;
thread = new Thread(new Runnable() {
@Override
@ -84,7 +101,7 @@ public class Machine extends MemIoOps {
}
public void setClock(double freq) {
clockNs = 1000.0 / freq;
clockPeriodNs = (long) (1000.0 / freq);
}
public void setCompactFlash(File file) {
@ -107,13 +124,17 @@ public class Machine extends MemIoOps {
while (!Thread.interrupted()) {
synchronized (proc) {
int runTstates = (int) ((System.nanoTime() - ns) / clockNs);
int runTstates = (int) ((System.nanoTime() - ns) / clockPeriodNs);
if (runTstates >= 4) {
long prevTstates = tstates;
long prevClockTime = clockTimeNs;
while (tstates < (prevTstates + runTstates)) {
proc.execute();
}
ns += (tstates - prevTstates) * clockNs;
long elapsed = clockTimeNs - prevClockTime;
tms9918.processFrame(elapsed);
onElapsedTime(elapsed);
ns += elapsed;
}
}
@ -125,11 +146,19 @@ public class Machine extends MemIoOps {
}
}
protected void onElapsedTime(long elapsedNs) {
// Do nothing
}
@Override
public void reset() {
synchronized (proc) {
page = false;
tstates = 0;
clockTimeNs = 0;
if (tms9918 != null) {
tms9918.reset();
}
proc.reset();
super.reset();
}
@ -138,6 +167,7 @@ public class Machine extends MemIoOps {
@Override
public int fetchOpcode(int address) {
tstates += 4; // 3 clocks to fetch opcode from RAM and 1 execution clock
clockTimeNs += clockPeriodNs * 4;
if (!page && address < rom.length) {
return rom[address & 0xFFFF] & 0xff;
}
@ -147,6 +177,14 @@ public class Machine extends MemIoOps {
@Override
public int inPort(int port) {
tstates += 4; // 4 clocks for read byte from bus
clockTimeNs += clockPeriodNs * 4;
if ((port & 0xFF) == tmsRam) {
return tms9918.inRam();
}
if ((port & 0xFF) == tmsReg) {
return tms9918.inReg();
}
switch (port & 0xFF) {
case CF_DATA:
@ -168,12 +206,20 @@ public class Machine extends MemIoOps {
return 0x04; // Always return TX buffer empty
}
return 0;
return port;
}
@Override
public void outPort(int port, int value) {
tstates += 4; // 4 clocks for write byte to bus
clockTimeNs += clockPeriodNs * 4;
if ((port & 0xFF) == tmsRam) {
tms9918.outRam(value);
}
if ((port & 0xFF) == tmsReg) {
tms9918.outReg(value);
}
switch (port & 0xFF) {
case CF_DATA:
@ -222,6 +268,7 @@ public class Machine extends MemIoOps {
@Override
public int peek8(int address) {
tstates += 3; // 3 clocks for read byte from RAM
clockTimeNs += clockPeriodNs * 3;
if (!page && address < rom.length) {
return rom[address & 0xFFFF] & 0xff;
}
@ -231,6 +278,7 @@ public class Machine extends MemIoOps {
@Override
public void poke8(int address, int value) {
tstates += 3; // 3 clocks for write byte to RAM
clockTimeNs += clockPeriodNs * 3;
if (page || address >= rom.length) {
ram[address & 0xFFFF] = (byte) value;
}
@ -256,4 +304,8 @@ public class Machine extends MemIoOps {
}
}
protected void onTMS9918VSync() {
// Do nothing
}
}

Wyświetl plik

@ -94,6 +94,9 @@ public class Preferences {
String romImage2;
int romAddress2;
String compactFlashImage;
boolean openTMS9918Window;
int tms9918Ram;
int tms9918Register;
int lastUploadType;
String lastPath;
@ -120,6 +123,9 @@ public class Preferences {
downloadCommand = "A:DOWNLOAD {0}";
xmodemCommand = "A:XMODEM {0} /R /X0 /Q";
tms9918Ram = 0x98;
tms9918Register = 0x99;
lru = new ArrayList<String>();
}
@ -414,6 +420,30 @@ public class Preferences {
this.compactFlashImage = compactFlashImage;
}
public boolean isOpenTMS9918Window() {
return openTMS9918Window;
}
public void setOpenTMS9918Window(boolean openTMS9918Window) {
this.openTMS9918Window = openTMS9918Window;
}
public int getTms9918Ram() {
return tms9918Ram;
}
public void setTms9918Ram(int tms9918Data) {
this.tms9918Ram = tms9918Data;
}
public int getTms9918Register() {
return tms9918Register;
}
public void setTms9918Register(int tms9918Control) {
this.tms9918Register = tms9918Control;
}
public void save() throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);

Wyświetl plik

@ -85,6 +85,9 @@ public class PreferencesDialog extends Dialog {
Text romImage2;
Text romAddress2;
Text compactFlashImage;
Button openTMS9918Window;
Text tms9918VRam;
Text tms9918Register;
Preferences preferences;
String defaultFont;
@ -775,6 +778,23 @@ public class PreferencesDialog extends Dialog {
}
}
});
new Label(composite, SWT.NONE);
openTMS9918Window = new Button(composite, SWT.CHECK);
openTMS9918Window.setText("Open TMS9918 Window");
openTMS9918Window.setSelection(preferences.isOpenTMS9918Window());
label = new Label(composite, SWT.NONE);
label.setText("TMS9918 VRam Port");
tms9918VRam = new Text(composite, SWT.BORDER);
tms9918VRam.setLayoutData(new GridData(convertWidthInCharsToPixels(3), SWT.DEFAULT));
tms9918VRam.setText(String.format("%02X", preferences.getTms9918Ram()));
label = new Label(composite, SWT.NONE);
label.setText("TMS9918 Register Port");
tms9918Register = new Text(composite, SWT.BORDER);
tms9918Register.setLayoutData(new GridData(convertWidthInCharsToPixels(3), SWT.DEFAULT));
tms9918Register.setText(String.format("%02X", preferences.getTms9918Register()));
}
void addSeparator(Composite parent) {
@ -835,6 +855,9 @@ public class PreferencesDialog extends Dialog {
preferences.setRomAddress2(Integer.valueOf(romAddress2.getText(), 16));
preferences.setRomImage2(romImage2.getText());
preferences.setCompactFlashImage(compactFlashImage.getText());
preferences.setOpenTMS9918Window(openTMS9918Window.getSelection());
preferences.setTms9918Ram(Integer.valueOf(tms9918VRam.getText(), 16));
preferences.setTms9918Register(Integer.valueOf(tms9918Register.getText(), 16));
super.okPressed();
}

Wyświetl plik

@ -0,0 +1,446 @@
/*
* Copyright (c) 2018-19 Marco Maccaferri and others.
* All rights reserved.
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License v1.0 which accompanies this
* distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.maccasoft.tools;
import java.util.Arrays;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.RGB;
public class TMS9918 {
public static final int NTSC = 0;
public static final int PAL = 1;
public static final int TMSMODE3 = 0b00000010;
public static final int TMSBLANK = 0b01000000;
public static final int TMSMODE1 = 0b00010000;
public static final int TMSMODE2 = 0b00001000;
public static final int TMSSPRSIZE = 0b00000010;
public static final int TMSSPRMAG = 0b00000001;
public static final int FRAME_WIDTH = 320;
public static final int FRAME_HEIGHT = 240;
public static final int FRAME_TOP = (FRAME_HEIGHT - 192) / 2;
public static final int FRAME_LEFT = (FRAME_WIDTH - 256) / 2;
public static final int STATE_VSYNC = -1;
PaletteData paletteData;
ImageData imageData;
byte[] reg;
byte[] ram;
byte status;
int ramPtr;
int data;
int state;
long currentTimeNs;
long nextTimeNs;
int ROW_TIME_NS;
int DISPLAY_ROWS;
public TMS9918() {
ROW_TIME_NS = 63500;
DISPLAY_ROWS = 262;
reg = new byte[8];
ram = new byte[16384];
ramPtr = 0;
data = -1;
state = STATE_VSYNC - 1;
nextTimeNs = 0;
paletteData = new PaletteData(new RGB[] {
new RGB(0x00, 0x00, 0x00), new RGB(0x00, 0x00, 0x00), new RGB(0x20, 0xC0, 0x20), new RGB(0x60, 0xE0, 0x60),
new RGB(0x20, 0x20, 0xE0), new RGB(0x40, 0x60, 0xE0), new RGB(0xA0, 0x20, 0x20), new RGB(0x40, 0xC0, 0xE0),
new RGB(0xE0, 0x20, 0x20), new RGB(0xE0, 0x60, 0x60), new RGB(0xC0, 0xC0, 0x20), new RGB(0xC0, 0xC0, 0x80),
new RGB(0x20, 0x80, 0x20), new RGB(0xC0, 0x40, 0xA0), new RGB(0xA0, 0xA0, 0xA0), new RGB(0xE0, 0xE0, 0xE0)
});
imageData = new ImageData(FRAME_WIDTH, FRAME_HEIGHT, 8, paletteData, 1, new byte[FRAME_WIDTH * FRAME_HEIGHT]);
}
public void setTiming(int timing) {
if (timing == PAL) {
ROW_TIME_NS = 64000;
DISPLAY_ROWS = 312;
}
else {
ROW_TIME_NS = 63500;
DISPLAY_ROWS = 262;
}
}
public void reset() {
Arrays.fill(reg, (byte) 0);
}
public void outReg(int value) {
if (data == -1) {
data = value;
}
else {
if ((value & 0x80) != 0) {
writeReg(value & 0x07, data);
}
else {
setRamPtr((value << 8) | data);
}
data = -1;
}
}
public void outRam(int value) {
ram[ramPtr] = (byte) value;
ramPtr = (ramPtr + 1) & 0x3FFF;
}
protected void writeReg(int index, int value) {
reg[index] = (byte) value;
}
protected void setRamPtr(int value) {
ramPtr = value & 0x3FFF;
}
public int getRamPtr() {
return ramPtr;
}
public int inRam() {
int result = ram[ramPtr] & 0xFF;
ramPtr = (ramPtr + 1) & 0x3FFF;
return result;
}
public int inReg() {
int result = status;
status &= ~0x80;
return result;
}
public void processFrame(long elapsedTimeNs) {
currentTimeNs += elapsedTimeNs;
while (currentTimeNs >= nextTimeNs) {
state++;
if (state >= FRAME_HEIGHT) {
state = STATE_VSYNC;
status |= 0x80;
onVSync();
}
if (state == STATE_VSYNC) {
nextTimeNs += ROW_TIME_NS * (DISPLAY_ROWS - FRAME_HEIGHT);
}
else if (state >= 0 && state < FRAME_HEIGHT) {
processRow(state);
nextTimeNs += ROW_TIME_NS;
}
}
}
protected void onVSync() {
// Do nothing
}
void processRow(int row) {
int register2 = (reg[2] << 10) & 0x3FFF;
int register3 = (reg[3] << 6) & 0x3FFF;
int register4 = (reg[4] << 11) & 0x3FFF;
int register5 = (reg[5] << 7) & 0x3FFF;
int register6 = (reg[6] << 11) & 0x3FFF;
byte backdrop = (byte) (reg[7] & 0x0F);
int index = row * FRAME_WIDTH;
if ((reg[1] & TMSBLANK) == 0) {
Arrays.fill(imageData.data, index, index + FRAME_WIDTH, (byte) 0);
return;
}
Arrays.fill(imageData.data, index, index + FRAME_WIDTH, backdrop);
if (row >= FRAME_TOP && row < (FRAME_TOP + 192)) {
index += FRAME_LEFT;
row -= FRAME_TOP;
if ((reg[1] & TMSMODE1) != 0) {
byte c0 = (byte) (reg[7] & 0x0F);
byte c1 = (byte) ((reg[7] & 0xF0) >> 4);
register2 += (row >> 3) * 40;
register4 += row & 0x07;
index += 8;
for (int i = 0; i < 40; i++) {
int tile = ram[register2++] & 0xFF;
byte pattern = ram[register4 + (tile << 3)];
imageData.data[index++] = (pattern & 0x80) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x40) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x20) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x10) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x08) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x04) != 0 ? c1 : c0;
}
}
else {
if ((reg[1] & TMSMODE2) != 0) {
register2 += (row >> 3) << 5;
register4 += (row >> 2) & 0x07;
for (int i = 0; i < 32; i++) {
int tile = ram[register2++] & 0xFF;
byte colors = ram[register4 + (tile << 3)];
byte c0 = (byte) (colors & 0x0F);
if (c0 == 0) {
c0 = backdrop;
}
byte c1 = (byte) ((colors & 0xF0) >> 4);
if (c1 == 0) {
c1 = backdrop;
}
imageData.data[index++] = c1;
imageData.data[index++] = c1;
imageData.data[index++] = c1;
imageData.data[index++] = c1;
imageData.data[index++] = c0;
imageData.data[index++] = c0;
imageData.data[index++] = c0;
imageData.data[index++] = c0;
}
}
else if ((reg[0] & TMSMODE3) != 0) {
register2 += (row >> 3) * 32;
register3 &= ~0x1FFF;
register3 += (row >> 6) << 11;
register3 += row & 0x07;
register4 &= ~0x1FFF;
register4 += (row >> 6) << 11;
register4 += row & 0x07;
for (int i = 0; i < 32; i++) {
int tile = ram[register2++] & 0xFF;
byte pattern = ram[register4 + (tile << 3)];
byte colors = ram[register3 + (tile << 3)];
byte c0 = (byte) (colors & 0x0F);
if (c0 == 0) {
c0 = backdrop;
}
byte c1 = (byte) ((colors & 0xF0) >> 4);
if (c1 == 0) {
c1 = backdrop;
}
imageData.data[index++] = (pattern & 0x80) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x40) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x20) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x10) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x08) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x04) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x02) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x01) != 0 ? c1 : c0;
}
}
else {
register2 += (row >> 3) * 32;
register4 += row & 0x07;
for (int i = 0; i < 32; i++) {
int tile = ram[register2++] & 0xFF;
byte pattern = ram[register4 + (tile << 3)];
byte colors = ram[register3 + (tile >> 3)];
byte c0 = (byte) (colors & 0x0F);
if (c0 == 0) {
c0 = backdrop;
}
byte c1 = (byte) ((colors & 0xF0) >> 4);
if (c1 == 0) {
c1 = backdrop;
}
imageData.data[index++] = (pattern & 0x80) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x40) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x20) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x10) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x08) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x04) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x02) != 0 ? c1 : c0;
imageData.data[index++] = (pattern & 0x01) != 0 ? c1 : c0;
}
}
int size = 8;
if ((reg[1] & TMSSPRSIZE) != 0) {
size <<= 1;
}
if ((reg[1] & TMSSPRMAG) != 0) {
size <<= 1;
}
int count = 0;
for (int sprite = 0; sprite < 32 && count < 4; sprite++, register5 += 4) {
int y = ram[register5] & 0xFF;
if (y == 0xD0) {
break;
}
int x = ram[register5 + 1] & 0xFF;
if ((ram[register5 + 3] & 0x80) != 0) {
x -= 32;
}
int tile = ram[register5 + 2] & 0xFF;
byte c1 = (byte) (ram[register5 + 3] & 0x0F);
if (c1 == 0) {
continue;
}
int offset = row - y;
if (offset < 0 || offset >= size) {
continue;
}
index = (row + FRAME_TOP) * FRAME_WIDTH + FRAME_LEFT + x;
if ((reg[1] & TMSSPRMAG) != 0) {
offset >>= 1;
}
int patternIndex = register6 + (tile << 3) + offset;
int pattern = ram[patternIndex] & 0xFF;
if ((reg[1] & TMSSPRSIZE) != 0) {
pattern = (pattern << 8) | (ram[patternIndex + 16] & 0xFF);
for (int i = 0x8000; i != 0x0000; i >>= 1) {
if (x >= 0 && x < 256) {
if ((pattern & i) != 0) {
imageData.data[index] = c1;
}
if ((reg[1] & TMSSPRMAG) != 0) {
x++;
index++;
if (x >= 0 && x < 256) {
if ((pattern & i) != 0) {
imageData.data[index] = c1;
}
}
}
}
x++;
index++;
}
}
else {
for (int i = 0x80; i != 0x00; i >>= 1) {
if (x >= 0 && x < 256) {
if ((pattern & i) != 0) {
imageData.data[index] = c1;
}
if ((reg[1] & TMSSPRMAG) != 0) {
x++;
index++;
if (x >= 0 && x < 256) {
if ((pattern & i) != 0) {
imageData.data[index] = c1;
}
}
}
}
x++;
index++;
}
}
count++;
}
}
}
}
public void redrawFrame() {
for (int row = 0; row < FRAME_HEIGHT; row++) {
processRow(row);
}
status |= 0x80;
}
public ImageData getImageData() {
return imageData;
}
public int getStatus() {
return status & 0xFF;
}
public void setStatus(int status) {
this.status = (byte) status;
}
public int getReg(int index) {
return reg[index] & 0xFF;
}
public void setReg(int index, int value) {
reg[index] = (byte) value;
}
public int getRegAddr(int index) {
switch (index) {
case 2:
return (reg[2] << 10) & 0x3FFF;
case 3:
return (reg[3] << 6) & 0x3FFF;
case 4:
return (reg[4] << 11) & 0x3FFF;
case 5:
return (reg[5] << 7) & 0x3FFF;
case 6:
return (reg[6] << 11) & 0x3FFF;
}
return 0;
}
public byte[] getRegs() {
return this.reg;
}
public void setRegs(byte[] regs) {
System.arraycopy(regs, 0, this.reg, 0, Math.min(regs.length, this.reg.length));
}
public byte[] getRam() {
return this.ram;
}
public void setRam(byte[] ram) {
System.arraycopy(ram, 0, this.ram, 0, Math.min(ram.length, this.ram.length));
}
}

Wyświetl plik

@ -0,0 +1,124 @@
/*
* Copyright (c) 2018-19 Marco Maccaferri and others.
* All rights reserved.
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License v1.0 which accompanies this
* distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.maccasoft.tools;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
public abstract class TMS9918Terminal {
Display display;
Canvas canvas;
Image image;
final Runnable redrawRunnable = new Runnable() {
@Override
public void run() {
pendingRedraw.set(false);
if (canvas == null || canvas.isDisposed()) {
return;
}
if (image != null) {
image.dispose();
}
image = new Image(display, getImageData());
canvas.redraw();
canvas.update();
}
};
final AtomicBoolean pendingRedraw = new AtomicBoolean();
public TMS9918Terminal(Composite parent) {
display = parent.getDisplay();
canvas = new Canvas(parent, SWT.DOUBLE_BUFFERED);
canvas.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
canvas.setBounds(canvas.computeTrim(0, 0, TMS9918.FRAME_WIDTH, TMS9918.FRAME_HEIGHT));
canvas.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
if (image != null) {
image.dispose();
}
image = null;
}
});
canvas.addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
if (image == null || image.isDisposed()) {
return;
}
Rectangle bounds = canvas.getBounds();
e.gc.setAdvanced(false);
e.gc.setAntialias(SWT.OFF);
e.gc.setInterpolation(SWT.NONE);
e.gc.drawImage(image, 0, 0, TMS9918.FRAME_WIDTH, TMS9918.FRAME_HEIGHT, 0, 0, bounds.width, bounds.height);
}
});
}
protected abstract ImageData getImageData();
protected void redraw() {
if (pendingRedraw.compareAndSet(false, true)) {
display.asyncExec(redrawRunnable);
}
}
public void setLayoutData(Object data) {
canvas.setLayoutData(data);
}
public Object getLayoutData() {
return canvas.getLayoutData();
}
public Rectangle getBounds() {
return canvas.getBounds();
}
public void setFocus() {
canvas.setFocus();
}
public void addKeyListener(KeyListener l) {
canvas.addKeyListener(l);
}
public void removeKeyListener(KeyListener l) {
canvas.removeKeyListener(l);
}
public void dispose() {
canvas.dispose();
}
}