News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_zhulien

JSASM - JS virtual assembler

Started by zhulien, 13:48, 19 November 21

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

zhulien

I've been playing the idea of developing in a virtual machine in a virtual assembly language - I have even coded a pretty reasonable POC.


It works by coding your assembly language in a hybrid JS / Assembly language and you can run and debug it in your browser - no more crashing or annoying inside emulator development.  The goal is to convert the virtual assembler to native assembler - which is a lot easier than a full blown compiler and we get better resulting code.


Any thoughts?


zhulien

#1
Registers:

There are a set of virtual registers which would map to real registers as closely as possible.  Basically A8 to E8 for 8bit ones and A16 to E16 for 16 bit ones.  Using the registers closest to A are more likely to be allocated to real registers than ones below depending on target platform.



// registers
//
// js            6502                  z80
// ==         ====               ===
// A8, A8'         A                  A
// B8, B8'         X                  B
// C8, C8'         Y                  C
// D8, D8'         simulated               simulated
// E8, E8'         simulated               simulated


// A16, A16'      simulated               HL
// B16, B16'      simulated               DE
// C16, C16'      simulated               IX
// D16, D16'      simulated               IY
// E16, E16'      simulated               simulated


// F,  F'         S                  F
// M, M'         virtual               virtual
// PC            PC                  PC
// SP            simulated               SP


// flags
//
// js            6502                  z80
// ==         ====               ===
// C            c                  c
// I            i                  simulated
// Z            z                  z





zhulien

#2
Instruction Summary:




// instruction summary
//
// valid statuses: R = Simulator, 6 = 6502, Z = Z80
//
// status         vasm            6502                  z80
// ======         ====            ====                  ===


// (general register movement instructions)


// R            alt               simulated               exx
// R            ld                simulated               ld
//                              lda,ldx,ldy   
//                              sta,stx,sty
//                              tax, txa
//                              tay, tya
//                              tsx, txs
// R            nop               nop                     nop
// R            pop               pla,plp                  pop
// R            push            pha,php                  push
// R            swap            simulated               ex & simulated


// (flow control instructions)


// R            call            jsr                     call
// R            if(test(         simulated               simulated
// R            return            ret                     ret
// R            while(test(         simulated               simulated


// (arithmetic instructions)


// R            adc               adc                     adc
// R            add               ?                     add
// R            cp               cmp,cpx,cpy               cp
// R            dec               dex,dey                  dec
// PRIORITY2      decm            dec                     simulated: ld hl,m; dec (hl)
// R            div               simulated               simulated
// R            inc               inx,iny                  inc
// PRIORITY2      incm            inc                     simulated: ld hl,m; inc (hl)
// R            mod               simulated               simulated
// R            mult            simulated               simulated
// R            sbc               sbc                     sbc
// R            sub               ?                     sub


// (input / output instructions)


//   PRIORITY2      input            simulated               in
//   R            output            simulated               out


// (bit instructions)


// PRIORITY1      and               and                     and
// PRIORITY2      bit               bit                     bit
// PRIORITY1      or               ora                     or
// PRIORITY3      rla               rol                     rla
// PRIORITY3      rra               ror                     rra
// R            set(C            clc                     simulated: and a
// R            set(I            sei                     di, ei
// R            set(Z            simulated               simulated
// PRIORITY3      sla               asl                     sla
// PRIORITY3      sra               lsr                     sra
// PRIORITY1      xor               eor                     xor


// (block instructions)


// PRIORITY4      cpd               simulated               cpd
// PRIORITY4      cpdr            simulated               cpdr
// PRIORITY4      cpi               simulated               cpi
// PRIORITY4      cpir            simulated               cpir


// PRIORITY4      ldd               simulated               ldd
// PRIORITY4      lddr            simulated               lddr
// PRIORITY4      ldi               simulated               ldi
// PRIORITY4      ldir            simulated               ldir


// replaced instructions: by while(test(,  if(test(  and return


//                jp               jmp                     jp
//                jpnc            bcc                     jp nc
//                jpc               bcs                     jp c
//                jrnc            bcc                     jr nc
//                jrc               bcs                     jr c
//                jpnz            bne                     jp nz
//                jpz               beq                     jp z
//                jrnz            bne                     jr nz
//                jrz               beq                     jr z
//                reti            rti                     reti

