visual6502/macros.js

539 lines
14 KiB
JavaScript
Raw Normal View History

/*
2010-11-01 17:42:18 +00:00
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;
2010-11-01 19:36:52 +00:00
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(){
2010-10-06 19:51:57 +00:00
// 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)
2010-10-06 19:51:57 +00:00
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(){
2010-10-30 18:05:29 +00:00
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();
2010-11-01 18:02:52 +00:00
cycle++;
while(
2010-11-01 18:02:52 +00:00
!isNodeHigh(nodenames['clk0']) ||
( !isNodeHigh(nodenames['sync']) && isNodeHigh(nodenames['rw']) )
) {
halfStep();
2010-11-01 18:02:52 +00:00
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();
2010-09-19 07:02:26 +00:00
if(ctrace)console.log('initChip done after', now()-start);
}
2010-09-27 17:25:14 +00:00
function signalSet(n){
var signals=[];
2010-11-01 19:36:52 +00:00
for (var i=0; (i<=n)&&(i<presetLogLists.length) ; i++){
for (var j=0; j<presetLogLists[i].length; j++){
signals.push(presetLogLists[i][j]);
2010-09-27 17:25:14 +00:00
}
}
return signals;
}
function updateLogList(names){
2010-11-01 19:36:52 +00:00
// 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]);
2010-11-01 19:36:52 +00:00
}
initLogbox(logThese);
}
var traceChecksum='';
var goldenChecksum;
2010-09-27 17:25:14 +00:00
// 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();
2010-10-01 13:02:03 +00:00
if(animateChipLayout)
refresh();
cycle++;
chipStatus();
}
// triggers for breakpoints, watchpoints, input pin events
// almost always are undefined when tested, so minimal impact on performance
clockTriggers={};
writeTriggers={};
readTriggers={};
fetchTriggers={};
// example instruction tracing triggers
// fetchTriggers[0x20]="console.log('0x'+readAddressBus().toString(16)+': JSR');";
// fetchTriggers[0x60]="console.log('0x'+readAddressBus().toString(16)+': RTS');";
// fetchTriggers[0x4c]="console.log('0x'+readAddressBus().toString(16)+': JMP');";
2010-09-27 17:25:14 +00:00
// simulate a single clock phase with no update to graphics or trace
function halfStep(){
var clk = isNodeHigh(nodenames['clk0']);
eval(clockTriggers[cycle]);
if (clk) {setLow('clk0'); handleBusRead(); }
else {setHigh('clk0'); handleBusWrite();}
}
function handleBusRead(){
if(isNodeHigh(nodenames['rw'])){
var a = readAddressBus();
var d = eval(readTriggers[a]);
if(d == undefined)
d = mRead(readAddressBus());
if(isNodeHigh(nodenames['sync']))
eval(fetchTriggers[d]);
writeDataBus(d);
}
}
function handleBusWrite(){
if(!isNodeHigh(nodenames['rw'])){
var a = readAddressBus();
var d = readDataBus();
eval(writeTriggers[a]);
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') +
2010-10-05 18:12:44 +00:00
'&#8209' + // 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);}
2010-11-01 17:42:18 +00:00
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;
}
2010-09-27 13:16:59 +00:00
function busToString(busname){
// takes a signal name or prefix
// returns an appropriate string representation
2010-09-27 17:25:14 +00:00
if(busname=='cycle')
return cycle>>1;
if(busname=='pc')
2010-09-27 13:16:59 +00:00
return busToHex('pch') + busToHex('pcl');
if(busname=='p')
return readPstring();
2010-09-27 17:25:14 +00:00
if(busname=='tcstate')
2010-10-05 18:12:44 +00:00
return ['clock1','clock2','t2','t3','t4','t5'].map(busToHex).join("");
2010-11-01 17:42:18 +00:00
if(busname=='plaOutputs')
return listActivePlaOutputs();
2010-09-27 13:16:59 +00:00
return busToHex(busname);
}
function busToHex(busname){
2010-09-27 17:25:14 +00:00
// may be passed a bus or a signal, so allow multiple signals
var width=0;
2010-10-30 18:05:29 +00:00
var r=new RegExp('^' + busname + '[0-9]');
for(var i in nodenamelist){
2010-09-27 17:25:14 +00:00
if(r.test(nodenamelist[i])) {
width++;
2010-09-27 17:25:14 +00:00
}
}
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;
2010-09-27 17:25:14 +00:00
// finally, convert from logic values to hex
2010-10-30 18:05:29 +00:00
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);
2010-09-27 17:25:14 +00:00
if (loglevel>0) {
2010-11-01 19:36:52 +00:00
updateLogbox(logThese);
}
selectCell(ab);
}
2010-10-15 02:48:25 +00:00
function goFor(){
var n = headlessSteps;
2010-10-15 02:48:25 +00:00
estimatedHz1();
while(n--){
2010-10-15 02:48:25 +00:00
halfStep();
cycle++;
}
estimatedHz1();
chipStatus();
}
2010-10-02 11:51:33 +00:00
var prevHzTimeStamp=0;
var prevHzCycleCount=0;
var prevHzEstimate1=1;
var prevHzEstimate2=1;
var HzSamplingRate=10;
2010-10-15 02:48:25 +00:00
2010-10-02 11:51:33 +00:00
function estimatedHz(){
if(cycle%HzSamplingRate!=3)
return prevHzEstimate1;
2010-10-15 02:48:25 +00:00
return estimatedHz1();
}
function estimatedHz1(){
2010-10-02 11:51:33 +00:00
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
}
var logbox;
function initLogbox(names){
logbox=document.getElementById('logstream');
if(logbox==null)return;
logStream = [];
logStream.push("<td>" + names.join("</td><td>") + "</td>");
2010-09-27 17:25:14 +00:00
logbox.innerHTML = "<tr>"+logStream.join("</tr><tr>")+"</tr>";
}
2010-11-05 19:53:22 +00:00
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(){
var loglines=[];
logboxAppend=!logboxAppend;
2010-11-05 19:53:22 +00:00
// 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 signals=[];
for(i in names){
2010-09-27 13:16:59 +00:00
signals.push(busToString(names[i]));
}
2010-11-05 19:53:22 +00:00
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);
}