pcjs/my_modules/ecpjs-client/lib/parallelAdder.js

327 lines
13 KiB
JavaScript

/*
* parallelAdder.js
* by Jeff Parsons, May 11, 2012
*/
/*
* Creation of a ParallelAdder object is controlled by the following properties of the
* parmsAdder object:
*
* idResident: ID of the resident register
* idIncident: ID of the incident register
* idCarries: ID of the carries register
*
* However, by the time we're called, initParallelAdders() has already looked up the
* corresponding Register components by the above IDs (so that we don't have to be
* involved in the "wiring" process) and passes them to us as:
*
* regResident
* regIncident
* regCarries
*
* Our constructor also creates an internal Register object (regScratch) that's used
* to make a copy of the carry bits in the "carries" register after each internal add cycle.
*/
function ParallelAdder(parmsAdder, regResident, regIncident, regCarries)
{
Component.call(this, "ParallelAdder", parmsAdder);
this.regResident = regResident;
this.regIncident = regIncident;
this.regCarries = regCarries;
this.aBitsResident = regResident.getBits();
this.aBitsIncident = regIncident.getBits();
this.aBitsCarries = regCarries.getBits();
this.regScratch = new Register({nBits:regCarries.count()});
this.aBitsScratch = this.regScratch.getBits();
}
Component.subclass(Component, ParallelAdder, {
/*
* These functions implement the necessary Register interfaces that other components,
* like the Selector, expect us to support.
*/
count: function() {
return this.regResident.count();
},
readBit: function(iBit) {
return this.regResident.readBit(iBit);
},
writeBit: function(iBit, b) {
this.regResident.writeBit(iBit, b);
},
/*
* These are the functions unique to ParallelAdder.
*/
add: function(fnNotify) {
this.stopSteps();
this.cCarryCycles = 0;
this.regResident.writeUndefined(false);
this.regResident.updateAll();
this.regIncident.writeUndefined(false);
this.regIncident.updateAll();
this.firstStep(this.stepAddRegisters, fnNotify);
},
stop: function() {
this.stopSteps();
this.regResident.stopSteps();
this.regIncident.stopSteps();
},
stepAddRegisters: function(n) {
this.cCarryCycles++;
/*
* We call the next step directly, to give this step something concrete to do;
* we could have incorporated that code into this step, but it seems cleaner this way.
*/
this.stepClearCarries(n);
if (this.cCarryCycles == 1)
this.addStep(this.stepAddIncidentBits);
else
this.addStep(this.stepAddScratchBits);
this.addStep(this.stepCheckCarries);
return true;
},
stepClearCarries: function(n) {
this.printStep(n, "Clearing carry bits");
this.regCarries.writeAll(false);
if (n !== undefined)
this.regCarries.updateAll();
return true;
},
stepCheckCarries: function(n) {
if (!this.cCarries) {
this.printStep(n, "No carries, addition complete");
return false;
}
this.printStep(n, "Copying carry bits to scratch");
this.regScratch.copyAll(this.regCarries);
return true;
},
stepAddIncidentBits: function(n) {
this.cCarries = 0;
/*
* The following table describes the 4 possible cases of resident(i) and incident(i) bits,
* and what result(i) and carry(i+1) bits must be generated in each of those cases to simulate
* the addition of a resident and incident bit:
*
* case resident(i) incident(i) result(i) carry(i+1)
* ---- ----------- ----------- --------- ----------
* 1 false "+" false "=" false false
* 2 false "+" true "=" true false
* 3 true "+" false "=" true false
* 4 true "+" true "=" false true
*
* Prior to adding the incident bits, we cleared all the carry bits, so we only have to *set* carry bits,
* never *clear* them. As for the result bits, they must be written back to the resident bits register,
* so we must always write the resident result bit -- which you can tell from the table above is equivalent
* to an XOR operation (ie, true if only ONE of either the resident or incident bits is true, false if BOTH
* are false or true).
*/
this.printStep(n, "Adding incident bits");
for (var i=0; i < this.aBitsIncident.length; i++) {
bIncident = this.aBitsIncident[i];
bResident = this.aBitsResident[i];
if (bResident && bIncident) {
if (i+1 < this.aBitsCarries.length) {
this.aBitsCarries[i+1] = true;
this.cCarries++;
}
}
this.aBitsResident[i] = (bResident? !bIncident : bIncident); // the closest thing we have to a logical XOR operation
}
if (n !== undefined) {
this.regResident.updateAll();
this.regCarries.updateAll();
}
return true;
},
stepAddScratchBits: function(n) {
this.cCarries = 0;
/*
* We take advantage of the fact that on iteration i, all scratch bits below bit index i must be zero,
* so we can start checking scratch bits (ie, the carry bits from the previous iteration) at bit index "cCarryCycles".
*
* NOTE: We should assert that all bits below that index are indeed zero, and that we never end up here when
* cCarryCycles == aBitsScratch.length (because there shouldn't have been any saved carries from the length-1 iteration).
*/
this.printStep(n, "Adding previous carry bits");
for (var i=this.cCarryCycles; i < this.aBitsScratch.length; i++) {
bScratch = this.aBitsScratch[i];
bResident = this.aBitsResident[i];
if (bResident && bScratch) {
if (i+1 < this.aBitsCarries.length) {
this.aBitsCarries[i+1] = true;
this.cCarries++;
}
}
this.aBitsResident[i] = (bResident? !bScratch : bScratch); // the closest thing we have to a logical XOR operation
}
if (n !== undefined) {
this.regResident.updateAll();
this.regCarries.updateAll();
}
return true;
}
});
/*
* initParallelAdders()
*
* Initializes all the necessary HTML to construct the component as spec'ed.
*
* Note that each element (e) of class "parallelAdder" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the ParallelAdder constructor
* expects; specifically:
*
* idResident: ID of register containing "resident" binary digits
* idIncident: ID of register containing "incident" binary digits
* idCarries: ID of register used for recording resulting carries
*/
function initParallelAdders()
{
var aeAdders = Component.getElementsByClass(window.document, "parallelAdder");
for (var iAdder=0; iAdder < aeAdders.length; iAdder++) {
var eAdder = aeAdders[iAdder];
var parmsAdder = Component.getComponentParms(eAdder);
//
// Let's find all the prerequisite register components next....
//
var regResident = Component.getComponentByID(parmsAdder.idResident);
var regIncident = Component.getComponentByID(parmsAdder.idIncident);
var regCarries = Component.getComponentByID(parmsAdder.idCarries);
if (!regResident) regResident = new Register();
if (!regIncident) regIncident = new Register();
if (!regCarries) regCarries = new Register();
//
// Now we can create the ParallelAdder object, record it, and wire it up to the associated document elements.
//
var adder = new ParallelAdder(parmsAdder, regResident, regIncident, regCarries);
initParallelAdderControls(adder, eAdder);
}
}
/*
* initParallelAdderControls(reg, eReg)
*
* For each Adder object created by initParallelAdders(), this function looks for any controls that have been defined
* along with the adder element in the current document, and "wires" them as needed.
*
* The following controls are supported:
*
* One optional 'output' control of class "status"
* One optional 'button' control of class "add"
* One optional 'button' control of class "test"
* One optional 'button' control of class "step" (if this exists, it will override any "step" setting above)
*/
function initParallelAdderControls(adder, eAdder)
{
adder.eAdder = eAdder;
var aeControls = Component.getElementsByClass(eAdder.parentNode, "controls");
for (var iControl = 0; iControl < aeControls.length; iControl++) {
var aeChildren = aeControls[iControl].childNodes;
for (var i=0; i < aeChildren.length; i++) {
var e = aeChildren[i];
if (e.nodeType != document.ELEMENT_NODE)
continue;
var sClass = e.getAttribute("class");
if (e.nodeName == "BUTTON" && sClass == "add") {
e.onclick = function() {
adder.stop();
addParallelValues(adder);
};
continue;
}
if (e.nodeName == "BUTTON" && sClass == "test") {
adder.eTest = e;
e.onclick = function() {addStartParallelTest(adder);};
continue;
}
if (e.nodeName == "BUTTON" && sClass == "step") {
adder.setStep(e);
continue;
}
if (e.nodeName == "DIV" && sClass == "status") {
adder.setStatusUpdate(
function(e) {
return function(s) {
e.innerHTML = (s? "<span>"+s+"</span>" : "");
};
}(e)
);
continue;
}
}
}
}
/*
* Function called by the anonymous click handler for the "Add" button.
*/
function addParallelValues(adder)
{
adder.stop();
var vResident = adder.regResident.getValue();
// adder.printStatus("Resident register loaded: " + vResident);
var vIncident = adder.regIncident.getValue();
// adder.printStatus("Incident register loaded: " + vIncident);
adder.add(function() {
var s1 = "Success", s2 = "==";
var vResult = adder.regResident.getValue();
if (vResult != vResident + vIncident) {s1 = "Error"; s2 = "!=";}
adder.printStatus(s1 + ": result (" + vResult + ") " + s2 + " resident (" + vResident + ") + incident (" + vIncident + ")");
});
}
/*
* Function called by the anonymous click handler for the "Test" button.
*/
function addParallelTestValues(adder)
{
adder.cTests++;
var vResident = Math.random() * 0.5;
var vIncident = Math.random() * 0.5;
adder.regResident.writeValue(vResident, function() {
vResident = adder.regResident.getValue();
// adder.printStatus("Resident register loaded: " + vResident);
adder.regIncident.writeValue(vIncident, function() {
vIncident = adder.regIncident.getValue();
// adder.printStatus("Incident register loaded: " + vIncident);
adder.add(function() {
var vResult = adder.regResident.getValue();
adder.cTotalCycles += adder.cCarryCycles - 1; // we don't want to count the initial addition, just the addition of carries
adder.printStatus("Addition complete: " + vResult + " (" + adder.cCarryCycles + " carry cycles, " + (adder.cTotalCycles/adder.cTests) + " average)");
if (vResult != vResident + vIncident)
adder.printStatus("Error: result (" + vResult + ") != resident (" + vResident + ") + incident (" + vIncident + "): " + (vResident + vIncident));
else
if (adder.cTests < 1000) {
addParallelTestValues(adder);
return;
}
addStopParallelTest(adder);
});
});
});
}
function addStartParallelTest(adder)
{
adder.stop();
adder.eTest.innerHTML = "Stop";
adder.eTest.onclick = function() {addStopParallelTest(adder);};
adder.cTests = 0;
adder.cTotalCycles = 0;
addParallelTestValues(adder);
}
function addStopParallelTest(adder)
{
adder.stop();
adder.eTest.innerHTML = "Test";
adder.eTest.onclick = function() {addStartParallelTest(adder);};
}
/*
* Initialize all the components on the page.
*/
web.onInit(initParallelAdders);