mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-09-29 06:55:37 +00:00
new inline verilog assembler
This commit is contained in:
parent
1790ca1747
commit
f6d320a05b
@ -41,7 +41,7 @@ module frame_buffer_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
|
|||||||
parameter IN_VPU = 8'b01000011;
|
parameter IN_VPU = 8'b01000011;
|
||||||
|
|
||||||
reg [7:0] ram[0:63];
|
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 wire [7:0] address_bus;
|
||||||
output reg [7:0] to_cpu;
|
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};
|
vsync, hsync, vpaddle, hpaddle, display_on};
|
||||||
IN_VPU: to_cpu = {vpu_ram[vpu_write], vpu_ram[vpu_write+1]};
|
IN_VPU: to_cpu = {vpu_ram[vpu_write], vpu_ram[vpu_write+1]};
|
||||||
// ROM
|
// ROM
|
||||||
8'b1???????: to_cpu = rom[address_bus[6:0] + 128];
|
8'b1???????: to_cpu = rom[address_bus[7:0] + 128];
|
||||||
default: ;
|
default: ;
|
||||||
endcase
|
endcase
|
||||||
|
|
||||||
@ -131,6 +131,7 @@ module frame_buffer_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
|
|||||||
__asm
|
__asm
|
||||||
.arch femto8
|
.arch femto8
|
||||||
.org 128
|
.org 128
|
||||||
|
.len 128
|
||||||
|
|
||||||
.define VPU_LO 8
|
.define VPU_LO 8
|
||||||
.define VPU_HI 9
|
.define VPU_HI 9
|
||||||
@ -152,25 +153,24 @@ Start:
|
|||||||
sta VPU_LO
|
sta VPU_LO
|
||||||
sta VPU_HI
|
sta VPU_HI
|
||||||
sta 0
|
sta 0
|
||||||
sta 1
|
|
||||||
DisplayLoop:
|
DisplayLoop:
|
||||||
zero B
|
zero B
|
||||||
movrb A
|
mov A,[b]
|
||||||
sta VPU_WRITE
|
sta VPU_WRITE
|
||||||
sta VPU_MOVE
|
sta VPU_MOVE
|
||||||
sta VPU_WRITE
|
sta VPU_WRITE
|
||||||
sta VPU_MOVE
|
sta VPU_MOVE
|
||||||
sta VPU_WRITE
|
sta VPU_WRITE
|
||||||
sta VPU_MOVE
|
sta VPU_MOVE
|
||||||
lia F_VSYNC
|
lda #F_VSYNC
|
||||||
lib IN_FLAGS
|
ldb #IN_FLAGS
|
||||||
andrb NOP
|
and none,[B]
|
||||||
bz DisplayLoop
|
bz DisplayLoop
|
||||||
WaitVsync:
|
WaitVsync:
|
||||||
andrb NOP
|
and none,[B]
|
||||||
bnz WaitVsync
|
bnz WaitVsync
|
||||||
zero B
|
zero B
|
||||||
movrb A
|
mov A,[b]
|
||||||
inc A
|
inc A
|
||||||
sta 0
|
sta 0
|
||||||
jmp DisplayLoop
|
jmp DisplayLoop
|
||||||
|
@ -52,7 +52,7 @@ module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
|
|||||||
parameter IN_FLAGS = 8'b01000010;
|
parameter IN_FLAGS = 8'b01000010;
|
||||||
|
|
||||||
reg [7:0] ram[0:63];
|
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 wire [7:0] address_bus;
|
||||||
output reg [7:0] to_cpu;
|
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,
|
IN_FLAGS: to_cpu = {2'b0, frame_collision,
|
||||||
vsync, hsync, vpaddle, hpaddle, display_on};
|
vsync, hsync, vpaddle, hpaddle, display_on};
|
||||||
// ROM
|
// ROM
|
||||||
8'b1???????: to_cpu = rom[address_bus[6:0] + 128];
|
8'b1???????: to_cpu = rom[address_bus[6:0]];
|
||||||
default: ;
|
default: ;
|
||||||
endcase
|
endcase
|
||||||
|
|
||||||
@ -154,6 +154,7 @@ module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
|
|||||||
__asm
|
__asm
|
||||||
.arch femto8
|
.arch femto8
|
||||||
.org 128
|
.org 128
|
||||||
|
.len 128
|
||||||
|
|
||||||
.define PADDLE_X 0
|
.define PADDLE_X 0
|
||||||
.define PADDLE_Y 1
|
.define PADDLE_Y 1
|
||||||
@ -178,11 +179,11 @@ module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
|
|||||||
.define F_COLLIDE 32
|
.define F_COLLIDE 32
|
||||||
|
|
||||||
Start:
|
Start:
|
||||||
lia 128
|
lda #128
|
||||||
sta PLAYER_X
|
sta PLAYER_X
|
||||||
sta ENEMY_X
|
sta ENEMY_X
|
||||||
sta ENEMY_Y
|
sta ENEMY_Y
|
||||||
lia 180
|
lda #180
|
||||||
sta PLAYER_Y
|
sta PLAYER_Y
|
||||||
zero A
|
zero A
|
||||||
sta SPEED
|
sta SPEED
|
||||||
@ -190,67 +191,67 @@ Start:
|
|||||||
sta ENEMY_DIR
|
sta ENEMY_DIR
|
||||||
; test hpaddle flag
|
; test hpaddle flag
|
||||||
DisplayLoop:
|
DisplayLoop:
|
||||||
lia F_HPADDLE
|
lda #F_HPADDLE
|
||||||
lib IN_FLAGS
|
ldb #IN_FLAGS
|
||||||
andrb NOP
|
and none,[B]
|
||||||
bz DisplayLoop
|
bz DisplayLoop
|
||||||
; [vpos] -> paddle_x
|
; [vpos] -> paddle_x
|
||||||
lib IN_VPOS
|
ldb #IN_VPOS
|
||||||
movrb A
|
mov A,[B]
|
||||||
sta PLAYER_X
|
sta PLAYER_X
|
||||||
; wait for vsync=1 then vsync=0
|
; wait for vsync=1 then vsync=0
|
||||||
lia F_VSYNC
|
lda #F_VSYNC
|
||||||
lib IN_FLAGS
|
ldb #IN_FLAGS
|
||||||
WaitForVsyncOn:
|
WaitForVsyncOn:
|
||||||
andrb NOP
|
and none,[B]
|
||||||
bz WaitForVsyncOn
|
bz WaitForVsyncOn
|
||||||
WaitForVsyncOff:
|
WaitForVsyncOff:
|
||||||
andrb NOP
|
and none,[B]
|
||||||
bnz WaitForVsyncOff
|
bnz WaitForVsyncOff
|
||||||
; check collision
|
; check collision
|
||||||
lia F_COLLIDE
|
lda #F_COLLIDE
|
||||||
lib IN_FLAGS
|
ldb #IN_FLAGS
|
||||||
andrb NOP
|
and none,[B]
|
||||||
bz NoCollision
|
bz NoCollision
|
||||||
; load slow speed
|
; load slow speed
|
||||||
lia 16
|
lda #16
|
||||||
sta SPEED
|
sta SPEED
|
||||||
NoCollision:
|
NoCollision:
|
||||||
; update speed
|
; update speed
|
||||||
lib SPEED
|
ldb #SPEED
|
||||||
movrb A
|
mov A,[B]
|
||||||
inc A
|
inc A
|
||||||
; don't store if == 0
|
; don't store if == 0
|
||||||
bz MaxSpeed
|
bz MaxSpeed
|
||||||
sta SPEED
|
sta SPEED
|
||||||
MaxSpeed:
|
MaxSpeed:
|
||||||
movrb A
|
mov A,[B]
|
||||||
lsr A
|
lsr A
|
||||||
lsr A
|
lsr A
|
||||||
lsr A
|
lsr A
|
||||||
lsr A
|
lsr A
|
||||||
; add to lo byte of track pos
|
; add to lo byte of track pos
|
||||||
lib TRACKPOS_LO
|
ldb #TRACKPOS_LO
|
||||||
addrb B
|
add B,[B]
|
||||||
swapab
|
swapab
|
||||||
sta TRACKPOS_LO
|
sta TRACKPOS_LO
|
||||||
swapab
|
swapab
|
||||||
; update enemy vert pos
|
; update enemy vert pos
|
||||||
lib ENEMY_Y
|
ldb #ENEMY_Y
|
||||||
addrb A
|
add A,[B]
|
||||||
sta ENEMY_Y
|
sta ENEMY_Y
|
||||||
; update enemy horiz pos
|
; update enemy horiz pos
|
||||||
lib ENEMY_X
|
ldb #ENEMY_X
|
||||||
movrb A
|
mov A,[B]
|
||||||
lib ENEMY_DIR
|
ldb #ENEMY_DIR
|
||||||
addrb A
|
add A,[B]
|
||||||
sta ENEMY_X
|
sta ENEMY_X
|
||||||
subi A 64
|
sub A,#64
|
||||||
andi A 127
|
and A,#127
|
||||||
bnz SkipXReverse
|
bnz SkipXReverse
|
||||||
; load ENEMY_DIR and negate
|
; load ENEMY_DIR and negate
|
||||||
zero A
|
zero A
|
||||||
subrb A
|
sub A,[B]
|
||||||
sta ENEMY_DIR
|
sta ENEMY_DIR
|
||||||
; back to display loop
|
; back to display loop
|
||||||
SkipXReverse:
|
SkipXReverse:
|
||||||
|
@ -31,9 +31,6 @@ module starfield_top(clk, reset, hsync, vsync, rgb);
|
|||||||
.lfsr(lfsr));
|
.lfsr(lfsr));
|
||||||
|
|
||||||
wire star_on = &lfsr[15:9];
|
wire star_on = &lfsr[15:9];
|
||||||
wire r = display_on && star_on && lfsr[0];
|
assign rgb = display_on && star_on ? lfsr[2:0] : 0;
|
||||||
wire g = display_on && star_on && lfsr[1];
|
|
||||||
wire b = display_on && star_on && lfsr[2];
|
|
||||||
assign rgb = {b,g,r};
|
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
189
src/assembler.js
189
src/assembler.js
@ -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());
|
|
@ -433,6 +433,12 @@ var VerilogPlatform = function(mainElement, options) {
|
|||||||
mouse_pressed = false;
|
mouse_pressed = false;
|
||||||
if (e.target.setCapture) e.target.releaseCapture();
|
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);
|
audio = new SampleAudio(AUDIO_FREQ);
|
||||||
idata = video.getFrameData();
|
idata = video.getFrameData();
|
||||||
// TODO: 15.7 kHz?
|
// TODO: 15.7 kHz?
|
||||||
@ -499,8 +505,7 @@ var VerilogPlatform = function(mainElement, options) {
|
|||||||
trace_index = scope_x_offset = 0;
|
trace_index = scope_x_offset = 0;
|
||||||
trace_buffer.fill(0);
|
trace_buffer.fill(0);
|
||||||
dirty = true;
|
dirty = true;
|
||||||
console.log(gen.rotate);
|
if (video) video.setRotate(gen.rotate ? -90 : 0);
|
||||||
video.setRotate(gen.rotate ? -90 : 0);
|
|
||||||
}
|
}
|
||||||
this.tick = function() {
|
this.tick = function() {
|
||||||
gen.tick2();
|
gen.tick2();
|
||||||
|
@ -180,7 +180,7 @@ function scrollProfileView(_ed) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function newEditor(mode) {
|
function newEditor(mode) {
|
||||||
var isAsm = mode=='6502' || mode =='z80';
|
var isAsm = mode=='6502' || mode =='z80' || mode=='verilog'; // TODO
|
||||||
editor = CodeMirror(document.getElementById('editor'), {
|
editor = CodeMirror(document.getElementById('editor'), {
|
||||||
theme: 'mbo',
|
theme: 'mbo',
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
|
271
src/worker/assembler.js
Normal file
271
src/worker/assembler.js
Normal 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};
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
function compileVerilator(code, platform, options) {
|
||||||
loadNative("verilator_bin");
|
loadNative("verilator_bin");
|
||||||
load("verilator2js");
|
load("verilator2js");
|
||||||
var errors = [];
|
var errors = [];
|
||||||
|
var asmlines = [];
|
||||||
// compile inline asm
|
// compile inline asm
|
||||||
// TODO: keep line numbers
|
// TODO: keep line numbers
|
||||||
code = code.replace(/__asm\b([\s\S]+?)\b__endasm\b/g, function(s,asmcode) {
|
code = code.replace(/__asm\b([\s\S]+?)\b__endasm\b/g, function(s,asmcode,index) {
|
||||||
var asmout = compileCASPR(asmcode, platform, options);
|
var firstline = code.substr(0,index).match(/\n/g).length;
|
||||||
|
var asmout = compileASM(asmcode, platform, options);
|
||||||
if (asmout.errors && asmout.errors.length) {
|
if (asmout.errors && asmout.errors.length) {
|
||||||
errors = asmout.errors;
|
errors = asmout.errors;
|
||||||
|
for (var i=0; i<errors.length; i++)
|
||||||
|
errors[i].line += firstline;
|
||||||
return "";
|
return "";
|
||||||
} else if (asmout.output) {
|
} else if (asmout.output) {
|
||||||
var s = "";
|
var s = "";
|
||||||
var out = asmout.output;
|
var out = asmout.output;
|
||||||
for (var i=0; i<out.length; i++) {
|
for (var i=0; i<out.length; i++) {
|
||||||
if (i>0) s += ",";
|
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;
|
return s;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1162,6 +1184,7 @@ function compileVerilator(code, platform, options) {
|
|||||||
rtn.errors = errors;
|
rtn.errors = errors;
|
||||||
rtn.lines = [];// TODO
|
rtn.lines = [];// TODO
|
||||||
rtn.intermediate = {listing:h_file + cpp_file};
|
rtn.intermediate = {listing:h_file + cpp_file};
|
||||||
|
rtn.lines = asmlines;
|
||||||
return rtn;
|
return rtn;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
84
test/cli/testasm.js
Normal file
84
test/cli/testasm.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user