13
13
< meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
14
14
< meta name ="title " content ="EXACT ">
15
15
< meta name ="author " content ="Fereydoun Memarzanjany ">
16
- < meta name ="description " content ="Bare-metal Intel iAPX 86/10 Emulator Written In Pure WebAssembly. ">
16
+ < meta name ="description " content ="A Bare-Metal Intel 8086 Emulator Written In Raw WebAssembly. ">
17
17
< meta name ="keywords " content ="intel, 8086, iapx, emulator, webassembly, wasm, wat, wast, assembly, browser, cpu, cpu-emulator, 16bit, emulation, graphical, x86, 8086-emulator, 8086-architecture ">
18
18
< style >
19
19
/*
38
38
}
39
39
40
40
41
+ /* I Avoid using external dependencies but this is required considering how web browsers *
42
+ * either don't have the same built-in fonts or override and replace those with custom ones */
43
+ @import url (//fonts.googleapis.com/css?family=Noto+Sans);
41
44
42
45
/*
43
46
* Custom styles for elements
50
53
}
51
54
52
55
code {
53
- font-family : lucida console !important ; /* First impression is important, some browsers try to override the default font (monocode) for <code> tags */
56
+ font-family : 'Noto Sans' , monospace !important ; /* First impression is important, some browsers try to override the default font (monocode) for <code> tags */
54
57
}
55
58
56
59
p {
@@ -139,6 +142,7 @@ <h1><code>EXACT</code></h1>
139
142
< select class ="button " id ="examples ">
140
143
< option selected disabled > Examples</ option >
141
144
< option value ="checked_add "> checked_add</ option >
145
+ < option value ="jump "> jump</ option >
142
146
< option value ="memory "> memory</ option >
143
147
< option value ="rotate "> rotate</ option >
144
148
< option value ="stack "> stack</ option >
@@ -201,7 +205,7 @@ <h1><code>EXACT</code></h1>
201
205
Source Available at < a href ="https://github.com/Thraetaona/EXACT "> GitHub</ a >
202
206
</ body >
203
207
204
-
208
+
205
209
< script type ="module " async >
206
210
"use strict" ;
207
211
( async ( ) => {
@@ -216,9 +220,18 @@ <h1><code>EXACT</code></h1>
216
220
// be used to create 'instances' of this emulator, each instance has its own isolated memory along with global variable scope.
217
221
const module = await WebAssembly . compileStreaming ( fetch ( './8086.wasm' ) ) ;
218
222
223
+ // The UI updater's handle, as ianonymous intervals can not be cleared.
219
224
let updateUI ;
220
225
221
- document . getElementById ( 'examples' ) . addEventListener ( 'change' , async function ( example ) {
226
+ document . getElementById ( 'examples' ) . addEventListener ( 'change' , e => { init ( e , 1 ) ; } , false ) ;
227
+ document . getElementById ( 'upload' ) . addEventListener ( 'change' , e => { init ( e , 0 ) ; } , false ) ;
228
+
229
+
230
+ // The front-end currently allows uploading a local (residing in the same directory as this file) or remote (from the users PC).
231
+ // It would be much better if it also supported 'live' examples, such as embedding an assembler (like NASM) inside of the webpage
232
+ // which allows users to write assembly code and see it's run-time results at the same time.
233
+ async function init ( uploaded , local ) {
234
+
222
235
// Clears the UI updater (if any)
223
236
clearInterval ( updateUI )
224
237
@@ -232,94 +245,17 @@ <h1><code>EXACT</code></h1>
232
245
const byteBuffer = new Uint8Array ( instance . exports . memory . buffer ) ,
233
246
wordBuffer = new Uint16Array ( instance . exports . memory . buffer ) ,
234
247
registers = document . getElementsByClassName ( "register" ) ;
235
-
236
- let file = await fetch ( 'examples/' + example . target . value ) ;
237
- let buffer = await file . arrayBuffer ( ) ;
238
248
239
- const opcodes = new DataView ( buffer , 0 , buffer . byteLength ) ;
240
-
241
- instance . exports . programLength . value = opcodes . byteLength ;
242
-
243
- for ( let offset = 0 ; offset < instance . exports . programLength . value ; offset ++ ) {
244
- byteBuffer [ offset + 40 ] = opcodes . getUint8 ( offset ) ;
245
- }
249
+ if ( local ) {
250
+ const file = await fetch ( 'examples/' + uploaded . target . value ) ;
251
+ const buffer = await file . arrayBuffer ( ) ;
246
252
247
- if ( instance . exports . programLength . value > 0 ) {
248
- for ( let offset = 0 ; offset < 3 ; offset ++ ) {
249
- document . getElementById ( "control" ) . children [ offset ] . disabled = false ;
250
- }
251
- }
253
+ const opcodes = new DataView ( buffer , 0 , buffer . byteLength ) ;
252
254
253
- // Simply modifying programLength's value allows us to re-use this existing
254
- // variable rather than making separate 'halt' or 'continue' flag.
255
- document . getElementById ( 'run' ) . addEventListener ( 'click' , function ( ) {
256
255
instance . exports . programLength . value = opcodes . byteLength ;
257
- instance . exports . run ( ) ;
258
- } , false ) ;
259
- document . getElementById ( 'step' ) . addEventListener ( 'click' , function ( ) {
260
- const temp = instance . exports . IP . value + 1 ;
261
- instance . exports . programLength . value = ( temp > opcodes . byteLength ) ? opcodes . byteLength : temp ;
262
- instance . exports . run ( ) ;
263
- } , false ) ;
264
- document . getElementById ( 'stop' ) . addEventListener ( 'click' , function ( ) {
265
- instance . exports . programLength . value = instance . exports . IP . value ;
266
- instance . exports . run ( ) ;
267
- } , false ) ;
268
-
269
- updateUI = window . setInterval ( async function ( ) {
270
- for ( let offset = 0 ; offset < 8 ; offset ++ ) {
271
- // 16-Bit General-purpose and Index registers
272
- registers [ offset ] . innerHTML = wordBuffer [ offset ] ;
273
- // 8-Bit register decodes
274
- registers [ offset + 8 ] . innerHTML = byteBuffer [ offset ] ;
275
- }
276
-
277
- for ( let offset = 8 ; offset < 12 ; offset ++ ) {
278
- // Segment registers
279
- registers [ offset + 8 ] . innerHTML = wordBuffer [ offset ] ;
280
- }
281
-
282
- // Flag registers do not follow a uniform order, hence the manual assignment.
283
- registers [ 20 ] . innerHTML = byteBuffer [ 24 ] ;
284
- registers [ 21 ] . innerHTML = byteBuffer [ 26 ] ;
285
- registers [ 22 ] . innerHTML = byteBuffer [ 28 ] ;
286
- registers [ 23 ] . innerHTML = byteBuffer [ 30 ] ;
287
- registers [ 24 ] . innerHTML = byteBuffer [ 31 ] ;
288
- registers [ 25 ] . innerHTML = byteBuffer [ 32 ] ;
289
- registers [ 26 ] . innerHTML = byteBuffer [ 33 ] ;
290
- registers [ 27 ] . innerHTML = byteBuffer [ 34 ] ;
291
- registers [ 28 ] . innerHTML = byteBuffer [ 35 ] ;
292
-
293
- // Program counter
294
- registers [ 29 ] . innerHTML = instance . exports . IP . value ;
295
- } , 15 ) ;
296
- } , false ) ;
297
-
298
- document . getElementById ( 'upload' ) . addEventListener ( 'change' , async function ( uploaded ) {
299
-
300
- // Clears the UI updater (if any)
301
- clearInterval ( updateUI )
302
-
303
- // Each time a new file is uploaded, we will create an 'instance' of this emulator for it,
304
- // the instance will be dedicated to running the said program.
305
- const instance = await WebAssembly . instantiate ( module , imports ) ;
306
-
307
- // Since the UI runs at 60 frames per second we should not be
308
- // allocating memory on the heap every time updateUI is run,
309
- // so we allocate it once in here, and simply pass it to the UI updater.
310
- const byteBuffer = new Uint8Array ( instance . exports . memory . buffer ) ,
311
- wordBuffer = new Uint16Array ( instance . exports . memory . buffer ) ,
312
- registers = document . getElementsByClassName ( "register" ) ;
313
-
314
- let file = uploaded . target . files [ 0 ] ;
315
- let reader = new FileReader ( ) ;
316
-
317
- reader . onload = function ( ) {
318
- let opcodes = new Uint8Array ( reader . result ) ;
319
- instance . exports . programLength . value = opcodes . length ;
320
256
321
257
for ( let offset = 0 ; offset < instance . exports . programLength . value ; offset ++ ) {
322
- byteBuffer [ offset + 40 ] = opcodes [ offset ] ;
258
+ byteBuffer [ offset + 40 ] = opcodes . getUint8 ( offset ) ;
323
259
}
324
260
325
261
if ( instance . exports . programLength . value > 0 ) {
@@ -331,21 +267,56 @@ <h1><code>EXACT</code></h1>
331
267
// Simply modifying programLength's value allows us to re-use this existing
332
268
// variable rather than making separate 'halt' or 'continue' flag.
333
269
document . getElementById ( 'run' ) . addEventListener ( 'click' , function ( ) {
334
- instance . exports . programLength . value = opcodes . length ;
270
+ instance . exports . programLength . value = opcodes . byteLength ;
335
271
instance . exports . run ( ) ;
336
272
} , false ) ;
337
273
document . getElementById ( 'step' ) . addEventListener ( 'click' , function ( ) {
338
274
const temp = instance . exports . IP . value + 1 ;
339
- instance . exports . programLength . value = ( temp > opcodes . length ) ? opcodes . length : temp ;
275
+ instance . exports . programLength . value = ( temp > opcodes . byteLength ) ? opcodes . byteLength : temp ;
340
276
instance . exports . run ( ) ;
341
277
} , false ) ;
342
278
document . getElementById ( 'stop' ) . addEventListener ( 'click' , function ( ) {
343
279
instance . exports . programLength . value = instance . exports . IP . value ;
344
280
instance . exports . run ( ) ;
345
281
} , false ) ;
346
282
}
347
-
348
- reader . readAsArrayBuffer ( file ) ;
283
+ else {
284
+ const file = uploaded . target . files [ 0 ] ;
285
+ const reader = new FileReader ( ) ;
286
+
287
+ reader . onload = function ( ) {
288
+ let opcodes = new Uint8Array ( reader . result ) ;
289
+ instance . exports . programLength . value = opcodes . length ;
290
+
291
+ for ( let offset = 0 ; offset < instance . exports . programLength . value ; offset ++ ) {
292
+ byteBuffer [ offset + 40 ] = opcodes [ offset ] ;
293
+ }
294
+
295
+ if ( instance . exports . programLength . value > 0 ) {
296
+ for ( let offset = 0 ; offset < 3 ; offset ++ ) {
297
+ document . getElementById ( "control" ) . children [ offset ] . disabled = false ;
298
+ }
299
+ }
300
+
301
+ // Simply modifying programLength's value allows us to re-use this existing
302
+ // variable rather than making separate 'halt' or 'continue' flag.
303
+ document . getElementById ( 'run' ) . addEventListener ( 'click' , function ( ) {
304
+ instance . exports . programLength . value = opcodes . length ;
305
+ instance . exports . run ( ) ;
306
+ } , false ) ;
307
+ document . getElementById ( 'step' ) . addEventListener ( 'click' , function ( ) {
308
+ const temp = instance . exports . IP . value + 1 ;
309
+ instance . exports . programLength . value = ( temp > opcodes . length ) ? opcodes . length : temp ;
310
+ instance . exports . run ( ) ;
311
+ } , false ) ;
312
+ document . getElementById ( 'stop' ) . addEventListener ( 'click' , function ( ) {
313
+ instance . exports . programLength . value = instance . exports . IP . value ;
314
+ instance . exports . run ( ) ;
315
+ } , false ) ;
316
+ }
317
+
318
+ reader . readAsArrayBuffer ( file ) ;
319
+ }
349
320
350
321
updateUI = window . setInterval ( async function ( ) {
351
322
for ( let offset = 0 ; offset < 8 ; offset ++ ) {
@@ -374,7 +345,7 @@ <h1><code>EXACT</code></h1>
374
345
// Program counter
375
346
registers [ 29 ] . innerHTML = instance . exports . IP . value ;
376
347
} , 15 ) ;
377
- } , false ) ;
348
+ }
378
349
} ) ( ) ;
379
350
</ script >
380
351
</ html >
0 commit comments