new inline verilog assembler

This commit is contained in:
Steven Hugg 2018-02-17 23:12:09 -06:00
parent 1790ca1747
commit f6d320a05b
9 changed files with 434 additions and 242 deletions

View File

@ -41,7 +41,7 @@ module frame_buffer_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
parameter IN_VPU = 8'b01000011;
reg [7:0] ram[0:63];
reg [7:0] rom[0:255];
reg [7:0] rom[0:127];
output wire [7:0] address_bus;
output reg [7:0] to_cpu;
@ -95,7 +95,7 @@ module frame_buffer_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
vsync, hsync, vpaddle, hpaddle, display_on};
IN_VPU: to_cpu = {vpu_ram[vpu_write], vpu_ram[vpu_write+1]};
// ROM
8'b1???????: to_cpu = rom[address_bus[6:0] + 128];
8'b1???????: to_cpu = rom[address_bus[7:0] + 128];
default: ;
endcase
@ -131,7 +131,8 @@ module frame_buffer_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
__asm
.arch femto8
.org 128
.len 128
.define VPU_LO 8
.define VPU_HI 9
.define VPU_WRITE 10
@ -152,25 +153,24 @@ Start:
sta VPU_LO
sta VPU_HI
sta 0
sta 1
DisplayLoop:
zero B
movrb A
mov A,[b]
sta VPU_WRITE
sta VPU_MOVE
sta VPU_WRITE
sta VPU_MOVE
sta VPU_WRITE
sta VPU_MOVE
lia F_VSYNC
lib IN_FLAGS
andrb NOP
lda #F_VSYNC
ldb #IN_FLAGS
and none,[B]
bz DisplayLoop
WaitVsync:
andrb NOP
and none,[B]
bnz WaitVsync
zero B
movrb A
mov A,[b]
inc A
sta 0
jmp DisplayLoop

View File

@ -52,7 +52,7 @@ module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
parameter IN_FLAGS = 8'b01000010;
reg [7:0] ram[0:63];
reg [7:0] rom[0:255];
reg [7:0] rom[0:127];
output wire [7:0] address_bus;
output reg [7:0] to_cpu;
@ -80,7 +80,7 @@ module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
IN_FLAGS: to_cpu = {2'b0, frame_collision,
vsync, hsync, vpaddle, hpaddle, display_on};
// ROM
8'b1???????: to_cpu = rom[address_bus[6:0] + 128];
8'b1???????: to_cpu = rom[address_bus[6:0]];
default: ;
endcase
@ -154,6 +154,7 @@ module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
__asm
.arch femto8
.org 128
.len 128
.define PADDLE_X 0
.define PADDLE_Y 1
@ -178,11 +179,11 @@ module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
.define F_COLLIDE 32
Start:
lia 128
lda #128
sta PLAYER_X
sta ENEMY_X
sta ENEMY_Y
lia 180
sta ENEMY_Y
lda #180
sta PLAYER_Y
zero A
sta SPEED
@ -190,67 +191,67 @@ Start:
sta ENEMY_DIR
; test hpaddle flag
DisplayLoop:
lia F_HPADDLE
lib IN_FLAGS
andrb NOP
lda #F_HPADDLE
ldb #IN_FLAGS
and none,[B]
bz DisplayLoop
; [vpos] -> paddle_x
lib IN_VPOS
movrb A
ldb #IN_VPOS
mov A,[B]
sta PLAYER_X
; wait for vsync=1 then vsync=0
lia F_VSYNC
lib IN_FLAGS
lda #F_VSYNC
ldb #IN_FLAGS
WaitForVsyncOn:
andrb NOP
and none,[B]
bz WaitForVsyncOn
WaitForVsyncOff:
andrb NOP
and none,[B]
bnz WaitForVsyncOff
; check collision
lia F_COLLIDE
lib IN_FLAGS
andrb NOP
lda #F_COLLIDE
ldb #IN_FLAGS
and none,[B]
bz NoCollision
; load slow speed
lia 16
lda #16
sta SPEED
NoCollision:
; update speed
lib SPEED
movrb A
ldb #SPEED
mov A,[B]
inc A
; don't store if == 0
bz MaxSpeed
sta SPEED
MaxSpeed:
movrb A
mov A,[B]
lsr A
lsr A
lsr A
lsr A
; add to lo byte of track pos
lib TRACKPOS_LO
addrb B
ldb #TRACKPOS_LO
add B,[B]
swapab
sta TRACKPOS_LO
swapab
; update enemy vert pos
lib ENEMY_Y
addrb A
ldb #ENEMY_Y
add A,[B]
sta ENEMY_Y
; update enemy horiz pos
lib ENEMY_X
movrb A
lib ENEMY_DIR
addrb A
ldb #ENEMY_X
mov A,[B]
ldb #ENEMY_DIR
add A,[B]
sta ENEMY_X
subi A 64
andi A 127
sub A,#64
and A,#127
bnz SkipXReverse
; load ENEMY_DIR and negate
zero A
subrb A
sub A,[B]
sta ENEMY_DIR
; back to display loop
SkipXReverse:

