/** * RAM Component. * * Two settings are available: * - addressWidth: 1 to 20, default=10. Controls the width of the address input. * - bitWidth: 1 to 32, default=8. Controls the width of data pins. * * Amount of memory in the element is 2^addressWidth x bitWidth bits. * Minimum RAM size is: 2^1 x 1 = 2 bits. * Maximum RAM size is: 2^20 x 32 = 1M x 32 bits => 32 Mbits => 4MB. * Maximum 8-bits size: 2^20 x 8 = 1M x 8 bits => 1MB. * Default RAM size is: 2^10 x 8 = 1024 bytes => 1KB. * * RAMs are volatile therefore this component does not persist the memory contents. * * Changes to addressWidth and bitWidth also cause data to be lost. * Think of these operations as being equivalent to taking a piece of RAM out of a * circuit board and replacing it with another RAM of different size. * * The contents of the RAM can be reset to zero by setting the RESET pin 1 or * or by selecting the component and pressing the "Reset" button in the properties window. * * The contents of the RAM can be dumped to the console by transitioning CORE DUMP pin to 1 * or by selecting the component and pressing the "Core Dump" button in the properties window. * Address spaces that have not been written will show up as `undefined` in the core dump. * * NOTE: The maximum address width of 20 is arbitrary. * Larger values are possible, but in practice circuits won't need this much * memory and keeping the value small helps avoid allocating too much memory on the browser. * Internally we use a sparse array, so only the addresses that are written are actually * allocated. Nevertheless, it is better to prevent large allocations from happening * by keeping the max addressWidth small. If needed, we can increase the max. */ function RAM(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 8, addressWidth = 10) { CircuitElement.call(this, x, y, scope, dir, Math.min(Math.max(1, bitWidth), 32)); this.setDimensions(60, 40); this.directionFixed = true; this.labelDirection = "UP"; this.addressWidth = Math.min(Math.max(1, addressWidth), this.maxAddressWidth); this.address = new Node(-this.leftDimensionX, -20, 0, this, this.addressWidth, "ADDRESS"); this.dataIn = new Node(-this.leftDimensionX, 0, 0, this, this.bitWidth, "DATA IN"); this.write = new Node(-this.leftDimensionX, 20, 0, this, 1, "WRITE"); this.reset = new Node(0, this.downDimensionY, 0, this, 1, "RESET"); this.coreDump = new Node(-20, this.downDimensionY, 0, this, 1, "CORE DUMP"); this.dataOut = new Node(this.rightDimensionX, 0, 1, this, this.bitWidth, "DATA OUT"); this.prevCoreDumpValue = undefined; this.clearData() } RAM.prototype = Object.create(CircuitElement.prototype); RAM.prototype.tooltipText = "Random Access Memory"; RAM.prototype.shortName = "RAM"; RAM.prototype.maxAddressWidth = 20; RAM.prototype.constructor = RAM; RAM.prototype.helplink = "https://docs.circuitverse.org/#/memoryElements?id=ram"; RAM.prototype.mutableProperties = { "addressWidth": { name: "Address Width", type: "number", max: "20", min: "1", func: "changeAddressWidth", }, "dump": { name: "Core Dump", type: "button", func: "dump", }, "reset": { name: "Reset", type: "button", func: "clearData", }, } RAM.prototype.customSave = function () { return { // NOTE: data is not persisted since RAMs are volatile. constructorParamaters: [this.direction, this.bitWidth, this.addressWidth], nodes: { address: findNode(this.address), dataIn: findNode(this.dataIn), write: findNode(this.write), reset: findNode(this.reset), coreDump: findNode(this.coreDump), dataOut: findNode(this.dataOut), }, } } RAM.prototype.newBitWidth = function (value) { value = parseInt(value); if (!isNaN(value) && this.bitWidth != value && value >= 1 && value <= 32) { this.bitWidth = value; this.dataIn.bitWidth = value; this.dataOut.bitWidth = value; this.clearData(); } } RAM.prototype.changeAddressWidth = function (value) { value = parseInt(value); if (!isNaN(value) && this.addressWidth != value && value >= 1 && value <= this.maxAddressWidth) { this.addressWidth = value; this.address.bitWidth = value; this.clearData(); } } RAM.prototype.clearData = function () { this.data = new Array(Math.pow(2, this.addressWidth)); this.tooltipText = this.memSizeString() + " " + this.shortName; } RAM.prototype.isResolvable = function () { return this.address.value !== undefined || this.reset.value !== undefined || this.coreDump.value !== undefined; } RAM.prototype.resolve = function () { if (this.write.value == 1) { this.data[this.address.value] = this.dataIn.value; } if (this.reset.value == 1) { this.clearData(); } if (this.coreDump.value && this.prevCoreDumpValue != this.coreDump.value) { this.dump(); } this.prevCoreDumpValue = this.coreDump.value; this.dataOut.value = this.data[this.address.value] || 0; simulationArea.simulationQueue.add(this.dataOut); } RAM.prototype.customDraw = function () { var ctx = simulationArea.context; var xx = this.x; var yy = this.y; ctx.beginPath(); ctx.strokeStyle = "gray"; ctx.fillStyle = this.write.value ? "red" : "lightgreen"; ctx.lineWidth = correctWidth(1); drawCircle2(ctx, 50, -30, 3, xx, yy, this.direction); ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.textAlign = "center"; ctx.fillStyle = "black"; fillText4(ctx, this.memSizeString(), 0, -10, xx, yy, this.direction, 12); fillText4(ctx, this.shortName, 0, 10, xx, yy, this.direction, 12); fillText2(ctx, "A", this.address.x + 12, this.address.y, xx, yy, this.direction); fillText2(ctx, "DI", this.dataIn.x + 12, this.dataIn.y, xx, yy, this.direction); fillText2(ctx, "W", this.write.x + 12, this.write.y, xx, yy, this.direction); fillText2(ctx, "DO", this.dataOut.x - 15, this.dataOut.y, xx, yy, this.direction); ctx.fill(); } RAM.prototype.memSizeString = function () { var mag = ['', 'K', 'M']; var unit = this.bitWidth == 8 ? "B" : this.bitWidth == 1 ? "b" : " x " + this.bitWidth + 'b'; var v = Math.pow(2, this.addressWidth); var m = 0; while (v >= 1024 && m < mag.length - 1) { v /= 1024; m++; } return v + mag[m] + unit; } RAM.prototype.dump = function () { var logLabel = console.group && this.label; if (logLabel) { console.group(this.label); } console.log(this.data) if (logLabel) { console.groupEnd(); } } //This is a RAM without a clock - not normal //reset is not supported RAM.moduleVerilog = function () { return ` module RAM(dout, addr, din, we, dmp, rst); parameter WIDTH = 8; parameter ADDR = 10; output [WIDTH-1:0] dout; input [ADDR-1:0] addr; input [WIDTH-1:0] din; input we; input dmp; input rst; reg [WIDTH-1:0] mem [2**ADDR-1:0]; assign dout = mem[addr]; always @ (*) begin if (!we) mem[addr] = din; end endmodule `; }