// 10.11.2013 Projektstart
// 16.11.2013 Erste Vorfuehrung bei RetroPulsiv
// 25.11.2013 Trace geht
// 26.11.2013 2600 Zeilen, davon (SLS 500, Komp 200, N6502 1100)
// 27.11.2013 Trace geht besser :))
// 29.11.2013 3100 (SLS 800, Komp 200, N6502 1200) Zeilen und der neue, bessere Assembler geht immer noch nicht richtig.
// 04.12.2013 3200 (SLS 850, Komp 200, N6502 1250) Zeilen und der Assembler kann immer noch keine Vorwaertsreferenzen und Ausdruecke :(
// 17.12.2013 3850 (SLS 1120, Komp 250, N6502 1470) Zeilen und ("""), aber schneller ists geworden und Sound geht auch schon fast und der Assember kann jetzt alle Parameter richtig erkennen
// 25.12.2013 3850 (SLS 1100, Komp 600, N6502 1470) Zeilen und ("""), Sound, RNG, Apple Blockgrafik geht, Umbau auf Animationframe
// 29.12.2013 (HTML 890, SLS 1160, Komp 812, N6502 1477) Zeilen und ("""), Sound, RNG, Apple Blockgrafik geht, Umbau auf Animationframe
//
// Todo
// - Replace Word and Address Constants (0xFF, 0xFFFF) by some symbolic variables
// - Assembler: Param (in Pseudo-Ops) might be converted into some mapping to allow automatic parsing
// - Support Memory Exception - Call a coresponding ReadFail/WriteFail within the issuing CPU
// - Multi-CPU Support in General
// - SLS.Require Karten in der Source um in einem Programm eine Umgebung zu definieren
// - 'simpilifizierter' Assembler um SLS.Require Karten zu lesen und eine Umgebung automatisch daraus aufzubauen.
// - Assembler rightnow capitalizes everything, so no lower case literals - then again, lower case
// characters are (for) inferiour beeings - assembly men don't need em. Real men do Hex.
// For now resolved by not uplifting param at all ... still not cool
// - Foreward References !
// - Much of the functions of SLS, CPU and Components may be moved into prototypes to save space
// ... then again, most objects only exist once. Also private methods ( var x = function(){...})
// might be of interest for information hiding.
// - Byteorder still not defined.
// - WriteThru for Components
// - MicroSlices (1000 Insts) and I/O polling detection
// - Create a Container Component to speed up address decoding (Combining IO devices into a 'IO-Area' like first stage decoding or such)
// General Incompatibilities:
// - ArrayBuffer / Uint8Array / Uint16Array is used for Memory Components - IE is out :)
// - performance.now() is used - good bye Safari - well, Date.now() could be used as a (rough) replacement - not likely
// Now, it seams as if one Windows FireFox is using for performance.now() a timer with 15-16 ms (80Hz?) granuality (due some Windows internal default)
// ... not cool, thus Date.now() is more preceise here. Seams as if Safari is some lucky bastard :))
// - Additional parameters of setTimeout/setIntervall are used, so IE (at least prior to 10) is again out.
//////////////////////
// System Object(s) //
//////////////////////
// Der Manager
//
// (sung to 'Big Fat Road Manager' by the arrogant worms)
function SLS_Manager()
{
var that = this;
var Cpu = null;
this.Components = []; // hier gehoert noch was hin fuer die verschiedenen Adressraeume
this.ReadChain = []; // List of all Read EntryPoints
this.WriteChain = []; // List of all Write EntryPoints
// Right now only one CPU is supported. Additinal CPUs have to be added as Components for now.
this.SetCPU = function(cpu){Cpu = cpu;};
this.GetCPU = function() {return Cpu;};
// IO-Monitoring
var IOReadCnt = 0; // IO-Read-Count
var IOReadSer = 0; // IO-Read-Severity
var IOWriteCnt = 0; // IO-Write-Count
var IOWriteSer = 0; // IO-Write-Severity
var IOReadMon = function(severity){
IOReadCnt++; if(severity) IOReadSer += severity; };
var IOWriteMon = function(severity){
IOWriteCnt++; if(severity) IOWriteSer += severity; };
// Missing way to handle different address spaces
this.NoRead = function(){alert("No Memory Read Available"); return -1};
this.NoWrite = function(){alert("No Memory Write Available"); return -1};
this.ReadFail = function(){alert("Memory Read Fail - No component activated"); return -1};
this.WriteFail = function(){alert("Memory Write Fail - No component activated"); return -1;};
// Only one memory right now
this.Read = this.NoRead;
this.Write = this.NoWrite;
// Origially we iterated over each components Read/Write function
// Speed degrade was extreme. So it got changed to a list of Addresses and (simplified) Function entry points
// # of Comp Speed 6502 for each Test
// Op / Cnt / Mem / Sub / RoR
// F-Call 2 14 8 10 13 15
// F-Call 5 9 6,3 6,7 9 10
// F-Call 18 3,6 3,5 3 4 4,5
//
// List 2 17 9 10,5 15 18
// List 5 13 8 9,2 12 14,5
// List 18 7,2 5,8 5,5 7,5 8,4 (6,8-8,2) / (6,9-8,3)
//
// Usually the hit this happened in the last entry
// (and a few optimizations (good for ~15%)inbetween :)
// With hit in last entry we (now) get:
// List 18 9,1 6,4 7,1 9,8 10,8
// With hit in first entry we get:
// List 18 19 9,5 11 13 20
//
// Todo: Create a Container Component to speed up address decoding
this.ReadLp = function(Addr)
{
var i = (that.ReadChain.length - 1) | 0;
var k = that.ReadChain;
var ret = -1;
while( i>=0 )
{ // this komplex while condition offers a heavy speed bonus if most components are located above the addr in question (usually true for Programm code)
while((k[i][0] > Addr) || (Addr > k[i][1])) {i--;if(i < 0) break;} ;
if(i >= 0 )
{
ret = k[i][3](Addr);
if(ret !== -1) return ret;
}
}
return -1; // Hier eigentlich Fehler ausgeben weil sich garkeiner zustaendig gefuehlt hat.
};
this.WriteLp = function(Addr, Value)
{
var i = (that.WriteChain.length - 1) | 0;
var k = that.WriteChain;
var ret = -1;
while( i>=0 )
{
while((k[i][0] > Addr) || (Addr > k[i][1])) {i--;if(i < 0) break;} ;
if(i >= 0 )
{
ret = k[i][3](Addr, Value);
if(ret !== -1) return ret;
}
}
return -1; // Hier eigentlich Fehler ausgeben weil sich garkeiner zustaendig gefuehlt hat.
};
this.AddComponent = function( comp )
{
that.Components.unshift( comp );
if(comp.Typ != "CPU") // Everything except CPUs may have Read/Write function
{ // Well, we could modify this to have the CPUs function as last in line, so a CPUs Bus Error
// function could kick in. As a result MultiRead would become some sort of default - including
// its speed penalty. Maybe it can be eased with some extra handling for CPUs, but noe idea how.
if(comp.ReadV && comp.ReadV[2] && (typeof(comp.ReadV[3]) === "function")) // Has Read Vector with and size > 0 and a function
{
that.ReadChain.push(comp.ReadV);
switch(that.ReadChain.length)
{ // Select right function - ReadLp halves execution speed
case 0: that.Read = that.NoRead;
break;
case 1: that.Read = comp.ReadV[3];
break;
default: that.Read = that.ReadLp;
break;
}
}
if(comp.WriteV && comp.WriteV[2] && (typeof(comp.WriteV[3]) === "function")) // Has Write function
{
that.WriteChain.push(comp.WriteV);
switch(that.WriteChain.length)
{
case 0: that.Write = that.NoWrite;
break;
case 1: that.Write = comp.WriteV[3];
break;
default: that.Write = that.WriteLp;
break;
}
}
if(comp.IORate) comp.IORate(IOReadMon, IOWriteMon); // Add IO Monitoring if a hook function is defined
}
if(comp.Typ == "CPU") // Everything except CPUs may have Read/Write function
{ // if it's the first CPU, we make it our default CPU
if(!Cpu) Cpu = comp;
}
};
// Resove all late Bindings
// Needs to be called prior to running
// Maybe we can drop this some time ... like as part of executing the first instruction or such ...
this.Resolve = function()
{
if(Cpu)
{
Cpu.SetMemF(0,that.Read,that.Write,that.Write)
Cpu.GetContext = that.GetContext;
Cpu.SetContext = that.SetContext;
}
}
// Context Save/Restore For Concurent use or diagnosis
// As long as "The Regs" describe the complete context, Function will be inseted by SLS_Manager
// Maybe move this into some kind of CPU Prototype Object
this.GetContext = function()
{ // Traverse all Cpu.Regs Mappings and fetch their statii
var ret = { };
var tmp1 = Cpu.Regs;
for( var grp in tmp1 )
{
var tmp2 = tmp1[grp];
ret[grp] = {};
var tmp3 = ret[grp];
for( var reg in tmp2 )
{
var tmp4 = tmp2[reg];
tmp3[tmp4.Name] = tmp4.Get();
}
}
return ret;
};
this.SetContext = function(context)
{ // Traverse Context and set Cpu.Regs statii accordingly
var tmp1 = context;
for( var grp in tmp1 )
{
var tmp2 = tmp1[grp];
var tmp3 = Cpu.Regs[grp];
for( var reg in tmp2 )
{
tmp3[reg].Set(tmp2[reg]);
}
}
};
/////////////////
// Task Object //
/////////////////
// Provides an execution environment.
// If there's only one (like with CPU) A hook can be used so no external reference needs to be kept.
// There can be more than one, but then we need a way to multiply Components and such ... no idea how.
//
// Maybe a redesign for a more self contained Handling could be useful.
// No idea what relation is best, so tight now its some kind of a sub-object covered within.
//
this.TRef = function(count,speed,done,step)
{
var that = this;
this.Active = false; // Simulation (with this Task/Context) active
this.Forever = false; // Just run ahead
var Break = false; // Stop ASAP (set from external (User-)Function via TRef.Stop()
var Suspend = false; // Suspend Execution (set from external (User-)Function via TRef.Suspend() TRef.Continue()
// this.Cpu = null;
this.DoneF = null;
this.StepF = null;
this.Ret = 0; // Returncode of DoExecSlice() / cpu.Exec()
this.Start = 0; // from performance.now()
this.End = 0;
this.StartA = 0; // from Date.now()
this.EndA = 0;
// this.CntTot = 0;
this.CntDone = 0;
this.ClocksDone = 0;
this.SliceUsage = 50; // 50% activity
this.SliceTime = 16; // 16 ms per Slice (???)
// this.SliceUtil = 10; // Utilization
// this.TargetCps = 1; // Target Speed in Clocks per second
// this.ICnt = 0; // Instructions summed up for Slice Calculation
// this.CCnt = 0; // Clocks summed up for Slice Calculation
// this.TCnt = 0; // Time in ms summed up for Slice Calculation (performance.now) (Float)
// this.TCntA = 0; // Time in ms summed up for Slice Calculation (Date.now)
// this.ICnt1 = 0; // Copy from start to allow relevant totaling
// this.CCnt1 = 0; // "
// this.TCnt1 = 0; // "
// this.TCntA1 = 0; // "
// this.SliceSize = 0, // Instructions per Slice
this.MicroCnt = 17; // Designated number of MicroSlices per Slice (should be prime)
this.MicroMinSize= 109; // Minimum Size of a Microslice (should be prime)
this.ThreshInS = 10; // Threshold of Input instructions per Slice
this.ThreshInM = 15; // Threshold of Input instructions per MicroSlice
this.ThreshOutS = 5; // Threshold of Output instructions per Slice
this.ThreshOutM = 15; // Threshold of Output instructions per MicroSlice
this.Slices = 0; // Number of Slices up to now
this.MicroSlices = 0; // Number of MicroSlices up to now
this.TimerID = null;
// this.Context = null; // CPU Context saved at the end of each execution.
this.Cpu = Cpu; // take CPU from SLS_Manager Object
this.Context = that.Cpu.GetContext();
this.SliceUtil = (this.SliceTime * this.SliceUsage) / 100;
this.CntTot = count;
this.DoneF = done;
this.StepF = step;
this.ICnt = Math.round(this.Cpu.InstRate * this.SliceUtil * 16); // Set instructions count as if a default slice has been executed
this.CCnt = Math.round(this.Cpu.ClockRate * this.SliceUtil * 16); // Set clock count as if a default slice has been executed
this.TCnt = Math.round(this.SliceUtil); // Set run time as if a default slice has been executed
this.TCntA = Math.round(this.SliceUtil); // Set run time as if a default slice has been executed
this.TargetCps = (this.Cpu.ClockRate * 1000);
// Calculate preliminary
that.SliceSize = Math.ceil(this.ICnt / this.TCnt * this.SliceUtil) | 0; // Calculate Next Execution Slice as Integer
that.SliceSizeMx = (that.SliceSize * 1.6) | 0;
this.ICnt1 = this.ICnt;
this.CCnt1 = this.CCnt;
this.TCnt1 = this.TCnt;
this.TCntA1= this.TCntA;
// Set Relative Speed in %
this.SetSpeed = function( RelSpd ) { that.TargetCps = (that.Cpu.ClockRate * 1000 * 100 * RelSpd) / 100;};
this.Cancel = function( ) { Break = true;};
this.Suspend = function( ) { Suspend = true;};
this.Continue = function( ) { Suspend = false; that.DoExecR()};
this.Suspended = function( ) { return Suspend;};
// (leidlich) Echtzeitausfuehrung
// Break-Bearbeitung fehlt
// Und die berechnung der ausgefuehrten Instruktionen bei Fehler/Break
// Geschwindigkeitsdrosslung auf Echtzeit
// Die Verschiebung durch die Wartezeit auf den Intervall fehlt auch noch (Echtzeit einbeziehen)
// instead of the throtled (as in minimum time 4..10ms) setTimeout(fn,0) a method like
// described here http://dbaron.org/log/20100309-faster-timeouts could be used
// Finally settled for requestAnimationFrame(). Throtling will be done via Instructions per Slice ... somehow
// RealExec
// Execute Programm as a series of slices via intervall timer - could be also modified for execution via animation frame.
// count: Number of Iterations - if void, 1 is used
// done: Callback, Invoked after all slices are done, with Pre/Post Performance Counters
// and Context (done(that, ret)) ** Mabe later on with PreRun, PostRun, PreContext, PostContext
// step: Callback, Invoked after each slice have been executed with Pre/Post Performance Counters
// and Context (step(that, PreRun, PostRun, PreContext, PostContext))
// first: Callback, Invoked before the first slice is to be executed with Pre Performance Counters
// and Context (step(that, PreRun, undefined, PreContext, undefined))
// Returncodes so far:
// 0 - Nothing wrong
// Positive - From CPU Simulation
// 1 Stop from CPU ( 6502: BRK, 8080: HLT, ...)
// Negative - From Components or Simulation itself
// - 1 - Memory read fail
// - 2 - Execution ended via Cancel()
//
this.RealExec = function (count,done,step,first)
{
if(that.Active) { alert("Simulation already running"); return -1;};
that.Active = true; // Execution active
if(typeof(count) === 'undefined') count = 1;
Break = false; // Don't stop right away :)
that.Start = performance.now();
that.End = performance.now();
that.StartA = Date.now();
that.EndA = Date.now();
that.Slices = 0;
that.MicroSlices = 0;
that.CntTot = count;
that.CntDone = 0;
that.ClocksDone = 0;
that.DoneF = done;
that.StepF = step;
that.FirstF = first;
// If Cycle counter hits 30 bit (~1G), adjust counters.
// Basic asumption #of cycles > #of instructions > #of ms used
// If this isnt true, sequence is to be changed, or all 3 values
// to be checked: (that.CCnt & 0x400000 || that.ICnt & 0x400000 || that.TCnt & 0x400000)
that.SliceSize = Math.ceil((that.ICnt / that.TCnt) * that.SliceUtil) | 0; // Calculate Next Execution Slice as Integer
that.SliceSizeMx = (that.SliceSize * 1.6) | 0;
if(that.CCnt & 0x40000000){that.ICnt >>= 6; // This might become a problem if we execute more than 16G Cycles
that.CCnt >>= 6; // in less than 1s or 1000 instructions *LOL*
that.TCnt >>= 6; // Well, in case, just reduce shift count by 1 :)
that.TCntA /= 64;}
that.ICnt1 = that.ICnt;
that.CCnt1 = that.CCnt;
that.TCnt1 = that.TCnt;
that.TCntA1 = that.TCntA;
if(typeof(that.FirstF) == "function")
{
that.FirstF(that, that.Cpu.Perf.Get(), undefined, that.Cpu.GetContext(), undefined);
}
var ret = that.DoExecR(); // Execute first Slice
return ret;
}
// hier fehlt noch eine Funktionalitaet die die Ausfuehrungsgeschwindigkeit An die 'realitaet' bzw. den gewuenschten Faktor anpasst.
// Execute (another) timeslice (invoked direct for fist call or via requestAnimationFrame() for follow ups slices)
// due the principial single thread nature of JavaScript, any external signaling can only arrive here
// End execution when - done
// - break is set
// Don't execute while - suspend is set
this.DoExecR = function()
{
var ret;
if(Suspend) return 0; // right now no special code her ... maybe some future extension here.
if(Break)
{ // Stop Execution
that.Ret = -2;
that.End = performance.now();
that.EndA = Date.now();
if(typeof(that.DoneF) == "function") setTimeout(that.DoneF, 0, that, ret); // Call asynchronus finish function
that.Active = false;
return ret;
}
var Rest = that.CntTot - that.CntDone;
if( (Rest > that.SliceSizeMx) ||
that.Forever) Rest = that.SliceSize;
ret = that.DoExecSlice(Rest); // Execute a time slice
if(!that.Forever &&
that.CntDone >= that.CntTot)
{ // Scheduled run has been done
that.End = performance.now();
that.EndA = Date.now();
if(typeof(that.DoneF) == "function") setTimeout(that.DoneF, 0, that, ret); // Call asynchronus finish function
that.TimerID = 0
that.Active = false;
return ret;
};
that.TimerID = requestAnimationFrame(that.DoExecR);
// Calculate Next Execution Slice
that.SliceSize = Math.ceil((that.ICnt / that.TCnt) * that.SliceUtil) | 0;
that.SliceSizeMx = (that.SliceSize * 1.6) | 0;
if(that.CCnt & 0x40000000){that.ICnt >>= 6; // This might become a problem if we execute more than 16G Cycles
that.CCnt >>= 6; // in less than 1s or 1000 instructions *LOL*
that.TCnt >>= 6; // Well, in case, just reduce shift count by 1 :)
that.TCntA /= 64;}
return ret;
}
this.DoExecR = this.DoExecR.bind(this);
// Execute a Slice and update all counters etc.
this.DoExecSlice = function(count)
{
var ret;
var RunTime;
var RunTimeA;
var RunInst;
var RunClock;
var PreRun;
var PreContext;
var PostRun;
that.Slices++;
PreRun = that.Cpu.Perf.Get(); // Get Performance Counters
if(typeof(that.StepF) == "function") PreContext = that.Cpu.GetContext();
RunTime = performance.now();
RunTimeA = Date.now();
IOReadCnt = 0;
IOReadSer = 0;
IOWriteCnt = 0;
IOWriteSer = 0;
// var x=count;
// Run it as Micro slices with I/O monitoring
var n = (count / that.MicroCnt) | 0;
if( n < that.MicroMinSize ) n = that.MicroMinSize;
var m = (n * 1.5) |0;
var rdBrk = (that.ThreshInS * n / 100) |0; // Input Threshold per Slice
var wrBrk = (that.ThreshOutS * n / 100) |0; // Output Threshold per Slice
while(count > 0)
{
if(count < m) n = count;
ret = that.Cpu.Exec(n); // 'nuff played, lets do real work
count -= n;
that.MicroSlices++;
if((IOReadCnt +IOReadSer ) > rdBrk) break;
if((IOWriteCnt+IOWriteSer) > wrBrk) break;
if(ret) break;
}
// console.log("Slice="+that.Slices+"Count="+count+"/"+x+" IOReadCnt/Ser="+IOReadCnt+"/"+IOReadSer+" rdBrk="+rdBrk+" IOWriteCnt/Ser="+IOWriteCnt+"/"+IOWriteSer+" wrBrk="+wrBrk);
RunTime = (performance.now() - RunTime) || 1; // Runtime in ms, 1 ms minimum
RunTimeA = (Date.now() - RunTimeA) || 1; // Runtime in ms, 1 ms minimum
PostRun = that.Cpu.Perf.Get(); // Get Updated Performance Counters
RunInst = PostRun.ICnt - PreRun.ICnt;
RunClock = PostRun.CCnt - PreRun.CCnt;
that.Context = that.Cpu.GetContext();
that.ICnt += RunInst;
that.CCnt += RunClock;
that.TCnt += RunTime;
that.TCntA += RunTimeA;
that.CntDone += RunInst;
that.ClocksDone += RunClock;
that.Ret = ret;
if(typeof(that.StepF) == "function")
{
that.End = performance.now(); //*
that.EndA = Date.now(); //*
PreRun.TCnt = 0;
PreRun.TCntA = 0;
PostRun.TCnt = RunTime;
PostRun.TCntA = RunTimeA;
// To improve 'responsiness' this could be done via setTimeout(that.StepF, 0, that, PreRun, PostRun) then again, with high load steps could get lost.
that.StepF(that, PreRun, PostRun, PreContext, that.Context);
}
return ret;
}
// Executes Single Operations with callback - this will always run with maximum profiling ( well, at some point )
// Mainpurpose is Programm Analysis and/or Debugging
// Performance Analyze/Display function.
//
// count: Number of Iterations - if void, 1 is used
// done: Callback, Invoked after each Instruction with Pre/Post Performance Counters
// and Context (done(that, PreRun, PostRun, PreContext, PostContext))
// It must return 0 or or a non-nevative value to continue
// Any negative number will end execution. It is suggested to use numbers below -1024 for application codes.
// step: Callback, Invoked after all Instruction have been executed with Pre/Post Performance Counters
// and Context (step(that, PreRun, PostRun, PreContext, PostContext))
// It must return 0 or or a non-nevative value to continue
// Any negative number will end execution. It is suggested to use numbers below -1024 for application codes.
//
this.TraceExec = function(count,done,step)
{
if(typeof(count) === 'undefined') count = 1;
var ret;
var RunTime; // Runtime calcualtion is not realy useful here - unless we get sume microsecond resolution
var RunInst;
var RunClock;
var PreRun;
var PreRunT;
var PostRun;
var PreContext;
var PreContextT;
var PostContext;
// that.Slices++; Not a Slice
PreRunT = that.Cpu.Perf.Get();
PreContextT = that.Cpu.GetContext();
RunTime = Date.now();
PreRun = PreRunT;
PreContext = PreContextT;
for(var i = count;i;i--)
{
ret = that.Cpu.ExecOnceP(); // One Instruction with maximum Profileing or such. Not sure if it stays that way - I'd prefer to have the *R/*N hidden
that.Ret = ret;
PostRun = that.Cpu.Perf.Get(); // Get Updated Performance Counters
RunClock = PostRun.CCnt - PreRun.CCnt;
RunInst = PostRun.ICnt - PreRun.ICnt;
that.ICnt += RunInst;
that.CCnt += RunClock;
that.CntDone += RunInst;
that.ClocksDone += RunClock;
if(typeof(step) == "function")
{ // Do all the data only if there is a trace callback at all
PostContext = that.Cpu.GetContext();
PreRun.TCnt = 0;
PostRun.TCnt = 1; // RunTime; - or maybe 1/Cpu.Instrate ??? For the moment we avoide floats
ret = step(that, PreRun, PostRun, PreContext, PostContext);
PreRun = PostRun;
PreContext = PostContext;
}
if(ret < 0) break; // Any negative return is some kind of exception
}
that.Context = that.Cpu.GetContext();
RunTime = (Date.now() - RunTime) || 0; // Just in case all of this was quite slow :))
that.TCnt += RunTime; // that'll be 0 most of the time ... maybe until the counters work ... in a galaxy far away ...
that.Ret = ret;
if(typeof(done) === "function")
{ // Do all the data only if there is a trace callback at all
PostContext = that.Cpu.GetContext();
PreRun.TCnt = 0;
PostRun.TCnt = RunTime; // RunTime; - or maybe 1/Cpu.Instrate ??? For the moment we avoide floats
done(that, PreRunT, PostRun, PreContextT, that.Context);
}
return ret;
}
}
/////////////////////
// Compiler Object //
/////////////////////
// Creates a compiled Programm from a Source.
//
// InFile is a String object with all Source Lines seperated by \n
//
// .Source Contains the Source Programm as Array
// .Liste Contains the Compiled Program
// .LOAD() Loads the Program from .Liste into the target CPUs Memory
//
this.Compilat = function (InFile)
{
var that = this;
// Define all available Pseudo Ops
// todo: Param might be converted into some mapping to allow automatic parsing
// CPU identification needs to be used to select CPU within SLS
this.Instructions =
{ START: { Name: "START",
Short: "programm START",
Long: "Identifies CPU in Use and Starts compilation",
Param: " - Name Property of CPU to be Used",
Exec: null },
END: { Name: "END",
Short: "programm END",
Long: "Ends Source and Defines Start Address",
Param: " - Address/Label to Define Programms Entry Point",
Exec: function(dummy, OPb, Line, wohi)
{
var T1=wohi.EvalPar(Line.Par);
if(!T1 || T1.Resolved !== "A")
{
wohi.Flags++;
Line.Flag = "A009";
Line.FTxt = "Invalid Start Label in END Statement. No Entry defined.";
return 0;
}
LabV = T1.Value;
wohi.Entry = T1.Value;
Line.Used = Line.Used.concat(T1.Used);
Line.Fwrd = Line.Fwrd.concat(T1.Fwrd);
} },
ORG: { Name: "ORG",
Short: "ORiGin",
Long: "Changes Address Level for Generation",
Param: " - Address/Label to Continue From",
Exec: function(dummy, OPb, Line, wohi)
{
var T1=wohi.EvalPar(Line.Par);
if(!T1 || T1.Resolved !== "A")
{
wohi.Flags++;
Line.Flag = "A010";
Line.FTxt = "Invalid Address in ORG Statement. Line skiped";
return 0;
}
LabV = T1.Value;
wohi.Pegel = T1.Value;
Line.Used = Line.Used.concat(T1.Used);
Line.Fwrd = Line.Fwrd.concat(T1.Fwrd);
} },
EQU: { Name: "EQU",
Short: "EQUates",
Long: "Sets a Value for a Label",
Param: " - Address/Label to Continue From",
Exec: function(dummy, OPb, Line, wohi)
{
var T1=wohi.EvalPar(Line.Par);
if(!T1 || T1.Resolved !== "A")
{
wohi.Flags++;
Line.Flag = "A011";
Line.FTxt = "Invalid Address in EQU Statement. Line skiped";
return 0;
}
LabV = T1.Value;
Line.Used = Line.Used.concat(T1.Used);
Line.Fwrd = Line.Fwrd.concat(T1.Fwrd);
} },
DC: { Name: "DC",
Short: "Define Constant",
Long: "Allocates and Populates Storage.",
Param: " - Data to be Stored - right now only bytes",
Exec: function(dummy, OPb, Line, wohi)
{
for(var i=1; i< Line.Syslist.length;i++)
{
var val=wohi.EvalPar(Line.Syslist[i]);
var T1=wohi.EvalPar(Line.Syslist[i]);
if(!T1 || T1.Resolved !== "A")
{
wohi.Flags++;
Line.Flag = "A012";
Line.FTxt = "Invalid Address in DC Statement (" + i.toString() +"). Line generation aborted.";
return 0;
}
Line.Typ = 'I';
Line.Dta.push(T1.Value);
Line.Used = Line.Used.concat(T1.Used);
Line.Fwrd = Line.Fwrd.concat(T1.Fwrd);
}
} },
DS: { Name: "DS",
Short: "Define Storage",
Long: "Allocates Storage.",
Param: " - Data Types for Allocation - right now only bytes",
Exec: null },
SLS: { Name: "SLS",
Short: "Simulation Extensions",
Long: "Control simulation environment.",
Param: ".requires - define needed component",
Exec: null } };
// Line Types
this.LineTypes = { I: {Name: "Instruction"},
P: {Name: "Pseudo Instruction"},
M: {Name: "Macro Instruction"},
L: {Name: "Label Line"},
E: {Name: "Empty Line"},
C: {Name: "Comment"} ,
U: {Name: "Unknown"} ,
X: {Name: "Frickin' Unknown"} };
// This Mapping is Basicly Our Assembler
// It runs over all Source Lines and ries to assemble them into our Programm structure
// Only thing left to do is a second pass to resolve foreward references.
////////////
// Loader //
////////////
// Loads a compiled Programm into Memory using the Targets write function
// Todo, change for a Load() function to enable 'ROM' Writing.
this.Load = function(){ that.Liste.forEach( function(e){if(e.Dta){ var Pegel = e.Addr; e.Dta.forEach( function(ei){ SLS.Write(Pegel++, ei); }); } } );
CPU.Regs.ISA.PC.Set(that.Entry); };
// Evaluate a parameter expression
// Returns undefined or a mapping
// Value: Evaluated Value
// Resolved: A - All elements resolved
// V - contains (possible) forward refference (not all elements resolved)
// E - Errornous expression
// Types: Array with C - Constant
// L - Label
// V - (possible) forward Label
// P - Pegel relativ
// Labels: Array with all labels encountered
// Forwards: Array with all possible foreward references
this.EvalPar = function(exp)
{ // Well, right now, just a single parameter ... expressions to follow
var Val = undefined;
var Tps = [];
var Res = "A";
var Used = [];
var Fwrd = [];
// a few quick and dirty parameter evaluations for >, * and *+/*-
var CA0;
CA0 = exp.charAt(0); // Check for hi/lowbyte notation (8-Bit-CPUs ... doesn't realy belong here, does it?)
var lb = (CA0 === '<'); // Rather be moved into some CPU-Specific (unary) operator resolution
var hb = (CA0 === '>');
if( lb || hb ) exp = exp.slice(1);
if(exp === '*')
{
Val = that.Pegel;
Tps = ["P"];
}
else
{
CA0 = exp.charAt(0);
var pp = false;
var mp = false;
if(CA0 === '*')
{
CA0 = exp.charAt(1);
pp = (CA0 === '+');
mp = (CA0 === '-');
if( pp || mp ) exp = exp.slice(2);
Types = ["P"];
};
var T1 = that.EvalConst(exp);
if(!T1) return T1; // -> Undefined
Val = T1.Value;
Tps = Tps.concat(T1.Typ);
if(T1.Typ === "L") Used.push(exp.toUpperCase());
if(T1.Typ === "V") Fwrd.push(exp.toUpperCase());
if(pp) Val = that.Pegel + Val;
if(mp) Val = that.Pegel - Val;
};
if(lb) Val = Val & 0xFF;
if(hb) Val = (Val >> 8) & 0xFF;
return {Value: Val, Resolved: Res, Types: Tps, Used: Used, Fwrd: Fwrd};
};
// Evaluate a constant value
// Returns undefined or a mapping
// Typ: C - Constant
// L - Label
// V - (possible) forward Label
// Value: Evaluated Value
this.EvalConst = function(exp)
{
var T1;
if(typeof(Cpu.EvalConst) === "function")
{
T1 = Cpu.EvalConst(exp); // Check CPU Specific Notation
if(typeof(T1) !== "undefined") return {Typ: "C", Value: T1};
}
if(that.Labels[exp.toUpperCase()]) return {Typ: "L", Value: that.Labels[exp.toUpperCase()].Value};
var rx = /^X\'([0-9a-f]{1,8})\'$|^B\'([0-1]{1,32})\'$|^0x([0-9a-f]{1,8})$|^0o([0-7]{1,11})$|^0b([0-1]{1,32})$|^([0-9a-f]{1,8})h$|^([0-7]{1,11})o$|^([0-1]{1,32})b$|^C?\'(.)\'$|^(\d{1,9})$/i.exec(exp);
if(rx)
{
if(rx[1]) return {Typ: "C", Value: parseInt(rx[1],16)}; // X'...' (1..8)
if(rx[2]) return {Typ: "C", Value: parseInt(rx[2],2)}; // B'...' (1..32)
if(rx[3]) return {Typ: "C", Value: parseInt(rx[3],16)}; // 0x... (1..8)
if(rx[4]) return {Typ: "C", Value: parseInt(rx[4],8)}; // 0o... (1..11)
if(rx[5]) return {Typ: "C", Value: parseInt(rx[5],2)}; // 0b... (1..32)
if(rx[6]) return {Typ: "C", Value: parseInt(rx[6],16)}; // ...h (1..8)
if(rx[7]) return {Typ: "C", Value: parseInt(rx[7],8)}; // ...o (1..11)
if(rx[8]) return {Typ: "C", Value: parseInt(rx[8],2)}; // ...b (1..32)
if(rx[9]) return {Typ: "C", Value: rx[9].charCodeAt(0)}; // C'.'/'.' (1)
if(rx[10]) return {Typ: "C", Value: parseInt(rx[10],10)}; // ... (1..9)
return undefined; // something realy strange happened
}
// maybe some forward label ?
rx = exp.match(RxLab);
if(!rx) return undefined;
if(rx[0] !== exp) return undefined;
return {Typ: "V", Value: undefined};
}
// retruns one parameter (element) from a (possible) list
this.ScanPar = function(par)
{
var T1 = par;
var TS = [];
var instr = false;
var TOS = "";
var SLN = 0;
for(var i=0;i