View File

@ -31,9 +31,6 @@ module starfield_top(clk, reset, hsync, vsync, rgb);
.lfsr(lfsr));
wire star_on = &lfsr[15:9];
wire r = display_on && star_on && lfsr[0];
wire g = display_on && star_on && lfsr[1];
wire b = display_on && star_on && lfsr[2];
assign rgb = {b,g,r};
assign rgb = display_on && star_on ? lfsr[2:0] : 0;
endmodule

View File

@ -1,189 +0,0 @@
EXAMPLE_SPEC = {
vars:{
reg:{bits:2, toks:['a', 'b', 'ip', 'none']},
unop:{bits:3, toks:['mova','movb','inc','dec','asl','lsr','rol','ror']},
binop:{bits:3, toks:['or','and','xor','zero','add','sub','adc','sbb']},
imm4:{bits:4},
imm8:{bits:8},
rel:{bits:8, iprel:true, ipofs:1},
},
rules:[
{fmt:'~binop ~reg,b', bits:['00',1,'1',0]},
{fmt:'~binop ~reg,#~imm8', bits:['01',1,'1',0,2]},
{fmt:'~binop ~reg,[b]', bits:['11',1,'1',0]},
{fmt:'~unop ~reg', bits:['00',1,'0',0]},
{fmt:'lda #~imm8', bits:['01','00','0001',0]},
{fmt:'ldb #~imm8', bits:['01','01','0001',0]},
{fmt:'jmp ~imm8', bits:['01','10','0001',0]},
{fmt:'sta ~imm4', bits:['1001',0]},
{fmt:'bcc ~rel', bits:['1010','0001',0]},
{fmt:'bcs ~rel', bits:['1010','0011',0]},
{fmt:'bz ~rel', bits:['1010','1101',0]},
{fmt:'bnz ~rel', bits:['1010','0101',0]},
{fmt:'clc', bits:['10001000']},
{fmt:'swapab', bits:['10000001']},
{fmt:'reset', bits:['10001111']},
]
}
function rule2regex(rule, vars) {
var s = rule.fmt;
var varlist = [];
rule.prefix = s.split(/\s+/)[0];
s = s.replace(/\s+/g, '\\s');
s = s.replace(/\[/g, '\\[');
s = s.replace(/\]/g, '\\]');
s = s.replace(/\./g, '\\.');
s = s.replace(/~\w+/g, function(varname) {
varname = varname.substr(1);
var v = vars[varname];
varlist.push(varname);
if (!v)
throw Error("Could not find rule for ~" + varname);
else if (v.toks)
return '(\\w+)';
else
return '([0-9]+|[$][0-9a-f]+|\\w+)';
});
rule.re = new RegExp('^'+s+'$', 'i');
rule.varlist = varlist;
// TODO: check rule constraints
return rule;
}
var Assembler = function(spec) {
var self = this;
var ip = 0;
var linenum = 0;
var symbols = {};
var errors = [];
var outwords = [];
var fixups = [];
var width = 8;
for (var i=0; i<spec.rules.length; i++)
rule2regex(spec.rules[i], spec.vars);
function warning(msg) {
errors.push({msg:msg, line:linenum});
}
function addBytes(result) {
var op = result.opcode;
var nb = result.nbits/width;
for (var i=0; i<nb; i++)
outwords[ip++] = (op >> (nb-1-i)*width) & ((1<<width)-1);
}
self.buildInstruction = function(rule, m) {
var opcode = 0;
var oplen = 0;
for (var i=0; i<rule.bits.length; i++) {
var b = rule.bits[i];
var n,x;
if (b.length) {
n = b.length;
x = parseInt(b,2);
} else {
var id = m[b+1];
var v = spec.vars[rule.varlist[b]];
n = v.bits;
if (v.toks) {
x = v.toks.indexOf(id);
if (x < 0)
return null;
} else {
if (id.startsWith("$"))
x = parseInt(id.substr(1), 16);
else
x = parseInt(id);
// is it a label? add fixup
if (isNaN(x)) {
fixups.push({sym:id, ofs:ip, bitlen:n, bitofs:oplen, line:linenum, iprel:!!v.iprel, ipofs:(v.ipofs+0)});
x = 0;
}
}
}
var mask = (1<<n)-1;
if ((x&mask) != x)
warning("Value " + x + " could not fit in " + n + " bits");
opcode = (opcode << n) | x;
oplen += n;
}
if (oplen == 0)
warning("Opcode had zero length");
else if ((oplen % width) != 0)
warning("Opcode was not word-aligned (" + oplen + " bits)");
return {opcode:opcode, nbits:oplen};
}
self.assemble = function(line) {
linenum++;
// remove comments
line = line.replace(/[;].*/g, '');
line = line.trim().toLowerCase();
// is it a directive?
if (line[0] == '.') {
var tokens = line.split(/\s+/);
if (tokens[0] == '.define')
symbols[tokens[1]] = {value:tokens[2]};
else
warning("Unrecognized directive: " + line);
return;
}
// find labels
line = line.replace(/(\w+):/, function(_label, label) {
symbols[label] = {value:ip};
return '';
});
line = line.trim();
// look at each rule in order
for (var i=0; i<spec.rules.length; i++) {
var rule = spec.rules[i];
var m = rule.re.exec(line);
if (m) {
var result = self.buildInstruction(rule, m);
if (result) {
addBytes(result);
return result;
}
}
}
warning("Could not decode instruction: " + line);
}
self.finish = function() {
for (var i=0; i<fixups.length; i++) {
var fix = fixups[i];
var sym = symbols[fix.sym];
if (sym) {
var ofs = fix.ofs + (fix.bitofs>>3);
var shift = fix.bitofs&7;
var mask = ((1<<fix.bitlen)-1);
var value = sym.value;
if (fix.iprel) value -= fix.ofs + fix.ipofs;
value &= mask;
// TODO: check range
// TODO: span multiple words?
outwords[ofs] ^= value;
} else {
warning("Symbol '" + fix.sym + "' not found");
}
}
fixups = [];
}
self.state = function() {
return {ip:ip, line:linenum, output:outwords, errors:errors, fixups:fixups};
}
}
var asm = new Assembler(EXAMPLE_SPEC);
console.log(EXAMPLE_SPEC);
console.log(asm.assemble(".define FOO 0xa")); // TODO
console.log(asm.assemble(" sta FOO"));
console.log(asm.assemble(" sta 10"));
console.log(asm.assemble(" add a,#25 ; comment "));
console.log(asm.assemble("Label: asl a "));
console.log(asm.assemble(" sub b,[b] "));
console.log(asm.assemble(" bz Label "));
asm.finish();
console.log(asm.state());