zhulien

#3

Example:

Working JavaScript, within the simulator at the moment I have setup output port 8 to be output a char to the console.  It is quite obvious on a z80, what the below would assemble to.




function main()
{
   ld(A8, 5);
   set(Z, 0);


   while(test(NZ))
   {
      call(outA8);
      dec(A8);
   }
}


function outA8()
{
   output(1, A8);
}


call(main);

zhulien

#4

Challenges:

There have been only a couple of challenges so far but I think I have reasonably been able to get through them. 


Addressing Modes:


Coming up with a method of implementing addressing modes in JS which translates optimally to both a z80 and 6502, but I am pretty happy with the result so far.  That is as you would usually expect, but we have 3 addressing modifiers: M for memory, H for high byte and L for low byte e.g. 


Summary of ld commands:




// A:r8 -v ld(<R8>, <value>); ld a, 100
// B:r8 -r8 ld(<R8>, <R8>); ld a, b
// C:r8 -mv ld(<R8>, M(<value>)); ld a, (100)
// D:r8 -mr16 ld(<R8>, M(<R16>)); ld a, (hl)
// E:r8 -l ld(<R8>, L(<R16>)); ld a, l
// F:r8 -h ld(<R8>, H(<R16>)); ld a, h


// G:r16-v ld(<R16>, <value>); ld hl, 100
// H:r16-f ld(<R16>, <function>); ld hl, function1
// I:r16-r16 ld(<R16>, <R16>); ld hl, de
// J:r16-mv ld(<R16>, M(<value>)); ld hl, (100)
// K:r16-mr16 ld(<R16>, M(<R16>)); ld hl, (de)

// L:mv -r8 ld(M(<value>), <R8>); ld (100), a
// M:mv -r16 ld(M(<value>), <R16>); ld (100), hl
// N:mv -l ld(M(<value>), L(<R16>)); ld (100), l
// O:mv -h ld(M(<value>), H(<R16>)); ld (100), h


// P:mr16-r8 ld(M(<R16>), <R8>); ld (hl), a
// Q:mr16-r16 ld(M(<R16>), <R16>); ld (de), hl
// R:mr16-l ld(M(<R16>), L(<R16>)); ld (de), l
// S:mr16-h ld(M(<R16>), H(<R16>)); ld (de), h

// T:l -v ld(L(<R16>), <value>); ld l, 100
// U:l -r8 ld(L(<R16>), <R8>); ld l, a
// V:l -mv ld(L(<R16>), M(<value>)); ld l, (100)
// W:l -mr16 ld(L(<R16>), M(<R16>)); ld l, (de)


// X:h -v ld(H(<R16>), <value>); ld h, 100
// Y:h -r8 ld(H(<R16>), <R8>); ld h, a
// Z:h -mv ld(H(<R16>), M(<value>)); ld h, (100)
// 0:h -mr16 ld(H(<R16>), M(<R16>)); ld h, (de)



etc.


Stack Operations:


The only thing I have found a solution which I would rather a 'better' solution is the fact that a z80 only has 16bit pushes/pops and a 6502 only has only 8bit pushes/pops - meaning a bit of trickery to make them behave the same - even if simulating on real hardware.  Because a Z80 pushes A and F together, restoring eg: F would also restore A even if not wanted.



We currently allow:


push(A8); and push(A16); and their pop alternatives pop(A8); and pop(A16);




zhulien

#5
Simulated Memory:

To simulate memory we have this concept of the M register modifier.  On some platforms, the JS runner and a 6502 it somewhat operates like a memory pointer, or if you like... convert the content of the provided literal or register into a memory pointer.


We have some memory that we setup in arrays with the simulator currently to just cause some type of memory map to exist, it is pretty flexible.




// initialise memory
arrMemory.push({
id:'main',
enableport:1000,
disableport:1001,
startaddress:0,
endaddress:65535,
type:'memory',
subtype:'ram',
content:''
});

arrMemory.push({
id:'lowerrom',
enableport:2000,
disableport:2001,
startaddress:0,
endaddress:16384,
type:'memory',
subtype:'rom',
content:''
});

