mirror of
https://github.com/trebonian/visual6502.git
synced 2024-12-28 20:29:18 +00:00
522 lines
14 KiB
JavaScript
522 lines
14 KiB
JavaScript
/*
|
|
Copyright (c) 2010 Brian Silverman, Barry Silverman, Ed Spittles
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
*/
|
|
|
|
var memory = Array();
|
|
var cycle = 0;
|
|
var trace = Array();
|
|
var logstream = Array();
|
|
var running = false;
|
|
var logThese=[];
|
|
var presetLogLists=[
|
|
['cycle'],
|
|
['ab','db','rw','sync','pc','a','x','y','s','p'],
|
|
['ir','tcstate','pd'],
|
|
['adl','adh','sb','alu'],
|
|
['alucin','alua','alub','alucout','aluvout','dasb'],
|
|
['plaOutputs'],
|
|
['idb','dor'],
|
|
['irq','nmi','res'],
|
|
];
|
|
|
|
function loadProgram(){
|
|
// a moderate size of static testprogram might be loaded
|
|
if(testprogram.length!=0 && testprogramAddress != undefined)
|
|
for(var i=0;testprogram[i]!=undefined;i++){
|
|
var a=testprogramAddress+i;
|
|
mWrite(a, testprogram[i]);
|
|
if(a<0x200)
|
|
setCellValue(a, testprogram[i]);
|
|
}
|
|
// a small test program or patch might be passed in the URL
|
|
if(userCode.length!=0)
|
|
for(var i=0;i<userCode.length;i++){
|
|
if(userCode[i] != undefined){
|
|
mWrite(i, userCode[i]);
|
|
if(i<0x200)
|
|
setCellValue(i, userCode[i]);
|
|
}
|
|
}
|
|
// default reset vector will be 0x0000 because undefined memory reads as zero
|
|
if(userResetLow!=undefined)
|
|
mWrite(0xfffc, userResetLow);
|
|
if(userResetHigh!=undefined)
|
|
mWrite(0xfffd, userResetHigh);
|
|
}
|
|
|
|
function go(){
|
|
if(typeof userSteps != "undefined"){
|
|
if(--userSteps==0){
|
|
running=false;
|
|
userSteps=undefined;
|
|
}
|
|
}
|
|
if(running) {
|
|
step();
|
|
setTimeout(go, 0); // schedule the next poll
|
|
}
|
|
}
|
|
|
|
function goUntilSync(){
|
|
halfStep();
|
|
while(!isNodeHigh(nodenames['sync']) || isNodeHigh(nodenames['clk0']))
|
|
halfStep();
|
|
}
|
|
|
|
function goUntilSyncOrWrite(){
|
|
halfStep();
|
|
cycle++;
|
|
while(
|
|
!isNodeHigh(nodenames['clk0']) ||
|
|
( !isNodeHigh(nodenames['sync']) && isNodeHigh(nodenames['rw']) )
|
|
) {
|
|
halfStep();
|
|
cycle++;
|
|
}
|
|
chipStatus();
|
|
}
|
|
|
|
function testNMI(n){
|
|
initChip();
|
|
|
|
mWrite(0x0000, 0x38); // set carry
|
|
mWrite(0x0001, 0x4c); // jump to test code
|
|
mWrite(0x0002, 0x06);
|
|
mWrite(0x0003, 0x23);
|
|
|
|
mWrite(0x22ff, 0x38); // set carry
|
|
mWrite(0x2300, 0xea);
|
|
mWrite(0x2301, 0xea);
|
|
mWrite(0x2302, 0xea);
|
|
mWrite(0x2303, 0xea);
|
|
mWrite(0x2304, 0xb0); // branch carry set to self
|
|
mWrite(0x2305, 0xfe);
|
|
|
|
mWrite(0x2306, 0xb0); // branch carry set to self
|
|
mWrite(0x2307, 0x01);
|
|
mWrite(0x2308, 0x00); // brk should be skipped
|
|
mWrite(0x2309, 0xa9); // anything
|
|
mWrite(0x230a, 0xde); // anything
|
|
mWrite(0x230b, 0xb0); // branch back with page crossing
|
|
mWrite(0x230c, 0xf2);
|
|
|
|
mWrite(0xc018, 0x40); // nmi handler
|
|
|
|
mWrite(0xfffa, 0x18); // nmi vector
|
|
mWrite(0xfffb, 0xc0);
|
|
mWrite(0xfffc, 0x00); // reset vector
|
|
mWrite(0xfffd, 0x00);
|
|
|
|
for(var i=0;i<n;i++){step();}
|
|
setLow('nmi');
|
|
chipStatus();
|
|
for(var i=0;i<8;i++){step();}
|
|
setHigh('nmi');
|
|
chipStatus();
|
|
for(var i=0;i<16;i++){step();}
|
|
}
|
|
|
|
function initChip(){
|
|
var start = now();
|
|
for(var nn in nodes) {
|
|
nodes[nn].state = false;
|
|
nodes[nn].float = true;
|
|
}
|
|
|
|
nodes[ngnd].state = false;
|
|
nodes[ngnd].float = false;
|
|
nodes[npwr].state = true;
|
|
nodes[npwr].float = false;
|
|
for(var tn in transistors) transistors[tn].on = false;
|
|
setLow('res');
|
|
setLow('clk0');
|
|
setHigh('rdy'); setLow('so');
|
|
setHigh('irq'); setHigh('nmi');
|
|
recalcNodeList(allNodes());
|
|
for(var i=0;i<8;i++){setHigh('clk0'), setLow('clk0');}
|
|
setHigh('res');
|
|
for(var i=0;i<18;i++){halfStep();} // avoid updating graphics and trace buffer before user code
|
|
refresh();
|
|
cycle = 0;
|
|
trace = Array();
|
|
if(typeof expertMode != "undefined")
|
|
updateLogList();
|
|
chipStatus();
|
|
if(ctrace)console.log('initChip done after', now()-start);
|
|
}
|
|
|
|
function signalSet(n){
|
|
var signals=[];
|
|
for (var i=0; (i<=n)&&(i<presetLogLists.length) ; i++){
|
|
for (var j=0; j<presetLogLists[i].length; j++){
|
|
signals.push(presetLogLists[i][j]);
|
|
}
|
|
}
|
|
return signals;
|
|
}
|
|
|
|
function updateLogList(names){
|
|
// user supplied a list of signals, which we append to the set defined by loglevel
|
|
logThese = signalSet(loglevel);
|
|
if(typeof names == "undefined")
|
|
// this is a UI call - read the text input
|
|
names = document.getElementById('LogThese').value;
|
|
else
|
|
// this is an URL call - update the text input box
|
|
document.getElementById('LogThese').value = names;
|
|
names = names.split(/[\s,]+/);
|
|
for(var i=0;i<names.length;i++){
|
|
// could be a signal name, a node number, or a special name
|
|
if(typeof busToString(names[i]) != "undefined")
|
|
logThese.push(names[i]);
|
|
}
|
|
initLogbox(logThese);
|
|
}
|
|
|
|
var traceChecksum='';
|
|
var goldenChecksum;
|
|
|
|
// simulate a single clock phase, updating trace and highlighting layout
|
|
function step(){
|
|
var s=stateString();
|
|
var m=getMem();
|
|
trace[cycle]= {chip: s, mem: m};
|
|
if(goldenChecksum != undefined)
|
|
traceChecksum=adler32(traceChecksum+s+m.slice(0,511).toString(16));
|
|
halfStep();
|
|
if(animateChipLayout)
|
|
refresh();
|
|
cycle++;
|
|
chipStatus();
|
|
}
|
|
|
|
// triggers for breakpoints, watchpoints, input pin events
|
|
clockTriggers={};
|
|
|
|
// simulate a single clock phase with no update to graphics or trace
|
|
function halfStep(){
|
|
var clk = isNodeHigh(nodenames['clk0']);
|
|
eval(clockTriggers[cycle]); // usually undefined, no measurable performance loss
|
|
if (clk) {setLow('clk0'); handleBusRead(); }
|
|
else {setHigh('clk0'); handleBusWrite();}
|
|
}
|
|
|
|
function handleBusRead(){
|
|
if(isNodeHigh(nodenames['rw'])) writeDataBus(mRead(readAddressBus()));
|
|
}
|
|
|
|
function handleBusWrite(){
|
|
if(!isNodeHigh(nodenames['rw'])){
|
|
var a = readAddressBus();
|
|
var d = readDataBus();
|
|
mWrite(a,d);
|
|
if(a<0x200) setCellValue(a,d);
|
|
}
|
|
}
|
|
|
|
function readAddressBus(){return readBits('ab', 16);}
|
|
function readDataBus(){return readBits('db', 8);}
|
|
function readA(){return readBits('a', 8);}
|
|
function readY(){return readBits('y', 8);}
|
|
function readX(){return readBits('x', 8);}
|
|
function readP(){return readBits('p', 8);}
|
|
function readPstring(){
|
|
var result;
|
|
result = (isNodeHigh(nodenames['p7'])?'N':'n') +
|
|
(isNodeHigh(nodenames['p6'])?'V':'v') +
|
|
'‑' + // non-breaking hyphen
|
|
(isNodeHigh(nodenames['p3'])?'B':'b') +
|
|
(isNodeHigh(nodenames['p3'])?'D':'d') +
|
|
(isNodeHigh(nodenames['p2'])?'I':'i') +
|
|
(isNodeHigh(nodenames['p1'])?'Z':'z') +
|
|
(isNodeHigh(nodenames['p0'])?'C':'c');
|
|
return result;
|
|
}
|
|
function readSP(){return readBits('s', 8);}
|
|
function readPC(){return (readBits('pch', 8)<<8) + readBits('pcl', 8);}
|
|
function readPCL(){return readBits('pcl', 8);}
|
|
function readPCH(){return readBits('pch', 8);}
|
|
|
|
function listActivePlaOutputs(){
|
|
// PLA outputs are mostly ^op- but some have a prefix too
|
|
// - we'll allow the x and xx prefix but ignore the #
|
|
var r=new RegExp('^([x]?x-)?op-');
|
|
var pla=[];
|
|
for(var i in nodenamelist){
|
|
if(r.test(nodenamelist[i])) {
|
|
if(isNodeHigh(nodenames[nodenamelist[i]]))
|
|
pla.push(nodenamelist[i]);
|
|
}
|
|
}
|
|
return pla;
|
|
}
|
|
|
|
function readBit(name){
|
|
return isNodeHigh(nodenames[name])?1:0;
|
|
}
|
|
function readBits(name, n){
|
|
var res = 0;
|
|
for(var i=0;i<n;i++){
|
|
var nn = nodenames[name+i];
|
|
res+=((isNodeHigh(nn))?1:0)<<i;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function busToString(busname){
|
|
// takes a signal name or prefix
|
|
// returns an appropriate string representation
|
|
if(busname=='cycle')
|
|
return cycle>>1;
|
|
if(busname=='pc')
|
|
return busToHex('pch') + busToHex('pcl');
|
|
if(busname=='p')
|
|
return readPstring();
|
|
if(busname=='tcstate')
|
|
return ['clock1','clock2','t2','t3','t4','t5'].map(busToHex).join("");
|
|
if(busname=='plaOutputs')
|
|
return listActivePlaOutputs();
|
|
return busToHex(busname);
|
|
}
|
|
|
|
function busToHex(busname){
|
|
// may be passed a bus or a signal, so allow multiple signals
|
|
var width=0;
|
|
var r=new RegExp('^' + busname + '[0-9]');
|
|
for(var i in nodenamelist){
|
|
if(r.test(nodenamelist[i])) {
|
|
width++;
|
|
}
|
|
}
|
|
if(width==0) {
|
|
// not a bus, so could be a signal, a nodenumber or a mistake
|
|
if(typeof nodenames[busname] != "undefined")
|
|
return isNodeHigh(nodenames[busname])?"1":"0";
|
|
if((parseInt(busname)!=NaN) && (typeof nodes[busname] != "undefined"))
|
|
return isNodeHigh(busname)?"1":"0";
|
|
return undefined;
|
|
}
|
|
if(width>16)
|
|
return undefined;
|
|
// finally, convert from logic values to hex
|
|
return (0x10000+readBits(busname,width)).toString(16).slice(-(width-1)/4-1);
|
|
}
|
|
|
|
function writeDataBus(x){
|
|
var recalcs = Array();
|
|
for(var i=0;i<8;i++){
|
|
var nn = nodenames['db'+i];
|
|
var n = nodes[nn];
|
|
if((x%2)==0) {n.pulldown=true; n.pullup=false;}
|
|
else {n.pulldown=false; n.pullup=true;}
|
|
recalcs.push(nn);
|
|
x>>=1;
|
|
}
|
|
recalcNodeList(recalcs);
|
|
}
|
|
|
|
function mRead(a){
|
|
if(memory[a]==undefined) return 0;
|
|
else return memory[a];
|
|
}
|
|
|
|
function mWrite(a, d){memory[a]=d;}
|
|
|
|
function clkNodes(){
|
|
var res = Array();
|
|
res.push(943);
|
|
for(var i in nodes[943].gates){
|
|
var t = nodes[943].gates[i];
|
|
if(t.c1==npwr) res.push(t.c2);
|
|
if(t.c2==npwr) res.push(t.c1);
|
|
}
|
|
hiliteNode(res);
|
|
}
|
|
|
|
function runChip(){
|
|
var start = document.getElementById('start');
|
|
var stop = document.getElementById('stop');
|
|
start.style.visibility = 'hidden';
|
|
stop.style.visibility = 'visible';
|
|
if(typeof running == "undefined")
|
|
initChip();
|
|
running = true;
|
|
go();
|
|
}
|
|
|
|
function stopChip(){
|
|
var start = document.getElementById('start');
|
|
var stop = document.getElementById('stop');
|
|
start.style.visibility = 'visible';
|
|
stop.style.visibility = 'hidden';
|
|
running = false;
|
|
}
|
|
|
|
function resetChip(){
|
|
stopChip();
|
|
setStatus('resetting 6502...');
|
|
setTimeout(initChip,0);
|
|
}
|
|
|
|
function stepForward(){
|
|
if(typeof running == "undefined")
|
|
initChip();
|
|
stopChip();
|
|
step();
|
|
}
|
|
|
|
function stepBack(){
|
|
if(cycle==0) return;
|
|
showState(trace[--cycle].chip);
|
|
setMem(trace[cycle].mem);
|
|
var clk = isNodeHigh(nodenames['clk0']);
|
|
if(!clk) writeDataBus(mRead(readAddressBus()));
|
|
chipStatus();
|
|
}
|
|
|
|
function chipStatus(){
|
|
var ab = readAddressBus();
|
|
var machine1 =
|
|
' halfcyc:' + cycle +
|
|
' phi0:' + readBit('clk0') +
|
|
' AB:' + hexWord(ab) +
|
|
' D:' + hexByte(readDataBus()) +
|
|
' RnW:' + readBit('rw');
|
|
var machine2 =
|
|
' PC:' + hexWord(readPC()) +
|
|
' A:' + hexByte(readA()) +
|
|
' X:' + hexByte(readX()) +
|
|
' Y:' + hexByte(readY()) +
|
|
' SP:' + hexByte(readSP()) +
|
|
' ' + readPstring();
|
|
var chk='';
|
|
if(goldenChecksum != undefined)
|
|
chk=" Chk:" + traceChecksum + ((traceChecksum==goldenChecksum)?" OK":" no match");
|
|
setStatus(machine1, machine2, "Hz: " + estimatedHz().toFixed(1) + chk);
|
|
if (loglevel>0) {
|
|
updateLogbox(logThese);
|
|
}
|
|
selectCell(ab);
|
|
}
|
|
|
|
function goFor(){
|
|
var n = headlessSteps;
|
|
estimatedHz1();
|
|
while(n--){
|
|
halfStep();
|
|
cycle++;
|
|
}
|
|
estimatedHz1();
|
|
chipStatus();
|
|
}
|
|
|
|
var prevHzTimeStamp=0;
|
|
var prevHzCycleCount=0;
|
|
var prevHzEstimate1=1;
|
|
var prevHzEstimate2=1;
|
|
var HzSamplingRate=10;
|
|
|
|
function estimatedHz(){
|
|
if(cycle%HzSamplingRate!=3)
|
|
return prevHzEstimate1;
|
|
return estimatedHz1();
|
|
}
|
|
|
|
function estimatedHz1(){
|
|
var HzTimeStamp = now();
|
|
var HzEstimate = (cycle-prevHzCycleCount+.01)/(HzTimeStamp-prevHzTimeStamp+.01);
|
|
HzEstimate=HzEstimate*1000/2; // convert from phases per millisecond to Hz
|
|
if(HzEstimate<5)
|
|
HzSamplingRate=5; // quicker
|
|
if(HzEstimate>10)
|
|
HzSamplingRate=10; // smoother
|
|
prevHzEstimate2=prevHzEstimate1;
|
|
prevHzEstimate1=(HzEstimate+prevHzEstimate1+prevHzEstimate2)/3; // wrong way to average speeds
|
|
prevHzTimeStamp=HzTimeStamp;
|
|
prevHzCycleCount=cycle;
|
|
return prevHzEstimate1
|
|
}
|
|
|
|
function initLogbox(names){
|
|
var logbox=document.getElementById('logstream');
|
|
if(logbox==null)return;
|
|
|
|
logStream = [];
|
|
logStream.push("<td>" + names.join("</td><td>") + "</td>");
|
|
logbox.innerHTML = "<tr>"+logStream.join("</tr><tr>")+"</tr>";
|
|
}
|
|
|
|
var logboxAppend=true;
|
|
|
|
// can append or prepend new states to the log table
|
|
// when we reverse direction we need to reorder the log stream
|
|
function updateLogDirection(){
|
|
logboxAppend=!logboxAppend;
|
|
var logbox=document.getElementById('logstream');
|
|
var loglines=[];
|
|
// the first element is the header so we can't reverse()
|
|
for (var i=1;i<logStream.length;i++) {
|
|
loglines.unshift(logStream[i]);
|
|
}
|
|
loglines.unshift(logStream[0]);
|
|
logStream=loglines;
|
|
logbox.innerHTML = "<tr>"+logStream.join("</tr><tr>")+"</tr>";
|
|
}
|
|
|
|
// update the table of signal values, by prepending or appending
|
|
function updateLogbox(names){
|
|
var logbox=document.getElementById('logstream');
|
|
var signals=[];
|
|
|
|
for(i in names){
|
|
signals.push(busToString(names[i]));
|
|
}
|
|
if(logboxAppend)
|
|
logStream.push("<td>" + signals.join("</td><td>") + "</td>");
|
|
else
|
|
logStream.splice(1,0,"<td>" + signals.join("</td><td>") + "</td>");
|
|
|
|
logbox.innerHTML = "<tr>"+logStream.join("</tr><tr>")+"</tr>";
|
|
}
|
|
|
|
function getMem(){
|
|
var res = Array();
|
|
for(var i=0;i<0x200;i++) res.push(mRead(i));
|
|
return res;
|
|
}
|
|
|
|
function setMem(arr){
|
|
for(var i=0;i<0x200;i++){mWrite(i, arr[i]); setCellValue(i, arr[i]);}
|
|
}
|
|
|
|
function hexWord(n){return (0x10000+n).toString(16).substring(1)}
|
|
function hexByte(n){return (0x100+n).toString(16).substring(1)}
|
|
|
|
function adler32(x){
|
|
var a=1;
|
|
var b=0;
|
|
for(var i=0;i<x.length;i++){
|
|
a=(a+x.charCodeAt(i))%65521;
|
|
b=(b+a)%65521;
|
|
}
|
|
return (0x100000000+(b<<16)+a).toString(16).slice(-8);
|
|
}
|