Bläddra i källkod

Add bootloader and an initial memory implementation

Matt Coles 9 år sedan
förälder
incheckning
58a65d5e32
8 ändrade filer med 335 tillägg och 5 borttagningar
  1. 1 1
      index.html
  2. 220 1
      index.js
  3. 7 3
      js/assembler.js
  4. 16 0
      js/bootload.js
  5. 16 0
      js/dom.js
  6. 0 0
      memory/index.js
  7. 64 0
      memory/memory.js
  8. 11 0
      memory/package.json

+ 1 - 1
index.html

@@ -30,7 +30,7 @@
30 30
         <div class="col-md-4">
31 31
           <p>
32 32
           <span class="buttons">
33
-            <button class="btn btn-primary">Assemble</button>
33
+            <button class="btn btn-primary" id="assemble">Assemble</button>
34 34
             <button class="btn btn-success">Run</button>
35 35
             <button class="btn btn-warning">Step</button>
36 36
           </span>

+ 220 - 1
index.js

@@ -1 +1,220 @@
1
-// Will eventually be replaced with a bundle consisting of the 'js' folder
1
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2
+// --- START DEBUG ONLY CODE ---
3
+// const fs = require('fs')
4
+// const input = fs.readFileSync('test.asm', { encoding: 'utf-8' })
5
+// --- END DEBUG ONLY CODE ---
6
+
7
+const encoders = require('./encoders.js')
8
+const lines = raw => raw.split('\n').map(s => s.trim()).filter(s => !!s)
9
+
10
+const parse = lineArray => lineArray.map(line => {
11
+  return { 
12
+    instruction: line.substr(0, line.indexOf(' ')),
13
+    arguments: line.substr(line.indexOf(' ')+1).split(',').map(s => s.trim())
14
+  }
15
+}).map(line => encoders[line.instruction].apply(null, line.arguments))
16
+
17
+// console.log(parse(lines(input)))
18
+module.exports = {
19
+  'lines': lines,
20
+  'parse': parse
21
+}
22
+
23
+},{"./encoders.js":3}],2:[function(require,module,exports){
24
+// put some stuff into memory
25
+const memory = require('../memory/memory.js')
26
+const utils = require('./utils.js')
27
+
28
+// Pads a string with 0's until it is the desired length
29
+const padAddress = (addr, len, ch) => (addr.length >= len) ? 
30
+  addr : ch.repeat(len-addr.length).concat(addr)
31
+
32
+const incrAddr = (addr, n) => padAddress((parseInt(addr, 2) + n).toString(2), 32, '0')
33
+
34
+const baseAddr = '0xbfc00000'
35
+const loadBootCode = (code) => code.map((c, i) => memory.storeWord(incrAddr(utils.hex2bin(baseAddr, 32), i*4), utils.hex2bin(c, 32)))
36
+
37
+module.exports = {
38
+  'loadBootCode': loadBootCode
39
+}
40
+
41
+},{"../memory/memory.js":6,"./utils.js":5}],3:[function(require,module,exports){
42
+const utils = require('./utils.js')
43
+
44
+const regmap = [
45
+  'zero', 'at', 'v0', 'v1', 'a0', 'a1', 'a2', 't0', 't1', 't2', 't3', 't4', 't5',
46
+  't6', 't7', 's0', 's1', 's2', 's3', 's4', 's5', 's6', 's7', 't8', 't9', 'k0',
47
+  'k1', 'gp', 'sp', 's8', 'ra' ]
48
+
49
+const getReg = (id) => {
50
+  if (id.startsWith('$')) {
51
+    id = id.slice(1)
52
+  }
53
+  if ((isNaN(id) === false && +id >= 0 && +id <= 31) || (id = regmap.indexOf(id)) > -1) {
54
+    return id
55
+  } else {
56
+    utils.error("Invalid register: " + id)
57
+  }
58
+}
59
+
60
+const encoders = {
61
+  'LUI': (rt, immediate) => 
62
+  '0x'.concat(utils.bin2hex(
63
+    '001111'.concat(
64
+      '0'.repeat(5),
65
+      utils.dec2binu(getReg(rt), 5),
66
+      utils.hex2bin(immediate, 16)), 8)),
67
+  'ORI': (rt, rs, immediate) => 
68
+  '0x'.concat(utils.bin2hex(
69
+    '001101'.concat(
70
+      utils.dec2binu(getReg(rs), 5),
71
+      utils.dec2binu(getReg(rt), 5),
72
+      utils.hex2bin(immediate, 16)), 8)),
73
+  'SW': (rt, offsetBase) => {
74
+    const [offset, base] = utils.splitOffsetBase(offsetBase)
75
+    return '0x'.concat(utils.bin2hex(
76
+      '101011'.concat(
77
+        utils.dec2binu(getReg(base), 5),
78
+        utils.dec2binu(getReg(rt), 5),
79
+        utils.int16_2bin(offset, 16)), 8))
80
+  }
81
+}
82
+
83
+module.exports = encoders
84
+
85
+},{"./utils.js":5}],4:[function(require,module,exports){
86
+// This file should hold the DOM interaction code
87
+let hex = ''
88
+const assembler = require('./assembler.js')
89
+const bootloader = require('./bootload.js')
90
+
91
+const assembleButton = document.getElementById('assemble')
92
+const assembleButtonHandler = event => {
93
+  const code = document.getElementById('code')
94
+  const rawCode = code.value
95
+  hex = assembler.parse(assembler.lines(rawCode))
96
+  bootloader.loadBootCode(hex)
97
+  console.log(hex.join('\n'))
98
+}
99
+  
100
+
101
+assembleButton.addEventListener('click', assembleButtonHandler)
102
+
103
+},{"./assembler.js":1,"./bootload.js":2}],5:[function(require,module,exports){
104
+const error = (error) => console.error(error)
105
+
106
+const dec2binu = (dec, length) => {
107
+  let unsigned = Math.abs(dec)
108
+  let bin = unsigned.toString(2)
109
+  if (bin.length >= length) {
110
+    return bin
111
+  } else {
112
+    return '0'.repeat(length - bin.length).concat(bin)
113
+  }
114
+}
115
+
116
+const int16_2bin = (int16) => {
117
+  const signed32 = (int16 >>> 0).toString(2)
118
+  if (signed32.length > 16) {
119
+    return signed32.slice(16)
120
+  } else {
121
+    return '0'.repeat(16 - signed32.length).concat(signed32)
122
+  }
123
+}
124
+
125
+const hex2bin = (hex, length) => {
126
+  if (hex.startsWith('0x')) {
127
+    return dec2binu(parseInt(hex.slice(2), 16), length)
128
+  } else {
129
+    console.error("Hex immediates must start with 0x")
130
+  }
131
+}
132
+
133
+const bin2hex = (bin, length) => {
134
+  let hex = parseInt(bin, 2).toString(16)
135
+  if (hex.length >= length) {
136
+    return hex
137
+  } else {
138
+    return '0'.repeat(length - hex.length).concat(hex)
139
+  }
140
+}
141
+
142
+const splitOffsetBase = (offsetBase) => offsetBase.trim().slice(0, -1).split('(').map(s => s.trim())
143
+
144
+module.exports = {
145
+  'error': error,
146
+  'dec2binu': dec2binu,
147
+  'hex2bin': hex2bin,
148
+  'bin2hex': bin2hex,
149
+  'int16_2bin': int16_2bin,
150
+  'splitOffsetBase': splitOffsetBase
151
+}
152
+
153
+},{}],6:[function(require,module,exports){
154
+// At the moment the memory array is a simple array, this means that _most_ of the time we
155
+// will not be filling up the array and therefore will not be using much RAM, however if
156
+// the full 4gb of memory are used, then it might prove problematic, and I will likely
157
+// move to a server side memory model that persists most data to disk
158
+const mainMemory = {}
159
+
160
+// Pads a string with 0's until it is the desired length
161
+const padAddress = (addr, len, ch) => (addr.length >= len) ? 
162
+  addr : ch.repeat(len-addr.length).concat(addr)
163
+
164
+// Converts a string address to a number, adds 1, then back to a string to be padded to 32 bits
165
+const getNextAddr = addr => padAddress((parseInt(addr, 2) + 1).toString(2), 32, '0')
166
+
167
+// Load a byte as a 32 bit 0 extended word
168
+const loadByte = addr => padAddress(mainMemory[addr], 32, '0')
169
+
170
+// Load a halfword as a 32 bit 0 extended word
171
+const loadHalfWord = addr => padAddress(
172
+  loadByte(getNextAddr(addr))
173
+  .concat(loadByte(addr)), 32, '0')
174
+
175
+// load a full 32 bit word into memory
176
+const loadWord  = addr => padAddress(
177
+  loadHalfWord(getNextAddr(getNextAddr(addr))).concat(
178
+    loadHalfWord(addr)), 32, '0')
179
+
180
+// Stores a single byte in the memory array
181
+const storeByte = (addr, data) => {
182
+  if (data.length == 8) {
183
+    mainMemory[addr] = data
184
+  } else {
185
+    console.error("data length should be 8bit for byte store")
186
+  }
187
+}
188
+
189
+// Stores two bytes in memory array, with the lower bytes in the lower addresses
190
+const storeHalfWord = (addr, data) => {
191
+  console.log(addr)
192
+  if (data.length == 16) {
193
+    if (addr.slice(-1) === '0') {
194
+      storeByte(addr, data.slice(8))
195
+      storeByte(getNextAddr(addr), data.slice(0,8))
196
+    } else {
197
+      console.error("misaligned halfword assignments are not yet supported")
198
+    }
199
+  } else {
200
+    console.error("data length should be 16bit for halfword store")
201
+  }
202
+}
203
+
204
+// Stores two bytes in memory array, with the lower bytes in the lower addresses
205
+const storeWord = (addr, data) => {
206
+  if (data.length != 32) return console.error("data length should be 32bit") // function is void anyway so returning undefined doesn't matter
207
+  storeHalfWord(addr, data.slice(16))
208
+  storeHalfWord(getNextAddr(getNextAddr(addr)), data.slice(0, 16))
209
+}
210
+
211
+module.exports = {
212
+  storeWord: storeWord,
213
+  storeByte: storeByte,
214
+  storeHalfWord, storeHalfWord,
215
+  loadByte: loadByte,
216
+  loadHalfWord: loadHalfWord,
217
+  loadWord: loadWord
218
+}
219
+
220
+},{}]},{},[4]);

