/* * 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 += "