arrMemory.push({
id:'upperrom',
enableport:2002,
disableport:2003,
startaddress:49152,
endaddress:65535,
type:'memory',
subtype:'rom',
content:''
});

arrMemory.push({
id:'bank5',
enableport:3000,
disableport:3001,
startaddress:16384,
endaddress:32768,
type:'memory',
subtype:'ram',
content:''
});

arrMemory.push({
id:'bank6',
enableport:3002,
disableport:3003,
startaddress:16384,
endaddress:32768,
type:'memory',
subtype:'ram',
content:''
});

arrMemory.push({
id:'bank7',
enableport:3004,
disableport:3005,
startaddress:16384,
endaddress:32768,
type:'memory',
subtype:'ram',
content:''
});

arrMemory.push({
id:'bank8',
enableport:3006,
disableport:3007,
startaddress:16384,
endaddress:32768,
type:'memory',
subtype:'ram',
content:''
});



zhulien

Simulated input / output:


I will be using my CPC output functions to provide a simulated output and input, it could be easily modified to simulate other platform output.


Also, conditional running is also implemented - so one set of code can be conditional for simulator / z80 and 6502.


zhulien

#7
Real-World uses:


I like to prototype in JS, it will be cool if i can prototype in JS and progressively rewrite the functions in assembler and still test them in the same environment.  It speeds up development a lot.  Ideally then hit the assemble button and get one or more working platform code-bases out.


Whether we end up with a well coded playable game - if used for a game - time will tell... watch this space.


Currently coded in normal JS, soon to be JSASM:


https://8bitology.net/poc/nzstory/

zhulien

#8
simulated memory is now working fine for 8 bit reads and writes with correct handling of main ram, mapped/unmapped rom and mapped/unmapped ram.

zhulien

function main()
{
ld(B8, 5);        // loads register B8 with 5
ld(M(1024), B8);  // store register B8 into memory location 1024
ld(A8, M(1024));  // load register A8 with what was at location 1024

set(Z, 0);        // set the Z flag to not Z (Z must be 1 to be Z)

while(test(NZ))   // loop while NZ
{
call(outA8);  // call subroutine outA8
dec(A8);      // decrement A8
}
}

function outA8()
{
output(1, A8);       // output regsiter A8 to port 1
}

call(main);                  // call subroutine main

if this same code can run identically within a browser, on a CPC and on a C64 - would anyone other than me want to use it?

zhulien

I have now implemented most instructions with the exception of an IN instruction, BIT operations and memory block copies.

BSC

Quote from: zhulien on 13:48, 19 November 21It works by coding your assembly language in a hybrid JS / Assembly language and you can run and debug it in your browser - no more crashing or annoying inside emulator development.  The goal is to convert the virtual assembler to native assembler - which is a lot easier than a full blown compiler and we get better resulting code.
That is a pretty cool project! I could absolutely see me using this for prototyping some ideas and shortening my current development cycle. When will it be publicly available for some beta testing?  :D

Also had a look at https://8bitology.net/poc/nzstory/ but how am I supposed to interact with it?

** My SID player/tracker AYAY Kaeppttn! on github **  Some CPC music and experiments ** Other music ** More music on scenestream (former nectarine) ** Some shaders ** Some Soundtrakker tunes ** Some tunes in Javascript

My hardware: ** Schneider CPC 464 with colour screen, 64k extension, 3" and 5,25 drives and more ** Amstrad CPC 6128 with M4 board, GreaseWeazle.

zhulien

The NZ story thing is just a set of js game libraries i have coded with the intent to one day conver to z80, but I do see a lot of things not great decisions on an 8bit computer.

The jsasm cross assembler I can make available very soon as an alpha type release if you want to give feedback.

What it allows currently is to code logic in mnemonics coded as js functions and run them in the browser.  

Besides the bit operators for shifting and block copies I believe something useful could be developed.  The next step is of course translate those jsfunction-based mnemonics into native mnemonics which is a lot easier than when it was js to z80 (other thread).  I do still want to finish the js compiler but instead of js to z80 it would be js to jsasm so it supports multiple targets.  

I will post when the jsasm is deployed, yes you can self host it too if you prefer.

Powered by SMFPacks Menu Editor Mod