/* * register.js * by Jeff Parsons, May 7, 2012 */ /* * Creation of a Register object is controlled by the following properties of the * parmsReg object: * * nBits: number of bits * signed: true if signed (default), false otherwise * bit0Exp: the power-of-two for bit 0 (default is zero) * labels: true for labels, false otherwise (default) * * A Register object can be as small as a single bit, and in fact, a 1-bit Register * is exactly how you would create the equivalent of a Bit object. However, the more * common use of this class is to create a bit array. * * Internally, the bit indexes of a register correspond to the array indexes of aBits * (ie, aBits[0] contains the value for bit 0, aBits[1] is bit 1, etc). And the lowest * bit index represents the lowest power-of-two of the value represented by the register. * * The display of a Register object "cell" is handled by the given updateBit() function: * * updateBit(iBit, f) * * If f is undefined, the cell will be blanked; otherwise, either a "0" or a "1" will be * displayed. However, that's just the standard implementation; the caller is free to * define any other behavior (Remember: a register shouldn't care what it looks like). * * Internally, there are also "helper" properties (eg, decimalValue) and methods * (eg, writeValue()) used, for example, to help write data into the register. * Here's a list of some of them (it's difficult to promise that this list will be kept * up-to-date): * * decimalValue: a decimal floating-point value being written to the register * decimalPower: a power-of-two used to help convert decimalValue to binary * decimalBit: a bit index used to help convert decimalValue to binary * decimalSave: saves the initial decimal value, for visual comparison purposes */ var MAX_FRACTIONAL_DIGITS = 12; function Register(parmsReg, updateBit) { Component.call(this, "Reg", parmsReg); if (parmsReg === undefined) { parmsReg = {nBits:40, signed:true, bit0Exp:0, labels:true}; } this.aBits = new Array(parmsReg.nBits); this.signed = parmsReg.signed; this.bit0Exp = parmsReg.bit0Exp; this.labels = parmsReg.labels; /* * BUGBUG: Compute a reasonable value for this based on how many significant decimal digits * (ie, to the right of the decimal point) correspond to the smallest given negative power-of-two. */ this.fixedDigits = (this.bit0Exp < 0? MAX_FRACTIONAL_DIGITS : 0); this.upperBound = Math.pow(2, this.bit0Exp + this.aBits.length - (this.signed? 1 : 0)); this.lowerBound = (this.signed? -this.upperBound : 0); this.updateBit = (updateBit === undefined? function(iBit, f) {} : updateBit); } Component.subclass(Component, Register, { /* * getBits() is used for "direct" access to the bits; use readBit() and writeBit() to * access and change individual bits when speed isn't important. Note that changing bits * directly, as well as calling the write or copy functions, bypasses display updates, so * use updateBit() or updateAll() to update the display of one bit or the entire register * as needed. Alternatively, use modifyBit() or modifyAll() to both change and display a * single bit or the entire register. */ count: function() { return this.aBits.length; }, getBits: function() { return this.aBits; }, readBit: function(iBit) { return this.aBits[iBit]; }, writeBit: function(iBit, b) { this.aBits[iBit] = b; }, writeAll: function(b) { for (var iBit=0; iBit < this.aBits.length; iBit++) this.aBits[iBit] = b; }, writeUndefined: function(b) { for (var iBit=0; iBit < this.aBits.length; iBit++) if (this.aBits[iBit] === undefined) this.aBits[iBit] = b; }, notBit: function(iBit) { this.aBits[iBit] = !this.aBits[iBit]; }, notAll: function() { for (var iBit=0; iBit < this.aBits.length; iBit++) this.aBits[iBit] = !this.aBits[iBit]; }, copyAll: function(reg) { for (var iBit=0; iBit < this.aBits.length; iBit++) this.aBits[iBit] = reg.aBits[iBit]; }, updateAll: function() { for (var iBit=0; iBit < this.aBits.length; iBit++) this.updateBit(iBit, this.aBits[iBit]); this.refreshLiveValue(); }, modifyBit: function(iBit, b) { this.writeBit(iBit, b); this.updateBit(iBit, b); this.refreshLiveValue(); }, modifyAll: function(b) { this.writeAll(b); this.updateAll(); }, readValue: function() { this.stopSteps(); this.printDecimal(); this.writeUndefined(false); this.updateAll(); this.fPostOp = 0; this.decimalSave = undefined; this.decimalValue = 0; this.decimalBit = this.aBits.length - 1; if (this.signed && this.aBits[this.decimalBit]) { this.fPostOp = -1; } this.decimalExp = this.bit0Exp + this.decimalBit; this.decimalPower = Math.pow(2, this.decimalExp); this.firstStep(this.stepCompareBitToPower); return this.decimalValue; // NOTE: this return value is valid ONLY if single-stepping has been disabled }, writeValue: function(v, fnNotify) { this.stopSteps(); this.modifyAll(undefined); /* * There are two obvious ways to handle negative values: one is to negate at the beginning, * producing a positive value, and convert as we would any other positive value; when done, * flip all the bits and add a bit at index 0 (ie, a traditional two's-complement conversion). * * However, this variation is better: make the value positive, subtract a bit at index 0, * convert as before, and then flip all the bits. It doesn't matter what order we perform the * two's-complement conversion steps, and performing a "pre-subtraction" against the input value * is cheaper for us than performing a "post-addition" on the output value (because we can * use internal math operations on the input value, whereas the output value is stored only as * an array of bits). * * One downside: when stepping through the conversion process, it may seem odd to see the * initial value modified ever so slightly (eg, -0.5 converted to 0.499999999998181). We could * add an additional explicit step to clear up any potential confusion. */ this.fPostOp = 0; this.decimalSave = v; if (v < 0) { v = -v; v -= Math.pow(2, this.bit0Exp); // BUGBUG: Assert that v is still positive (for tiny negative values of v, this will be a concern) this.fPostOp = 1; } this.decimalValue = v; this.decimalBit = this.aBits.length - 1; if (this.signed) { this.modifyBit(this.decimalBit, 0); this.decimalBit--; } this.decimalExp = this.bit0Exp + this.decimalBit; this.decimalPower = Math.pow(2, this.decimalExp); this.firstStep(this.stepCompareDecimalToPower, fnNotify); }, getValue: function() { var decimalValue = 0; var decimalBit = this.aBits.length - 1; var fPostNegate = this.signed && this.aBits[decimalBit]; var decimalExp = this.bit0Exp + decimalBit; var decimalPower = Math.pow(2, decimalExp); do { if (this.aBits[decimalBit]) decimalValue += decimalPower; if (decimalBit == 0) break; decimalBit--; decimalPower /= 2; } while (true); if (fPostNegate) { decimalValue = -(Math.pow(2, this.bit0Exp + this.aBits.length) - decimalValue); } return decimalValue; }, setLiveUpdate: function(updateLiveValue) { this.updateLiveValue = updateLiveValue; this.refreshLiveValue(); }, refreshLiveValue: function() { if (this.updateLiveValue) { this.updateLiveValue(this.getValue()); } }, printDecimal: function(v) { if (this.updateDecimal !== undefined) { /* * We allow v to be undefined, as way as signalling that we are beginning a fresh * conversion; we will be calling printDecimal() again at the completion of the conversion, * and v will be defined at that point. */ this.updateDecimal(v); if (v !== undefined && this.log) console.log(this.toString() + ": updated decimal value to " + v.toFixed(this.fixedDigits)); } }, setDecimalUpdate: function(updateDecimal) { this.updateDecimal = updateDecimal; }, /* * The following "step" functions implement writeValue(). * * Once writeValue() has initialized all the internal decimal variables, it calls * the first step indirectly, via firstStep(), which in turns invokes other * steps, based on whether the current decimal power is greater than or equal to * the current decimal value. */ stepCompareDecimalToPower: function(n) { this.printStep(n, "Comparing decimal value (" + this.decimalValue + ") to 2" + this.decimalExp + " (" + this.decimalPower.toFixed(20) + ")"); if (this.decimalValue >= this.decimalPower) { this.addStep(this.stepSetDecimalBit); this.addStep(this.stepReduceDecimalValue); } else { this.addStep(this.stepClearDecimalBit); } if (!this.addStep(this.stepReduceDecimalPower)) return false; return true; }, stepSetDecimalBit: function(n) { this.printStep(n, "Setting bit " + this.decimalBit); this.writeBit(this.decimalBit, true); if (n !== undefined) { this.updateBit(this.decimalBit, true); this.refreshLiveValue(); } return true; }, stepClearDecimalBit: function(n) { this.printStep(n, "Clearing bit " + this.decimalBit); this.writeBit(this.decimalBit, false); if (n !== undefined) { this.updateBit(this.decimalBit, false); this.refreshLiveValue(); } return true; }, stepReduceDecimalValue: function(n) { this.printStep(n, "Reducing decimal value by 2" + this.decimalExp + " (" + this.decimalPower.toFixed(20) + ")"); // this.decimalPower.toFixed(this.fixedDigits)); this.decimalValue -= this.decimalPower; if (n !== undefined) this.printDecimal(this.decimalValue); return true; }, stepReduceDecimalPower: function(n) { if (this.decimalBit == 0) { var sStep = "Processed bit 0"; if (this.fPostOp > 0) { sStep = "Inverting all bits"; this.notAll(); this.updateAll(); } else if (this.fPostOp < 0) { sStep = "Negating result"; this.decimalValue = -(Math.pow(2, this.bit0Exp + this.aBits.length) - this.decimalValue); } this.printStep(n, sStep + ", conversion" + (this.decimalSave !== undefined? " of " + this.decimalSave.toFixed(this.fixedDigits) : "") + " complete"); if (n === undefined) { /* * Since the conversion was performed without single-stepping, we need to update the register via * updateAll() if this was a writeValue() operation (ie, decimalSave is defined); similarly, we need * to update the decimal value via printDecimal() if this was a readValue() operation. */ if (this.decimalSave !== undefined) this.updateAll(); } /* * printDecimal() may have never been called during the conversion, since we only call it when the value has been * reduced. So we always print at the end. */ this.printDecimal(this.decimalValue); return false; } this.printStep(n, "Reducing power-of-two"); this.decimalBit--; this.decimalExp--; this.decimalPower /= 2; return true; }, /* * The following "step" functions implement readValue(). * * Because we take the same "top-down" approach that writeValue() took (ie, from highest power/left-most bit down to * lowest power/right-most bit), we can use the same stepReduceDecimalPower step function that writeValue() used; both * procedures stop after they've processed bit 0. */ stepCompareBitToPower: function(n) { this.printStep(n, "Testing bit " + this.decimalBit); if (this.aBits[this.decimalBit]) this.addStep(this.stepIncreaseDecimalValue); if (!this.addStep(this.stepReduceDecimalPower)) return false; return true; }, stepIncreaseDecimalValue: function(n) { this.printStep(n, "Increasing decimal value by 2" + this.decimalExp + " (" + this.decimalPower.toFixed(20) + ")"); // this.decimalPower.toFixed(this.fixedDigits)); this.decimalValue += this.decimalPower; if (n !== undefined) this.printDecimal(this.decimalValue); return true; } }); /* * initRegisters() * * Initializes all the necessary HTML to construct every register as spec'ed. * * This function operates on every element (e) of class "register" and inserts * the appropriate HTML child elements of class "bitCell". * * Note that each element (e) of class "register" is expected to have a "data-value" * attribute containing the same JSON-encoded parameters that the Register constructor * expects. */ function initRegisters() { var aeRegs = Component.getElementsByClass(window.document, "register"); for (var iReg=0; iReg < aeRegs.length; iReg++) { var eReg = aeRegs[iReg]; var parmsReg = Component.getComponentParms(eReg); var sHTML = ""; var nExp = parmsReg.bit0Exp + parmsReg.nBits - 1; for (var iCell=0; iCell < parmsReg.nBits; iCell++,nExp--) { var sLabel = ""; sBitClass = "bitCell"; if (iCell == 0) { sBitClass += " bitCellLeft"; if (parmsReg.signed) sLabel = "+/-"; } var sCellID = "r" + iReg + "c" + iCell; var sCell = "
\n"; if (!parmsReg.labels) { sHTML += sCell; } else { if (!sLabel) sLabel = "2" + nExp + ""; sHTML += "
\n" + sCell + "
" + sLabel + "
\n
\n"; } } eReg.innerHTML = sHTML; if (parmsReg.id) { eReg.setAttribute("id", "reg" + parmsReg.id); } // // Now that all the document elements have been defined, create an array that refers // to all "bitCell" elements in bit index order (ie, reverse of display order). // var aeBits = []; var aeCells = Component.getElementsByClass(eReg, "bitCell"); for (var i=aeCells.length-1; i >= 0; i--) { aeBits.push(aeCells[i]); } // // Now we can create the Register object, record it, and wire it up to the associated document elements. // var reg = new Register(parmsReg, function(aeBitsParm) { return function(iBit, f) { var s = (f===undefined? " " : (f? "1":"0")); aeBitsParm[iBit].innerHTML = s; }; }(aeBits) ); for (var i=0; i < aeBits.length; i++) { aeBits[i].onclick = function(regParm, iParm) { // // If we defined the onclick handler below as "function(e)" instead of simply "function()", then we could // also receive an event object (e); however, IE reportedly requires that we examine a global (window.event) // instead. If that's true, and if we ever care to get more details about the click event, then we might // have to worry about that (eg, define a local var: "var event = window.event || e"). // return function() { toggleRegisterBit(regParm, iParm); }; }(reg, i); } initRegisterControls(reg, eReg); // // For testing purposes, we could tweak a few of the bits, just to see if all the "wiring" works. // // reg.modifyBit(7, true); // reg.modifyBit(9, false); // } } /* * initRegisterControls(reg, eReg) * * For each Register object created by initRegisters(), this function looks for any controls that have been defined * along with the register element in the current document, and "wires" them as needed. * * The following controls are supported: * * One optional 'input' control of class "value" * One optional 'output' control of class "value" * One optional 'output' control of class "status" * One optional 'button' control of class "random" * One optional 'button' control of class "write" * One optional 'button' control of class "read" * One optional 'button' control of class "clear" * One optional 'button' control of class "step" (if this exists, it will override any "step" setting above) */ function initRegisterControls(reg, eReg) { var aeControls = Component.getElementsByClass(eReg.parentNode, "controls"); for (var iControl = 0; iControl < aeControls.length; iControl++) { var aeChildren = aeControls[iControl].childNodes; var eDecimal = null, eRandom = null, eWrite = null, eRead = null; 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 == "INPUT" && sClass == "value") { eDecimal = e; reg.setDecimalUpdate( function(e) { return function(v) { e.value = (v !== undefined? v.toFixed(reg.fixedDigits) : ""); }; }(e) ); continue; } if (e.nodeName == "DIV" && sClass == "value") { reg.setLiveUpdate( function(e) { return function(v) { e.innerHTML = (v !== undefined? "Live value: " + v.toFixed(reg.fixedDigits) + "" : ""); }; }(e) ); continue; } if (e.nodeName == "DIV" && sClass == "status") { reg.setStatusUpdate( function(e) { return function(s) { e.innerHTML = (s? "" + s + "" : ""); }; }(e) ); continue; } if (e.nodeName == "BUTTON" && sClass == "random") { eRandom = e; continue; } if (e.nodeName == "BUTTON" && sClass == "write") { eWrite = e; continue; } if (e.nodeName == "BUTTON" && sClass == "read") { eRead = e; continue; } if (e.nodeName == "BUTTON" && sClass == "clear") { e.onclick = function() { clearRegisterValue(reg); }; continue; } if (e.nodeName == "BUTTON" && sClass == "step") { reg.setStep(e); continue; } } if (eRandom && eDecimal) { eRandom.onclick = function(eDecimal) { return function() { randomizeRegisterValue(reg, eDecimal); }; }(eDecimal); } if (eWrite && eDecimal) { eWrite.onclick = function(eDecimal) { return function() { writeRegisterValue(reg, eDecimal); }; }(eDecimal); } if (eRead) { eRead.onclick = function(eDecimal) { return function() { readRegisterValue(reg, eDecimal); }; }(eDecimal); } } } /* * Function called by the anonymous click handler for all of the individual register "bitCell" elements. */ function toggleRegisterBit(reg, iBit) { reg.modifyBit(iBit, reg.readBit(iBit) === false); } /* * Function called by the anonymous click handler for the "Random" button. * * It calls reg.stopSteps() to stop any internal operation currently in progress (eg, * a previous writeValue() or readValue() operation), but it doesn't attempt to write the new value into * the register; that's the "Write" button's job, once the user chooses to accept the new value. */ function randomizeRegisterValue(reg, eDecimal) { /* * NOTE: eDecimal is an element, so set the "value" property rather than the "innerHTML" property. */ reg.stopSteps(); eDecimal.value = Math.random().toFixed(reg.fixedDigits); } /* * Function called by the anonymous click handler for the "Write" button. * * This takes whatever input value the user has entered (either manually or by clicking the "Random" button) * and calls reg.writeValue() to begin the process of converting the decimal floating-point value to binary and * writing the result to the register. * * If we ever add a user control (eg, a drop-down list) to select a step delay, then we can call setStep() * to change the delay from whatever default delay was selected. Note that a setStep() delay of 0 disables all * single-step output, allowing the operation to run at full speed. */ function writeRegisterValue(reg, eDecimal) { reg.stopSteps(); var v = parseFloat(eDecimal.value); if (isNaN(v)) v = 0; if (v >= reg.lowerBound && v < reg.upperBound) { reg.writeValue(v); } else { reg.printStatus("Error: decimal value " + v + " out of bounds (" + reg.lowerBound + " <= v < " + reg.upperBound + ")"); } } /* * Function called by the anonymous click handler for the "Read" button. * * This starts the reg.readValue() procedure, which does return a value, but it's meaningful only if * single-stepping has been disabled. It doesn't matter, because either way, reg.readValue() insures that * that the decimal field is zeroed at the beginning of the procedure and updated at the end (not to mention * intermediate intervals if single-stepping is enabled), so there's no need to update the field here. * * If we ever add a user control (eg, a drop-down list) to select a step delay, then we can call setStep() * to change the delay from whatever default delay was selected. Note that a setStep() delay of 0 disables all * single-step output, allowing the operation to run at full speed. */ function readRegisterValue(reg, eDecimal) { reg.stopSteps(); reg.readValue(); } /* * Function called by the anonymous click handler for the "Clear" button. */ function clearRegisterValue(reg) { reg.stopSteps(); reg.modifyAll(false); reg.printDecimal(); reg.printStatus(); } /* * Initialize all the registers on the page. */ web.onInit(initRegisters);