forked from vonsim/vonsim
-
Notifications
You must be signed in to change notification settings - Fork 0
/
memory.ts
153 lines (136 loc) · 4.89 KB
/
memory.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { syscalls, unassigned } from "@vonsim/assembler";
import { MemoryAddress, MemoryAddressLike } from "@vonsim/common/address";
import { Byte } from "@vonsim/common/byte";
import type { JsonValue } from "type-fest";
import { Component, ComponentInit } from "./component";
import { SimulatorError } from "./error";
import type { EventGenerator } from "./events";
export type MemoryOperation =
| { type: "memory:read"; address: MemoryAddressLike }
| { type: "memory:read.ok"; address: MemoryAddress; value: Byte<8> }
| { type: "memory:read.error"; error: SimulatorError<"address-out-of-range"> }
| { type: "memory:write"; address: MemoryAddressLike; value: Byte<8> }
| { type: "memory:write.ok"; address: MemoryAddress; value: Byte<8> }
| {
type: "memory:write.error";
error:
| SimulatorError<"address-has-instruction">
| SimulatorError<"address-is-reserved">
| SimulatorError<"address-out-of-range">;
};
/**
* Memory.
* @see {@link https://vonsim.github.io/en/computer/memory}
*
* It has the memory itself ({@link Memory.#buffer}) and a set of reserved memory addresses.
* These reserved addresses are the ones that are used by the instructions, and they are
* stored to prevent the user from writing to them.
*
* ---
* This class is: MUTABLE
*/
export class Memory extends Component {
static readonly SIZE = MemoryAddress.MAX_ADDRESS + 1;
#buffer: Uint8Array;
#codeMemory: Set<number>;
#reservedMemory: Set<number>;
constructor(options: ComponentInit) {
super(options);
if (options.data === "unchanged") {
this.#buffer = options.previous.memory.#buffer;
} else if (options.data === "randomize") {
this.#buffer = new Uint8Array(Memory.SIZE).map(() => Byte.random(8).unsigned);
} else {
this.#buffer = new Uint8Array(Memory.SIZE);
}
// Load syscalls addresses into memory
this.#reservedMemory = new Set();
for (const [number, address] of syscalls) {
const start = number * 4; // Interrupt vector position
this.#buffer.set(address.toUint8Array(), start);
this.#buffer.set(Byte.zero(16).toUint8Array(), start + 2);
this.#reservedMemory.add(start);
this.#reservedMemory.add(start + 1);
this.#reservedMemory.add(start + 2);
this.#reservedMemory.add(start + 3);
}
// Load data directives into memory
for (const directive of options.program.data) {
let offset = directive.start.value;
for (const value of directive.getValues()) {
if (value !== unassigned) this.#buffer.set(value.toUint8Array(), offset);
offset += directive.size / 8;
}
}
// Load instructions into memory
this.#codeMemory = new Set();
for (const instruction of options.program.instructions) {
this.#buffer.set(instruction.toBytes(), instruction.start.value);
for (let i = 0; i < instruction.length; i++) {
this.#codeMemory.add(instruction.start.value + i);
}
}
}
/**
* Reads a byte from memory at the specified address.
* @param address The address to read the byte from.
* @returns The byte at the specified address (always 8-bit) or null if there was an error.
*
* ---
* Called by the CPU.
*/
*read(address: MemoryAddressLike): EventGenerator<Byte<8> | null> {
address = Number(address);
yield { type: "memory:read", address };
if (!MemoryAddress.inRange(address)) {
yield {
type: "memory:read.error",
error: new SimulatorError("address-out-of-range", address),
};
return null;
}
const value = Byte.fromUnsigned(this.#buffer.at(address)!, 8);
yield { type: "memory:read.ok", address: MemoryAddress.from(address), value };
return value;
}
/**
* Writes a byte to memory at the specified address.
* @param address The address to write the byte to.
* @param value The byte to write.
* @returns Whether the operation succedeed or not (boolean).
*
* ---
* Called by the CPU.
*/
*write(address: MemoryAddressLike, value: Byte<8>): EventGenerator<boolean> {
address = Number(address);
yield { type: "memory:write", address, value };
if (!MemoryAddress.inRange(address)) {
yield {
type: "memory:write.error",
error: new SimulatorError("address-out-of-range", address),
};
return false;
}
if (this.#codeMemory.has(address)) {
yield {
type: "memory:write.error",
error: new SimulatorError("address-has-instruction", address),
};
return false;
}
if (this.#reservedMemory.has(address)) {
yield {
type: "memory:write.error",
error: new SimulatorError("address-is-reserved", address),
};
return false;
}
this.#buffer.set([value.unsigned], address);
yield { type: "memory:write.ok", address: MemoryAddress.from(address), value };
return true;
}
toJSON() {
return [...this.#buffer] satisfies JsonValue;
}
}