+ 7 - 3
js/assembler.js

@@ -1,6 +1,6 @@
1 1
 // --- START DEBUG ONLY CODE ---
2
-const fs = require('fs')
3
-const input = fs.readFileSync('test.asm', { encoding: 'utf-8' })
2
+// const fs = require('fs')
3
+// const input = fs.readFileSync('test.asm', { encoding: 'utf-8' })
4 4
 // --- END DEBUG ONLY CODE ---
5 5
 
6 6
 const encoders = require('./encoders.js')
@@ -13,4 +13,8 @@ const parse = lineArray => lineArray.map(line => {
13 13
   }
14 14
 }).map(line => encoders[line.instruction].apply(null, line.arguments))
15 15
 
16
-console.log(parse(lines(input)))
16
+// console.log(parse(lines(input)))
17
+module.exports = {
18
+  'lines': lines,
19
+  'parse': parse
20
+}

+ 16 - 0
js/bootload.js

@@ -0,0 +1,16 @@
1
+// put some stuff into memory
2
+const memory = require('../memory/memory.js')
3
+const utils = require('./utils.js')
4
+
5
+// Pads a string with 0's until it is the desired length
6
+const padAddress = (addr, len, ch) => (addr.length >= len) ? 
7
+  addr : ch.repeat(len-addr.length).concat(addr)
8
+
9
+const incrAddr = (addr, n) => padAddress((parseInt(addr, 2) + n).toString(2), 32, '0')
10
+
11
+const baseAddr = '0xbfc00000'
12
+const loadBootCode = (code) => code.map((c, i) => memory.storeWord(incrAddr(utils.hex2bin(baseAddr, 32), i*4), utils.hex2bin(c, 32)))
13
+
14
+module.exports = {
15
+  'loadBootCode': loadBootCode
16
+}

