202 lines
7.1 KiB
JavaScript
202 lines
7.1 KiB
JavaScript
/**
|
|
* 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
|
|
`;
|
|
}
|