Giter Site home page Giter Site logo

francisrstokes / 16bitjs Goto Github PK

View Code? Open in Web Editor NEW
488.0 16.0 53.0 227 KB

๐Ÿ’ป A 16-bit virtual machine, including assembly language with 37 instructions, binary assembler, and a step through debugger

License: MIT License

Assembly 5.11% JavaScript 94.76% Brainfuck 0.13%
javascript 16-bit vm virtual-machine assembly-language cpu assembly virtual

16bitjs's Issues

LDV - a better way to deal with 16-bit immediate value

Since you word is 16-bit wide, you may split it into 2ร—8-bit, and keep two bits of immediate (SS bits) to select a special operation.

In MIPS, you have LUI instruction which sets a register with imm16 left shifted by 16 bits. Then you can use ADDIU or ORI to add/or-ize the register with a signed 16-bit immediate value, allowing in two instructions to set a 32-bit immediate value to a register. ARM Cortex architecture also have a similar way.

So in your case, it could be:

  • OO:00 โ€“ MVI Dr, imm8 โ€“> Dr = unsigned(imm8)
  • OO:01 โ€“ ADI Dr, imm8 โ€“> Dr = Dr + signed(imm8)
  • OO:10 โ€“ MUI Dr, imm8 โ€“> Dr = unsigned(imm8) shl 8
  • OO:11 โ€“ AUI Dr, imm8 โ€“> Dr = Dr + signed(imm8) shl 8

And

|LDV| D, V | VVVVVVVVVVDD0001 | Load a value into destination register.

becomes

|LDV| D, V, O | VVVVVVVVOODD0001 | Load a value into destination register.

with pseudos

|MVI| D, V | VVVVVVVV00DD0001 | Set a zero-extended lower byte to destination register.
|ADI| D, V | VVVVVVVV01DD0001 | Set a zero-extended lower byte to destination register.
|MUI| D, V | VVVVVVVV10DD0001 | Set a byte left shifted by 8 bits to destination register.
|AUI| D, V | VVVVVVVV11DD0001 | Add a byte left shifted by 8 bits to destination register.

Mnemonics stand for:

  • MVI: MoVe Immediate,
  • ADI: ADd Immediate,
  • MUI: Move Upper Immediate,
  • AUI: Add Upper Immediate.

The rationale is:
โ€“ the 8-bit immediate is zero/signed-extended respectively when bit 0 of SS is 0/1.
โ€“ the effective immediate value is set with the extended 8-bit immediate left shifted by 0/8 bits respectively when bit 1 of SS is 0/1,
โ€“ the effective immediate value is set/added respectively to the destination register when bit 0 of SS is 0/1.

As a result,

  • you can ditch INC/DEC Dr because you can use ADI Dr,+1/-1,
  • you can use a loop step different to -1/+1 with only one instruction,
  • you can use that pair: MVI Dr, 0xLL; AUI Dr, 0xHH <=> Dr = 0x00LL + 0xHH00 <=> Dr = 0xHHLL and define a pseudo MVA Dr, 0xHHLL,
  • you can add/substract a big constant amount: AUI Dr, 0xHH;ADI Dr,0xLL <=> Dr = Dr + 0xHH00 + 0x00LL <=> Dr = Dr + 0xHHLL if 0xLL is inferior or equals to 0x7f,
  • you can add/substract a big constant amount: AUI Dr, 0xHH+1; ADI Dr, 0xLL <=> Dr = Dr + (0xHH00+0x0100) + (0xffLL) <=> Dr = Dr + 0xHHLL if 0xLL is greater than 0x7f.
  • you can define a pseudo ADA Dr, 0xHHLL for the previous two points and which issues the right pair according to the sign bit of 0xLL.

An example (but incomplete) of ISA using almost the same rules of 16bitjs

Not a proposal, not an issue, just a way to show a different ISA which tries to use as much as possible all the field bits of an instruction. This ISA is not complete (no comparison instruction, no jump, missing stack alu, and so one).

  • ARM instruction inspiration as combining arithmetic and constant bit shifting in one instruction,
  • some look complex but they are indeed simple once you understand the underlying logic.
  • fix-point computation can be feasible in easier way and less instructions.
  • direct access to pushed arguments of a call or local variables.

Regarding stack operations, some ALU operations is missing (reserving/releasing N word in stack).

Now the example:
cpu16

LDV16 B, value probably doesn't work

Hi, I really enjoyed your article and I am now doing a code-along (I read the specs on your blog post and try to implement them in Python). I just finished coding the LDV16 expander and when I looked at your code, I saw that the (temporary) B register was hardcoded, so setting the B register with LDV16 probably won't work as intended. I solved this like this

MOV - using the upper 8 bits to store a sign-extended immediate as an addend

Proposal

Instruction MOV

MOV is renamed as MVR and MOV, DEC, and INC become pseudos using MVR

Before:

|MOV| D, S | XXXXXXXXSSDD0000 | Move value at source register to destination register|

After:

|MVR| D, S, V | VVVVVVVVSSDD0000 | Add sign-extended immediate value to value at source register and move it to destination register|
|MOV| D, S | 00000000SSDD0000 | Move value at source register to destination register|
|INC| D | 00000001DDDD0000 | Increment value at destination register|
|DEC| D | 11111111DDDD0000 | Decrement value at destination register|

Retro-compatibility is kept for assembly code, not for machine code.

Missing ESLint configuration

Hello @francisrstokes,

This is a very interesting project, thanks! greatly inspired me to read about some theory this weekend.

I want to contribute as well but the ESLint configuration file is missing in the repository. What is the policy on this? :)

Cheers!

Rename LDM to STA

Previously LoaD Memory with register because STore at Address.
Create a pseudo instruction LDM for compatibility.

Extend LDR with offset mode

To make accessing data structures easier, LDR can take two additional arguments: MODE and OFFSET_REGISTER.

If MODE is 1, then SOURCE is treated as a base which is offset by the value in OFFSET_REGISTER.

Merge SYS, HLT and RET since they no arguments

Those three instructions have only the 4-bit main opcode considered and no other relevant fields. Instead of having three different main opcode, we can have only one and used some X bits to carry the real opcode for SYS, HLT and RET and be able to add other possible instructions where no SS and DD are needed. As for SYS, I would have considered an immediate (8-bit ?) value to encode the system call code instead of relying on a register value (I believe it is A currently) . Two main opcodes would be freed for other usages that I can have in mind.

STACK - some RISC CPUs don't have a PUSH and POP instructions

There is a different way to use stack (supposedly it resides somewhere in memory).

push a
push b
push c

can be done this way:

sub sp, 3 // allocate 3 slots in word unit
str a,[sp + 0]
str b,[sp + 1]
str c,[sp + 2]
pop c
pop b
pop a

can be done this way:

str a,[sp + 0]
str b,[sp + 1]
str c,[sp + 2]
add sp, 3 // relaese 3 slots in word unit

Benefits are:

  • easy allocation of local variables in stack
  • possible dynamic allocation in stack (array of variable length)
  • direct access to local variables in many place to avoid too much pressure on the limited 4 registers
  • complex computations made easier (when involving more that 4 temporaries)

One way to call a program by RISC CPUs:

jal a, routine // call a routine by saving return in 'a' and jumping to routine
...
routine:
[sub sp, 1] only if you need to reuse 'a' in the body
[str a,[sp] only if you need to reuse 'a' in the body
... // do something
[ldr d,[sp] only if you needed to reuse 'a'
[add sp, 1] only if you needed to reuse 'a'
jr d

Sure, push, pop, call and ret allow more compact code but push and pop are not great with more complex expressions where you need to dup registers in stack: having a direct access to read/write in a stack slot makes complex expressions cleaner and shorter.

Refactor JLT to JCP (Jump ComPare)

JCP R1, R2, R3, Op

R1 is compared against R2, and the jump address is stored in R3. Operation will be in the list:

-Equal
-Not equal
-Less than
-Greater than
-Less than or equal
-Greater than or equal
-Zero (in which case R2 is ignored)
-Not zero (in which case R2 is ignored)

Create pseudo instructions for all the operations:

  • JEQ
  • JNE
  • JLT
  • JGT
  • JLE
  • JGE
  • JZE
  • JNZ

Security issue: code execution when assembling files

There is an eval in this line. That might result in code execution, so I tried to exploit it and give a proof of concept. This was quite hard to exploit since any ')' and ';' characters will cause the payload to be messed up: ';' will be seen as comments and ')' will cause the regex to stop capturing the group. Here's the proof of concept:

.data
.text
  .global main:

main:
    ldv A, ( [10, console.log`pwnt`][0] )
    hlt

and here's the stdout of node src/assembler -i asm/poc.asm -o test.out:

[ 'pwnt' ]
Read 6 instructions, including labels.
Expanded to 5, including labels.
Removed labels. Final instruction count: 4/65535
{}
Successfully assembled to binary file test.out

I would normally include a more serious (for example writing a file) proof of concept, but it was extremely hard to code in JS without parentheses.

Trouble to use "node src/assembler"

Under Windows 10 64-bit, latest version of Node.js: v8.2.1.

I'm not familiar with Node.js as it is pretty recent I'm using it at work.

So once Node.js installed, I made npm install and nothing else.

I tried this: C:\Dev\16bitjs>node src\assembler -i asm\factorial.asm -o bin\factorial.bin

And I got:

C:\Dev\16bitjs\src\assembler\preprocessor\get-data-table.js:70
      throw new Error`Unsupported data declaraction:\n${cur}\nExiting...`);
                                                                         ^

SyntaxError: Unexpected token )
    at createScript (vm.js:74:10)
    at Object.runInThisContext (vm.js:116:10)
    at Module._compile (module.js:533:28)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)
    at Module.require (module.js:513:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (C:\Dev\16bitjs\src\assembler\preprocessor\index.js:3:22)
    at Module._compile (module.js:569:30)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)
    at Module.require (module.js:513:17)

Not sure if the issue is due to my way to install Node.js.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.