View File

@ -433,6 +433,12 @@ var VerilogPlatform = function(mainElement, options) {
mouse_pressed = false;
if (e.target.setCapture) e.target.releaseCapture();
});
$(video.canvas).keydown(function(e) {
switch (e.keyCode) {
case 37: scope_time_x--; dirty=true; break;
case 39: scope_time_x++; dirty=true; break;
}
});
audio = new SampleAudio(AUDIO_FREQ);
idata = video.getFrameData();
// TODO: 15.7 kHz?
@ -499,8 +505,7 @@ var VerilogPlatform = function(mainElement, options) {
trace_index = scope_x_offset = 0;
trace_buffer.fill(0);
dirty = true;
console.log(gen.rotate);
video.setRotate(gen.rotate ? -90 : 0);
if (video) video.setRotate(gen.rotate ? -90 : 0);
}
this.tick = function() {
gen.tick2();

View File

@ -180,7 +180,7 @@ function scrollProfileView(_ed) {
}
function newEditor(mode) {
var isAsm = mode=='6502' || mode =='z80';
var isAsm = mode=='6502' || mode =='z80' || mode=='verilog'; // TODO
editor = CodeMirror(document.getElementById('editor'), {
theme: 'mbo',
lineNumbers: true,

271
src/worker/assembler.js Normal file
View File

@ -0,0 +1,271 @@
EXAMPLE_SPEC = {
name:'femto8',
vars:{
reg:{bits:2, toks:['a', 'b', 'ip', 'none']},
unop:{bits:3, toks:['mova','movb','inc','dec','asl','lsr','rol','ror']},
binop:{bits:3, toks:['or','and','xor','zero','add','sub','adc','sbb']},
imm4:{bits:4},
imm8:{bits:8},
rel:{bits:8, iprel:true, ipofs:1},
},
rules:[
{fmt:'~binop ~reg,b', bits:['00',1,'1',0]},
{fmt:'~binop ~reg,#~imm8', bits:['01',1,'1',0,2]},
{fmt:'~binop ~reg,[b]', bits:['11',1,'1',0]},
{fmt:'~unop ~reg', bits:['00',1,'0',0]},
{fmt:'mov ~reg,[b]', bits:['11',0,'0001']},
{fmt:'zero ~reg', bits:['00',0,'1011']},
{fmt:'lda #~imm8', bits:['01','00','0001',0]},
{fmt:'ldb #~imm8', bits:['01','01','0001',0]},
{fmt:'jmp ~imm8', bits:['01','10','0001',0]},
{fmt:'sta ~imm4', bits:['1001',0]},
{fmt:'bcc ~imm8', bits:['1010','0001',0]},
{fmt:'bcs ~imm8', bits:['1010','0011',0]},
{fmt:'bz ~imm8', bits:['1010','1100',0]},
{fmt:'bnz ~imm8', bits:['1010','0100',0]},
{fmt:'clc', bits:['10001000']},
{fmt:'swapab', bits:['10000001']},
{fmt:'reset', bits:['10001111']},
]
}
var Assembler = function(spec) {
var self = this;
var ip = 0;
var origin = 0;
var linenum = 0;
var symbols = {};
var errors = [];
var outwords = [];
var asmlines = [];
var fixups = [];
var width = 8;
var codelen = 0;
var aborted = false;
function rule2regex(rule, vars) {
var s = rule.fmt;
var varlist = [];
rule.prefix = s.split(/\s+/)[0];
s = s.replace(/\s+/g, '\\s+');
s = s.replace(/\[/g, '\\[');
s = s.replace(/\]/g, '\\]');
s = s.replace(/\./g, '\\.');
s = s.replace(/~\w+/g, function(varname) {
varname = varname.substr(1);
var v = vars[varname];
varlist.push(varname);
if (!v)
throw Error("Could not find rule for ~" + varname);
else if (v.toks)
return '(\\w+)';
else
return '([0-9]+|[$][0-9a-f]+|\\w+)';
});
rule.re = new RegExp('^'+s+'$', 'i');
rule.varlist = varlist;
// TODO: check rule constraints
return rule;
}
function preprocessRules() {
for (var i=0; i<spec.rules.length; i++)
rule2regex(spec.rules[i], spec.vars);
}
if (spec) preprocessRules();
function warning(msg) {
errors.push({msg:msg, line:linenum});
}
function fatal(msg) {
warning(msg);
aborted = true;
}
function hex(v, nd) {
try {
if (!nd) nd = 2;
var s = v.toString(16).toUpperCase();
while (s.length < nd)
s = "0" + s;
return s;
} catch (e) {
return v+"";
}
}
function addBytes(result) {
asmlines.push({
line:linenum,
offset:ip,
nbits:result.nbits
});
var op = result.opcode;
var nb = result.nbits/width;
for (var i=0; i<nb; i++) {
outwords[ip++ - origin] = (op >> (nb-1-i)*width) & ((1<<width)-1);
}
}
function parseConst(s) {
if (!s.length)
return s;
else if (s.startsWith("$"))
return parseInt(s.substr(1), 16);
else
return parseInt(s);
}
self.buildInstruction = function(rule, m) {
var opcode = 0;
var oplen = 0;
for (var i=0; i<rule.bits.length; i++) {
var b = rule.bits[i];
var n,x;
if (b.length) {
n = b.length;
x = parseInt(b,2);
} else {
var id = m[b+1];
var v = spec.vars[rule.varlist[b]];
if (!v) {
warning("Could not find matching identifier for '" + m[0] + "'");
return;
}
n = v.bits;
if (v.toks) {
x = v.toks.indexOf(id);
if (x < 0)
return null;
} else {
x = parseConst(id);
// is it a label? add fixup
if (isNaN(x)) {
fixups.push({sym:id, ofs:ip, bitlen:n, bitofs:oplen, line:linenum, iprel:!!v.iprel, ipofs:(v.ipofs+0)});
x = 0;
}
}
}
var mask = (1<<n)-1;
if ((x&mask) != x)
warning("Value " + x + " could not fit in " + n + " bits");
opcode = (opcode << n) | x;
oplen += n;
}
if (oplen == 0)
warning("Opcode had zero length");
else if ((oplen % width) != 0)
warning("Opcode was not word-aligned (" + oplen + " bits)");
return {opcode:opcode, nbits:oplen};
}
function loadArch(arch) {
if (self.loadFile) {
var json = self.loadFile(arch + ".json");
if (json && json.vars && json.rules) {
spec = json;
preprocessRules();
} else {
fatal("Could not load arch file '" + arch + ".json'");
}
}
}
function parseDirective(tokens) {
if (tokens[0] == '.define')
symbols[tokens[1]] = {value:tokens[2]};
else if (tokens[0] == '.org')
ip = origin = parseInt(tokens[1]);
else if (tokens[0] == '.len')
codelen = parseInt(tokens[1]);
else if (tokens[0] == '.width')
width = parseInt(tokens[1]);
else if (tokens[0] == '.arch')
loadArch(tokens[1]);
else
warning("Unrecognized directive: " + tokens);
}
self.assemble = function(line) {
linenum++;
// remove comments
line = line.replace(/[;].*/g, '');
line = line.trim().toLowerCase();
// is it a directive?
if (line[0] == '.') {
var tokens = line.split(/\s+/);
parseDirective(tokens);
return;
}
// find labels
line = line.replace(/(\w+):/, function(_label, label) {
symbols[label] = {value:ip};
return ''; // replace label with blank
});
line = line.trim();
if (line == '')
return; // empty line
// look at each rule in order
if (!spec) { fatal("Need to load .spec first"); return; }
for (var i=0; i<spec.rules.length; i++) {
var rule = spec.rules[i];
var m = rule.re.exec(line);
if (m) {
var result = self.buildInstruction(rule, m);
if (result) {
addBytes(result);
return result;
}
}
}
warning("Could not decode instruction: " + line);
}
self.finish = function() {
// apply fixups
for (var i=0; i<fixups.length; i++) {
var fix = fixups[i];
var sym = symbols[fix.sym];
if (sym) {
var ofs = fix.ofs + (fix.bitofs>>3);
var shift = fix.bitofs&7;
var mask = ((1<<fix.bitlen)-1);
var value = parseConst(sym.value);
if (fix.iprel) value -= fix.ofs + fix.ipofs;
value &= mask;
// TODO: check range
// TODO: span multiple words?
outwords[ofs - origin] ^= value;
} else {
warning("Symbol '" + fix.sym + "' not found");
}
}
// update asmlines
for (var i=0; i<asmlines.length; i++) {
var al = asmlines[i];
al.insns = '';
for (var j=0; j<al.nbits/width; j++) {
var word = outwords[al.offset + j - origin];
if (j>0) al.insns += ' ';
al.insns += hex(word,width/4);
}
}
while (outwords.length < codelen) {
outwords.push(0);
}
fixups = [];
return self.state();
}
self.assembleFile = function(text) {
var lines = text.split(/\n/g);
for (var i=0; i<lines.length && !aborted; i++) {
self.assemble(lines[i]);
}
return self.finish();
}
self.state = function() {
return {ip:ip, line:linenum, origin:origin, codelen:codelen,
output:outwords, asmlines:asmlines, errors:errors, fixups:fixups};
}
}

View File

@ -1113,25 +1113,47 @@ function compileCASPR(code, platform, options) {
}
}
function compileASM(asmcode, platform, options) {
load("assembler");
var asm = new Assembler();
asm.loadFile = function(filename) {
// TODO: what if it comes from dependencies?
var path = '../../presets/' + platform + '/' + filename;
var xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open("GET", path, false); // synchronous request
xhr.send(null);
return xhr.response;
};
var result = asm.assembleFile(asmcode);
return result;
}
function compileVerilator(code, platform, options) {
loadNative("verilator_bin");
load("verilator2js");
var errors = [];
var asmlines = [];
// compile inline asm
// TODO: keep line numbers
code = code.replace(/__asm\b([\s\S]+?)\b__endasm\b/g, function(s,asmcode) {
var asmout = compileCASPR(asmcode, platform, options);
code = code.replace(/__asm\b([\s\S]+?)\b__endasm\b/g, function(s,asmcode,index) {
var firstline = code.substr(0,index).match(/\n/g).length;
var asmout = compileASM(asmcode, platform, options);
if (asmout.errors && asmout.errors.length) {
errors = asmout.errors;
for (var i=0; i<errors.length; i++)
errors[i].line += firstline;
return "";
} else if (asmout.output) {
var s = "";
var out = asmout.output;
for (var i=0; i<out.length; i++) {
if (i>0) s += ",";
s += out[i];
s += 0|out[i];
}
//console.log(s);
asmlines = asmout.asmlines;
for (var i=0; i<asmlines.length; i++)
asmlines[i].line += firstline;
return s;
}
});
@ -1162,6 +1184,7 @@ function compileVerilator(code, platform, options) {
rtn.errors = errors;
rtn.lines = [];// TODO
rtn.intermediate = {listing:h_file + cpp_file};
rtn.lines = asmlines;
return rtn;
} catch(e) {
console.log(e);

84
test/cli/testasm.js Normal file
View File

@ -0,0 +1,84 @@
var vm = require('vm');
var fs = require('fs');
var assert = require('assert');
var includeInThisContext = function(path) {
var code = fs.readFileSync(path);
vm.runInThisContext(code, path);
};
includeInThisContext("src/worker/assembler.js");
describe('Assemble', function() {
it('Should assemble', function() {
var source = `.arch femto8
.org 128
.len 128
.define VPU_LO 8
.define VPU_HI 9
.define VPU_WRITE 10
.define VPU_MOVE 11
.define IN_FLAGS $42
.define F_VSYNC 16
Start:
zero A
sta VPU_LO
sta VPU_HI
sta 0
DisplayLoop:
zero B
mov A,[b]
sta VPU_WRITE
sta VPU_MOVE
lda #F_VSYNC
ldb #IN_FLAGS
and none,[B]
bz DisplayLoop
WaitVsync:
and none,[B]
bnz WaitVsync
zero B
mov A,[b]
inc A
sta 0
jmp DisplayLoop
`;
var asm = new Assembler(EXAMPLE_SPEC);
var result = asm.assembleFile(source);
//console.log(result);
//assert.equal(result, {});
assert.equal(128, result.origin);
assert.equal(152, result.ip);
assert.deepEqual({
insns: "0B",
line: 13,
nbits: 8,
offset: 128
}, result.asmlines[0]);
assert.deepEqual(
[ { line: 13, offset: 128, nbits: 8, insns: '0B' },
{ line: 14, offset: 129, nbits: 8, insns: '98' },
{ line: 15, offset: 130, nbits: 8, insns: '99' },
{ line: 16, offset: 131, nbits: 8, insns: '90' },
{ line: 18, offset: 132, nbits: 8, insns: '1B' },
{ line: 19, offset: 133, nbits: 8, insns: 'C1' },
{ line: 20, offset: 134, nbits: 8, insns: '9A' },
{ line: 21, offset: 135, nbits: 8, insns: '9B' },
{ line: 22, offset: 136, nbits: 16, insns: '41 10' },
{ line: 23, offset: 138, nbits: 16, insns: '51 42' },
{ line: 24, offset: 140, nbits: 8, insns: 'F9' },
{ line: 25, offset: 141, nbits: 16, insns: 'AC 84' },
{ line: 27, offset: 143, nbits: 8, insns: 'F9' },
{ line: 28, offset: 144, nbits: 16, insns: 'A4 8F' },
{ line: 29, offset: 146, nbits: 8, insns: '1B' },
{ line: 30, offset: 147, nbits: 8, insns: 'C1' },
{ line: 31, offset: 148, nbits: 8, insns: '02' },
{ line: 32, offset: 149, nbits: 8, insns: '90' },
{ line: 33, offset: 150, nbits: 16, insns: '61 84' },
], result.asmlines);
assert.equal(11, result.output[0]);
assert.equal(128, result.output.length);
});
});