+ 16 - 0
js/dom.js

@@ -0,0 +1,16 @@
1
+// This file should hold the DOM interaction code
2
+let hex = ''
3
+const assembler = require('./assembler.js')
4
+const bootloader = require('./bootload.js')
5
+
6
+const assembleButton = document.getElementById('assemble')
7
+const assembleButtonHandler = event => {
8
+  const code = document.getElementById('code')
9
+  const rawCode = code.value
10
+  hex = assembler.parse(assembler.lines(rawCode))
11
+  bootloader.loadBootCode(hex)
12
+  console.log(hex.join('\n'))
13
+}
14
+  
15
+
16
+assembleButton.addEventListener('click', assembleButtonHandler)

+ 0 - 0
memory/index.js


+ 64 - 0
memory/memory.js

@@ -0,0 +1,64 @@
1
+// At the moment the memory array is a simple array, this means that _most_ of the time we
2
+// will not be filling up the array and therefore will not be using much RAM, however if
3
+// the full 4gb of memory are used, then it might prove problematic, and I will likely
4
+// move to a server side memory model that persists most data to disk
5
+const mainMemory = {}
6
+
7
+// Pads a string with 0's until it is the desired length
8
+const padAddress = (addr, len, ch) => (addr.length >= len) ? 
9
+  addr : ch.repeat(len-addr.length).concat(addr)
10
+
11
+// Converts a string address to a number, adds 1, then back to a string to be padded to 32 bits
12
+const getNextAddr = addr => padAddress((parseInt(addr, 2) + 1).toString(2), 32, '0')
13
+
14
+// Load a byte as a 32 bit 0 extended word
15
+const loadByte = addr => padAddress(mainMemory[addr], 32, '0')
16
+
17
+// Load a halfword as a 32 bit 0 extended word
18
+const loadHalfWord = addr => padAddress(
19
+  loadByte(getNextAddr(addr))
20
+  .concat(loadByte(addr)), 32, '0')
21
+
22
+// load a full 32 bit word into memory
23
+const loadWord  = addr => padAddress(
24
+  loadHalfWord(getNextAddr(getNextAddr(addr))).concat(
25
+    loadHalfWord(addr)), 32, '0')
26
+
27
+// Stores a single byte in the memory array
28
+const storeByte = (addr, data) => {
29
+  if (data.length == 8) {
30
+    mainMemory[addr] = data
31
+  } else {
32
+    console.error("data length should be 8bit for byte store")
33
+  }
34
+}
35
+
36
+// Stores two bytes in memory array, with the lower bytes in the lower addresses
37
+const storeHalfWord = (addr, data) => {
38
+  if (data.length == 16) {
39
+    if (addr.slice(-1) === '0') {
40
+      storeByte(addr, data.slice(8))
41
+      storeByte(getNextAddr(addr), data.slice(0,8))
42
+    } else {
43
+      console.error("misaligned halfword assignments are not yet supported")
44
+    }
45
+  } else {
46
+    console.error("data length should be 16bit for halfword store")
47
+  }
48
+}
49
+
50
+// Stores two bytes in memory array, with the lower bytes in the lower addresses
51
+const storeWord = (addr, data) => {
52
+  if (data.length != 32) return console.error("data length should be 32bit") // function is void anyway so returning undefined doesn't matter
53
+  storeHalfWord(addr, data.slice(16))
54
+  storeHalfWord(getNextAddr(getNextAddr(addr)), data.slice(0, 16))
55
+}
56
+
57
+module.exports = {
58
+  storeWord: storeWord,
59
+  storeByte: storeByte,
60
+  storeHalfWord, storeHalfWord,
61
+  loadByte: loadByte,
62
+  loadHalfWord: loadHalfWord,
63
+  loadWord: loadWord
64
+}

+ 11 - 0
memory/package.json

@@ -0,0 +1,11 @@
1
+{
2
+  "name": "memory_server_model",
3
+  "version": "0.0.1",
4
+  "description": "",
5
+  "main": "index.js",
6
+  "scripts": {
7
+    "test": "echo \"Error: no test specified\" && exit 1"
8
+  },
9
+  "author": "",
10
+  "license": "MIT"
11
+}