Init
This commit is contained in:
commit
6ef300f402
25 changed files with 2280 additions and 0 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*.cr]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/docs/
|
||||||
|
/lib/
|
||||||
|
/bin/
|
||||||
|
/.shards/
|
||||||
|
*.dwarf
|
||||||
|
./examples/*.bin
|
||||||
|
examples/*.bin
|
||||||
|
/.vscode/
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Dominic Grimm <dominic.grimm@gmail.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# hence
|
||||||
|
|
||||||
|
A stack based CPU with microcode and custom firmware support.
|
44
examples/add.asm
Normal file
44
examples/add.asm
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
PROGRAM_SIZE = (32 * 1024)
|
||||||
|
INSTRUCTION_SIZE = 3
|
||||||
|
|
||||||
|
main_jump = (PROGRAM_SIZE - INSTRUCTION_SIZE - INSTRUCTION_SIZE)
|
||||||
|
|
||||||
|
.org 0x0000
|
||||||
|
|
||||||
|
push main_jump
|
||||||
|
jmp
|
||||||
|
|
||||||
|
data:
|
||||||
|
answer:
|
||||||
|
ANSWER_SIZE = 52
|
||||||
|
.bw "The answer to life, the universe, and everything is ", 0, ANSWER_SIZE
|
||||||
|
|
||||||
|
main:
|
||||||
|
push answer
|
||||||
|
loop:
|
||||||
|
dup
|
||||||
|
load
|
||||||
|
emit
|
||||||
|
|
||||||
|
inc
|
||||||
|
dup
|
||||||
|
push (answer + ANSWER_SIZE)
|
||||||
|
neq
|
||||||
|
push loop
|
||||||
|
jif
|
||||||
|
drop
|
||||||
|
|
||||||
|
push 40
|
||||||
|
push 2
|
||||||
|
add
|
||||||
|
print
|
||||||
|
|
||||||
|
push "!"
|
||||||
|
emit
|
||||||
|
push "\n"
|
||||||
|
emit
|
||||||
|
stp
|
||||||
|
|
||||||
|
.org main_jump
|
||||||
|
push main
|
||||||
|
jmp
|
41
examples/hello_world.asm
Normal file
41
examples/hello_world.asm
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
; Constants
|
||||||
|
PROGRAM_SIZE = (32 * 1024)
|
||||||
|
INSTRUCTION_SIZE = 3
|
||||||
|
|
||||||
|
main_jump = (PROGRAM_SIZE - INSTRUCTION_SIZE - INSTRUCTION_SIZE)
|
||||||
|
|
||||||
|
; Base organisation / offset
|
||||||
|
.org 0x0000
|
||||||
|
|
||||||
|
; Static data
|
||||||
|
push main_jump
|
||||||
|
jmp ; Skip data section as following data is not program code
|
||||||
|
|
||||||
|
data:
|
||||||
|
; Message data
|
||||||
|
msg:
|
||||||
|
MSG_SIZE = 12 ; Define size of message
|
||||||
|
.bw "Hello World!", 0, MSG_SIZE ; Embed byte words in binary
|
||||||
|
|
||||||
|
; Main label
|
||||||
|
main:
|
||||||
|
push msg
|
||||||
|
loop:
|
||||||
|
dup
|
||||||
|
load
|
||||||
|
emit
|
||||||
|
|
||||||
|
inc
|
||||||
|
dup
|
||||||
|
push (msg + MSG_SIZE)
|
||||||
|
neq
|
||||||
|
push loop
|
||||||
|
jif
|
||||||
|
push "\n"
|
||||||
|
emit
|
||||||
|
stp
|
||||||
|
|
||||||
|
; Jump to main label
|
||||||
|
.org main_jump
|
||||||
|
push main
|
||||||
|
jmp
|
2
pdf/chapter_break.tex
Normal file
2
pdf/chapter_break.tex
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
\usepackage{sectsty}
|
||||||
|
\sectionfont{\underline\clearpage}
|
19
pdf/pdf.sh
Normal file
19
pdf/pdf.sh
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
pandoc "$1" \
|
||||||
|
-f gfm \
|
||||||
|
-t pdf \
|
||||||
|
-H pdf/chapter_break.tex \
|
||||||
|
--wrap=none \
|
||||||
|
--reference-links \
|
||||||
|
-s \
|
||||||
|
--toc \
|
||||||
|
--toc-depth 4 \
|
||||||
|
-V geometry:a4paper \
|
||||||
|
-V mainfont="Vollkorn" \
|
||||||
|
-V monofont="DejaVu Sans Mono" \
|
||||||
|
-V linkcolor:blue \
|
||||||
|
--highlight-style pdf/pygments.theme \
|
||||||
|
--pdf-engine=xelatex \
|
||||||
|
-o "$2"
|
211
pdf/pygments.theme
Normal file
211
pdf/pygments.theme
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
{
|
||||||
|
"text-color": null,
|
||||||
|
"background-color": "#f8f8f8",
|
||||||
|
"line-number-color": "#aaaaaa",
|
||||||
|
"line-number-background-color": null,
|
||||||
|
"text-styles": {
|
||||||
|
"Other": {
|
||||||
|
"text-color": "#007020",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Attribute": {
|
||||||
|
"text-color": "#7d9029",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"SpecialString": {
|
||||||
|
"text-color": "#bb6688",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Annotation": {
|
||||||
|
"text-color": "#60a0b0",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Function": {
|
||||||
|
"text-color": "#06287e",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"String": {
|
||||||
|
"text-color": "#4070a0",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"ControlFlow": {
|
||||||
|
"text-color": "#007020",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Operator": {
|
||||||
|
"text-color": "#666666",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Error": {
|
||||||
|
"text-color": "#ff0000",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"BaseN": {
|
||||||
|
"text-color": "#40a070",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Alert": {
|
||||||
|
"text-color": "#ff0000",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Variable": {
|
||||||
|
"text-color": "#19177c",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"BuiltIn": {
|
||||||
|
"text-color": null,
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Extension": {
|
||||||
|
"text-color": null,
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Preprocessor": {
|
||||||
|
"text-color": "#bc7a00",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Information": {
|
||||||
|
"text-color": "#60a0b0",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"VerbatimString": {
|
||||||
|
"text-color": "#4070a0",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Warning": {
|
||||||
|
"text-color": "#60a0b0",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Documentation": {
|
||||||
|
"text-color": "#ba2121",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Import": {
|
||||||
|
"text-color": null,
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Char": {
|
||||||
|
"text-color": "#4070a0",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"DataType": {
|
||||||
|
"text-color": "#902000",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Float": {
|
||||||
|
"text-color": "#40a070",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Comment": {
|
||||||
|
"text-color": "#60a0b0",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"CommentVar": {
|
||||||
|
"text-color": "#60a0b0",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": true,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Constant": {
|
||||||
|
"text-color": "#880000",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"SpecialChar": {
|
||||||
|
"text-color": "#4070a0",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"DecVal": {
|
||||||
|
"text-color": "#40a070",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": false,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
},
|
||||||
|
"Keyword": {
|
||||||
|
"text-color": "#007020",
|
||||||
|
"background-color": null,
|
||||||
|
"bold": true,
|
||||||
|
"italic": false,
|
||||||
|
"underline": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
shard.lock
Normal file
10
shard.lock
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
version: 2.0
|
||||||
|
shards:
|
||||||
|
commander:
|
||||||
|
git: https://github.com/mrrooijen/commander.git
|
||||||
|
version: 0.4.0
|
||||||
|
|
||||||
|
version_from_shard:
|
||||||
|
git: https://github.com/hugopl/version_from_shard.git
|
||||||
|
version: 1.2.5
|
||||||
|
|
19
shard.yml
Normal file
19
shard.yml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
name: hence
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
authors:
|
||||||
|
- Dominic Grimm <dominic.grimm@gmail.com>
|
||||||
|
|
||||||
|
targets:
|
||||||
|
hence:
|
||||||
|
main: src/cli/hence.cr
|
||||||
|
|
||||||
|
crystal: 1.4.1
|
||||||
|
|
||||||
|
license: MIT
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
commander:
|
||||||
|
github: mrrooijen/commander
|
||||||
|
version_from_shard:
|
||||||
|
github: hugopl/version_from_shard
|
200
src/cli/hence.cr
Normal file
200
src/cli/hence.cr
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
require "commander"
|
||||||
|
require "../hence"
|
||||||
|
|
||||||
|
CLI = Commander::Command.new do |cmd|
|
||||||
|
cmd.use = "hence"
|
||||||
|
cmd.long = "Hence assembler and emulator"
|
||||||
|
|
||||||
|
cmd.run do
|
||||||
|
puts cmd.help
|
||||||
|
end
|
||||||
|
|
||||||
|
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||||
|
cmd.commands.add do |cmd|
|
||||||
|
cmd.use = "version"
|
||||||
|
cmd.short = "Prins current hence version"
|
||||||
|
cmd.long = cmd.short
|
||||||
|
|
||||||
|
cmd.run do
|
||||||
|
puts Hence::VERSION
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||||
|
cmd.commands.add do |cmd|
|
||||||
|
cmd.use = "firmware"
|
||||||
|
cmd.short = "Firmware utility"
|
||||||
|
cmd.long = cmd.short
|
||||||
|
|
||||||
|
cmd.flags.add do |flag|
|
||||||
|
flag.name = "file"
|
||||||
|
flag.long = "--file"
|
||||||
|
flag.default = false
|
||||||
|
flag.description = "Load firmware from file"
|
||||||
|
flag.persistent = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||||
|
cmd.commands.add do |cmd|
|
||||||
|
cmd.use = "ls"
|
||||||
|
cmd.short = "Lists available firmwares"
|
||||||
|
cmd.long = cmd.short
|
||||||
|
|
||||||
|
cmd.run do
|
||||||
|
puts Hence::Firmware::FIRMWARES.keys.join("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||||
|
cmd.commands.add do |cmd|
|
||||||
|
cmd.use = "compile <firmware> [out]"
|
||||||
|
cmd.short = "Compiles a firmware"
|
||||||
|
cmd.long = cmd.short
|
||||||
|
|
||||||
|
cmd.flags.add do |flag|
|
||||||
|
flag.name = "dump"
|
||||||
|
flag.long = "--dump"
|
||||||
|
flag.default = false
|
||||||
|
flag.description = "Hexdumps the firmware"
|
||||||
|
end
|
||||||
|
|
||||||
|
cmd.run do |opts, args|
|
||||||
|
bin = if opts.bool["file"]
|
||||||
|
Hence::Firmware.from_yaml(File.read(args[0]))
|
||||||
|
else
|
||||||
|
Hence::Firmware::FIRMWARES[args[0]]
|
||||||
|
end.compile(Hence::Microcode::MICROCODE_NAMES)
|
||||||
|
File.write(args[1], bin) if args[1]?
|
||||||
|
|
||||||
|
puts bin.hexdump if opts.bool["dump"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||||
|
cmd.commands.add do |cmd|
|
||||||
|
cmd.use = "doc <firmware> [out]"
|
||||||
|
cmd.short = "Generates documentation for a firmware"
|
||||||
|
cmd.long = cmd.short
|
||||||
|
|
||||||
|
cmd.flags.add do |flag|
|
||||||
|
flag.name = "dump"
|
||||||
|
flag.long = "--dump"
|
||||||
|
flag.default = false
|
||||||
|
flag.description = "Hexdumps the firmware"
|
||||||
|
end
|
||||||
|
|
||||||
|
cmd.run do |opts, args|
|
||||||
|
doc = if opts.bool["file"]
|
||||||
|
Hence::Firmware.from_yaml(File.read(args[0]))
|
||||||
|
else
|
||||||
|
Hence::Firmware::FIRMWARES[args[0]]
|
||||||
|
end.to_s
|
||||||
|
File.write(args[1], doc) if args[1]?
|
||||||
|
|
||||||
|
puts doc if opts.bool["dump"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||||
|
cmd.commands.add do |cmd|
|
||||||
|
cmd.use = "parse <src>"
|
||||||
|
cmd.short = "Parses source code"
|
||||||
|
cmd.long = cmd.short
|
||||||
|
|
||||||
|
cmd.run do |_opts, args|
|
||||||
|
# ameba:disable Lint/DebugCalls
|
||||||
|
pp Hence::Parser.parse(File.read(args[0]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||||
|
cmd.commands.add do |cmd|
|
||||||
|
cmd.use = "assemble <src> [out]"
|
||||||
|
cmd.short = "Compiles assembly code into a binary"
|
||||||
|
cmd.long = cmd.short
|
||||||
|
|
||||||
|
cmd.flags.add do |flag|
|
||||||
|
flag.name = "firmware"
|
||||||
|
flag.short = "-f"
|
||||||
|
flag.long = "--firmware"
|
||||||
|
flag.default = "default"
|
||||||
|
flag.description = "Custom firmware"
|
||||||
|
end
|
||||||
|
|
||||||
|
cmd.flags.add do |flag|
|
||||||
|
flag.name = "file"
|
||||||
|
flag.long = "--file"
|
||||||
|
flag.default = false
|
||||||
|
flag.description = "Load firmware from YAML definition file"
|
||||||
|
end
|
||||||
|
|
||||||
|
cmd.flags.add do |flag|
|
||||||
|
flag.name = "dump"
|
||||||
|
flag.long = "--dump"
|
||||||
|
flag.default = false
|
||||||
|
flag.description = "Hexdumps the binary"
|
||||||
|
end
|
||||||
|
|
||||||
|
cmd.run do |opts, args|
|
||||||
|
bin = Hence::Assembler.assemble(
|
||||||
|
Hence::Parser.parse(File.read(args[0])),
|
||||||
|
if opts.bool["file"]
|
||||||
|
Hence::Firmware.from_yaml(File.read(opts.string["file"]))
|
||||||
|
else
|
||||||
|
Hence::Firmware::FIRMWARES[opts.string["firmware"]]
|
||||||
|
end.build_opcodes[:names]
|
||||||
|
)
|
||||||
|
|
||||||
|
File.write(args[1], bin) if args[1]?
|
||||||
|
puts bin.hexdump if opts.bool["dump"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||||
|
cmd.commands.add do |cmd|
|
||||||
|
cmd.use = "run <bin>"
|
||||||
|
cmd.short = "Runs a binary"
|
||||||
|
cmd.long = cmd.short
|
||||||
|
|
||||||
|
cmd.flags.add do |flag|
|
||||||
|
flag.name = "firmware"
|
||||||
|
flag.short = "-f"
|
||||||
|
flag.long = "--firmware"
|
||||||
|
flag.default = "default"
|
||||||
|
flag.description = "Custom firmware"
|
||||||
|
end
|
||||||
|
|
||||||
|
cmd.flags.add do |flag|
|
||||||
|
flag.name = "file"
|
||||||
|
flag.long = "--file"
|
||||||
|
flag.default = false
|
||||||
|
flag.description = "Load firmware from file"
|
||||||
|
end
|
||||||
|
|
||||||
|
cmd.run do |opts, args|
|
||||||
|
bin = Bytes.new({{ 32 * 1024 }})
|
||||||
|
File.open(args[0]) do |file|
|
||||||
|
file.gets_to_end.to_slice.copy_to(bin)
|
||||||
|
end
|
||||||
|
|
||||||
|
Hence::Emulator.emulate(
|
||||||
|
Hence::Emulator::Data.new(
|
||||||
|
bin,
|
||||||
|
if opts.bool["file"]
|
||||||
|
buffer = Bytes.new({{ 32 * 1024 }})
|
||||||
|
File.open(opts.string["firmware"]) do |file|
|
||||||
|
file.gets_to_end.to_slice.copy_to(buffer)
|
||||||
|
end
|
||||||
|
|
||||||
|
buffer
|
||||||
|
else
|
||||||
|
Hence::Firmware::FIRMWARES[opts.string["firmware"]].compile(Hence::Microcode::MICROCODE_NAMES)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Commander.run(CLI, ARGV)
|
1
src/hence.cr
Normal file
1
src/hence.cr
Normal file
|
@ -0,0 +1 @@
|
||||||
|
require "./hence/*"
|
83
src/hence/assembler.cr
Normal file
83
src/hence/assembler.cr
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
module Hence
|
||||||
|
module Assembler
|
||||||
|
extend self
|
||||||
|
|
||||||
|
def assemble(ast : Parser::AST, opcode_names : Hash(String, UInt8), data = Data.new) : Bytes
|
||||||
|
ast.body.each do |node|
|
||||||
|
data.constants["OFFSET"] = Parser::AST::NumberArg.new(data.offset)
|
||||||
|
|
||||||
|
case node
|
||||||
|
when Parser::AST::ConstantDeclarationNode
|
||||||
|
raise "Constants can't be redefined" if data.constants[node.name]?
|
||||||
|
|
||||||
|
data.constants[node.name] = node.value
|
||||||
|
when Parser::AST::LabelNode
|
||||||
|
raise "Labels can't be redefined" if data.constants[node.name]?
|
||||||
|
|
||||||
|
data.constants[node.name] = Parser::AST::NumberArg.new(data.offset)
|
||||||
|
when Parser::AST::MacroCallNode
|
||||||
|
MACROS[node.name].call(data, node.args)
|
||||||
|
when Parser::AST::CallNode
|
||||||
|
data.bin[data.offset] = opcode_names[node.name]
|
||||||
|
arg = node.arg.try(&.resolve(data)) || Bytes.new(2)
|
||||||
|
data.bin[data.offset + 1] = arg[0]
|
||||||
|
data.bin[data.offset + 2] = arg[1]
|
||||||
|
|
||||||
|
data.offset += 3
|
||||||
|
else
|
||||||
|
raise "Unexpected node type: #{typeof(node)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
raise "Offset out of bounds: 0x#{data.offset.to_s(16).rjust(4, '0')} > 0x#{data.bin.size.to_s(16).rjust(4, '0')}" if data.offset > data.bin.size
|
||||||
|
end
|
||||||
|
|
||||||
|
data.bin
|
||||||
|
end
|
||||||
|
|
||||||
|
class Data
|
||||||
|
property bin
|
||||||
|
property offset
|
||||||
|
property constants
|
||||||
|
|
||||||
|
def initialize(
|
||||||
|
@bin = Bytes.new({{ 32 * 1024 }}),
|
||||||
|
@offset = 0_u16,
|
||||||
|
@constants = {} of String => Parser::AST::Arg
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
MACROS = {
|
||||||
|
"org" => ->(data : Data, args : Array(Parser::AST::Arg)) do
|
||||||
|
data.offset = args[0].resolve_as_number(data)
|
||||||
|
end,
|
||||||
|
"org_add" => ->(data : Data, args : Array(Parser::AST::Arg)) do
|
||||||
|
data.offset += args[0].resolve_as_number(data)
|
||||||
|
end,
|
||||||
|
"org_sub" => ->(data : Data, args : Array(Parser::AST::Arg)) do
|
||||||
|
data.offset -= args[0].resolve_as_number(data)
|
||||||
|
end,
|
||||||
|
"bytes" => ->(data : Data, args : Array(Parser::AST::Arg)) do
|
||||||
|
args.each do |a|
|
||||||
|
a.resolve(data).each do |b|
|
||||||
|
data.bin[data.offset] = b
|
||||||
|
data.offset += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
"bw" => ->(data : Data, args : Array(Parser::AST::Arg)) do
|
||||||
|
string_arg = String.new(args[0].resolve(data)).gsub(/\\n/, '\n').to_slice
|
||||||
|
pos_start = args[1]?.try(&.resolve_as_number(data)) || 0_u16
|
||||||
|
pos_end = args[2]?.try(&.resolve_as_number(data)) || string_arg.size.to_u16
|
||||||
|
|
||||||
|
string_arg[pos_start...pos_end].each do |b|
|
||||||
|
data.bin[data.offset] = b
|
||||||
|
data.offset += 1
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
"include" => ->(_data : Data, _args : Array(Parser::AST::Arg)) do
|
||||||
|
raise "Unimplemented"
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
247
src/hence/emulator.cr
Normal file
247
src/hence/emulator.cr
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
module Hence
|
||||||
|
module Emulator
|
||||||
|
extend self
|
||||||
|
|
||||||
|
def emulate(data : Data) : Data
|
||||||
|
until data.reg_pc == 0xffff_u16
|
||||||
|
data.reg_opc = (data.get_memory(data.reg_pc) & 0xff_u8).to_u8
|
||||||
|
data.reg_pc &+= 1
|
||||||
|
data.reg_arg = data.get_memory(data.reg_pc) << 8
|
||||||
|
data.reg_pc &+= 1
|
||||||
|
data.reg_arg = data.reg_arg | (data.get_memory(data.reg_pc) & 0xff_u8)
|
||||||
|
data.reg_pc &+= 1
|
||||||
|
|
||||||
|
base_mcc = data.reg_opc &* 4
|
||||||
|
data.reg_mccs = data.firmware[base_mcc].to_u16 << 8
|
||||||
|
data.reg_mccs = data.reg_mccs | data.firmware[base_mcc &+ 1]
|
||||||
|
data.reg_mcce = data.firmware[base_mcc &+ 2].to_u16 << 8
|
||||||
|
data.reg_mcce = data.reg_mcce | data.firmware[base_mcc &+ 3]
|
||||||
|
|
||||||
|
until data.reg_mccs &+ data.reg_mccpc >= data.reg_mcce
|
||||||
|
base_mccpc = data.reg_mccs &+ data.reg_mccpc
|
||||||
|
data.reg_mcc = data.firmware[base_mccpc]
|
||||||
|
data.reg_mccarg = data.firmware[base_mccpc &+ 1].to_u16 << 8
|
||||||
|
data.reg_mccarg = data.reg_mccarg | data.firmware[base_mccpc &+ 2]
|
||||||
|
data.reg_mccpc &+= 3
|
||||||
|
|
||||||
|
data.microcodes[data.reg_mcc].run(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
data.reg_mccpc = 0_u16
|
||||||
|
data.reg_tmp = 0_u16
|
||||||
|
data.reg_a = 0_u16
|
||||||
|
data.reg_b = 0_u16
|
||||||
|
data.reg_c = 0_u16
|
||||||
|
data.reg_d = 0_u16
|
||||||
|
end
|
||||||
|
|
||||||
|
data
|
||||||
|
end
|
||||||
|
|
||||||
|
class Data
|
||||||
|
property firmware
|
||||||
|
property microcodes
|
||||||
|
property reg_pc
|
||||||
|
property reg_opc
|
||||||
|
property reg_arg
|
||||||
|
property reg_mccpc
|
||||||
|
property reg_mccs
|
||||||
|
property reg_mcce
|
||||||
|
property reg_mcc
|
||||||
|
property reg_mccarg
|
||||||
|
property reg_tmp
|
||||||
|
property reg_sp
|
||||||
|
property reg_a
|
||||||
|
property reg_b
|
||||||
|
property reg_c
|
||||||
|
property reg_d
|
||||||
|
property reg_inp
|
||||||
|
property reg_out
|
||||||
|
|
||||||
|
def initialize(
|
||||||
|
@program : Bytes,
|
||||||
|
@firmware : Bytes,
|
||||||
|
@microcodes = Microcode::MICROCODES,
|
||||||
|
@reg_pc = 0_u16,
|
||||||
|
@reg_opc = 0_u8,
|
||||||
|
@reg_arg = 0_u16,
|
||||||
|
@reg_mccpc = 0_u16,
|
||||||
|
@reg_mccs = 0_u16,
|
||||||
|
@reg_mcce = 0_u16,
|
||||||
|
@reg_mcc = 0_u8,
|
||||||
|
@reg_mccarg = 0_u16,
|
||||||
|
@reg_tmp = 0_u16,
|
||||||
|
@reg_sp = 0_u16,
|
||||||
|
@reg_a = 0_u16,
|
||||||
|
@reg_b = 0_u16,
|
||||||
|
@reg_c = 0_u16,
|
||||||
|
@reg_d = 0_u16,
|
||||||
|
@reg_inp = 0_u16,
|
||||||
|
@reg_out = 0_u16,
|
||||||
|
@stack = Slice(UInt16).new({{ 8 * 1024 }}),
|
||||||
|
@memory = Slice(UInt16).new({{ 24 * 1024 }})
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_register(index : UInt8) : UInt16
|
||||||
|
case index
|
||||||
|
when 0x0_u8
|
||||||
|
@reg_pc
|
||||||
|
when 0x1_u8
|
||||||
|
@reg_opc.to_u16
|
||||||
|
when 0x2_u8
|
||||||
|
@reg_arg
|
||||||
|
when 0x3_u8
|
||||||
|
@reg_mccpc
|
||||||
|
when 0x4_u8
|
||||||
|
@reg_mccs
|
||||||
|
when 0x5_u8
|
||||||
|
@reg_mcce
|
||||||
|
when 0x6_u8
|
||||||
|
@reg_mcc.to_u16
|
||||||
|
when 0x7_u8
|
||||||
|
@reg_mccarg
|
||||||
|
when 0x8_u8
|
||||||
|
@reg_tmp
|
||||||
|
when 0x9_u8
|
||||||
|
@reg_sp
|
||||||
|
when 0xa_u8
|
||||||
|
@reg_a
|
||||||
|
when 0xb_u8
|
||||||
|
@reg_b
|
||||||
|
when 0xc_u8
|
||||||
|
@reg_c
|
||||||
|
when 0xd_u8
|
||||||
|
@reg_d
|
||||||
|
when 0xe_u8
|
||||||
|
@reg_inp
|
||||||
|
when 0xf_u8
|
||||||
|
@reg_out
|
||||||
|
else
|
||||||
|
raise "Invalid register index: #{index}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_register(index : UInt8, value : UInt16) : UInt16
|
||||||
|
case index
|
||||||
|
when 0x0_u8
|
||||||
|
@reg_pc = value
|
||||||
|
when 0x1_u8
|
||||||
|
@reg_opc = value
|
||||||
|
when 0x2_u8
|
||||||
|
@reg_arg = value
|
||||||
|
when 0x3_u8
|
||||||
|
@reg_mccpc = value
|
||||||
|
when 0x4_u8
|
||||||
|
@reg_mccs = value
|
||||||
|
when 0x5_u8
|
||||||
|
@reg_mcce = value
|
||||||
|
when 0x6_u8
|
||||||
|
@reg_mcc = value
|
||||||
|
when 0x7_u8
|
||||||
|
@reg_mccarg = value
|
||||||
|
when 0x8_u8
|
||||||
|
@reg_tmp = value
|
||||||
|
when 0x9_u8
|
||||||
|
@reg_sp = value
|
||||||
|
when 0xa_u8
|
||||||
|
@reg_a = value
|
||||||
|
when 0xb_u8
|
||||||
|
@reg_b = value
|
||||||
|
when 0xc_u8
|
||||||
|
@reg_c = value
|
||||||
|
when 0xd_u8
|
||||||
|
@reg_d = value
|
||||||
|
when 0xe_u8
|
||||||
|
@reg_inp = value
|
||||||
|
when 0xf_u8
|
||||||
|
@reg_out = value
|
||||||
|
else
|
||||||
|
raise "Invalid register index: #{index}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def alu(op : UInt8) : UInt16
|
||||||
|
@reg_tmp =
|
||||||
|
case op
|
||||||
|
when 0x00 # not
|
||||||
|
~@reg_a
|
||||||
|
when 0x01 # and
|
||||||
|
@reg_a & @reg_b
|
||||||
|
when 0x02 # nand
|
||||||
|
~(@reg_a & @reg_b)
|
||||||
|
when 0x03 # or
|
||||||
|
@reg_a | @reg_b
|
||||||
|
when 0x04 # nor
|
||||||
|
~(@reg_a | @reg_b)
|
||||||
|
when 0x05 # xor
|
||||||
|
@reg_a ^ @reg_b
|
||||||
|
when 0x06 # xnor
|
||||||
|
~(@reg_a ^ @reg_b)
|
||||||
|
when 0x07 # shl
|
||||||
|
@reg_a << @reg_b
|
||||||
|
when 0x08 # shr
|
||||||
|
@reg_a >> @reg_b
|
||||||
|
when 0x09 # add
|
||||||
|
@reg_a &+ @reg_b
|
||||||
|
when 0x0a # sub
|
||||||
|
@reg_a &- @reg_b
|
||||||
|
when 0x0b # mul
|
||||||
|
@reg_a &* @reg_b
|
||||||
|
when 0x0c # div
|
||||||
|
@reg_a // @reg_b
|
||||||
|
when 0x0d # mod
|
||||||
|
@reg_a % @reg_b
|
||||||
|
when 0x0e # cmp
|
||||||
|
if @reg_a < @reg_b
|
||||||
|
1_u16
|
||||||
|
elsif @reg_a > @reg_b
|
||||||
|
2_u16
|
||||||
|
else
|
||||||
|
0_u16
|
||||||
|
end
|
||||||
|
when 0x0f # eq
|
||||||
|
@reg_a == @reg_b ? 1_u16 : 0_u16
|
||||||
|
when 0x10 # neq
|
||||||
|
@reg_a != @reg_b ? 1_u16 : 0_u16
|
||||||
|
when 0x11 # lt
|
||||||
|
@reg_a < @reg_b ? 1_u16 : 0_u16
|
||||||
|
when 0x12 # gt
|
||||||
|
@reg_a > @reg_b ? 1_u16 : 0_u16
|
||||||
|
when 0x13 # leq
|
||||||
|
@reg_a <= @reg_b ? 1_u16 : 0_u16
|
||||||
|
when 0x14 # geq
|
||||||
|
@reg_a >= @reg_b ? 1_u16 : 0_u16
|
||||||
|
when 0x15 # bol
|
||||||
|
@reg_a == 1 ? 1_u16 : 0_u16
|
||||||
|
when 0x16 # neg
|
||||||
|
@reg_a == 1 ? 0_u16 : 1_u16
|
||||||
|
else
|
||||||
|
raise "Invalid ALU operation: 0x#{op.to_s(16).rjust(2, '0')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_memory(address : UInt16) : UInt16
|
||||||
|
if address < {{ 32 * 1024 }}
|
||||||
|
@program[address].to_u16
|
||||||
|
elsif address < {{ 40 * 1024 }}
|
||||||
|
@stack[address - {{ 32 * 1024 }}]
|
||||||
|
else
|
||||||
|
@memory[address - {{ 40 * 1024 }}]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_memory(address : UInt16, value : UInt16) : UInt16
|
||||||
|
if address < {{ 32 * 1024 }}
|
||||||
|
0_u16
|
||||||
|
elsif address < {{ 40 * 1024 }}
|
||||||
|
@stack[address - {{ 32 * 1024 }}] = value
|
||||||
|
|
||||||
|
value
|
||||||
|
else
|
||||||
|
@memory[address - {{ 40 * 1024 }}] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
181
src/hence/firmware.cr
Normal file
181
src/hence/firmware.cr
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
require "yaml"
|
||||||
|
require "ecr"
|
||||||
|
|
||||||
|
require "./firmware/*"
|
||||||
|
|
||||||
|
module Hence
|
||||||
|
class Firmware
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property name
|
||||||
|
|
||||||
|
@[YAML::Field(emit_null: true)]
|
||||||
|
property description
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property authors
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property date
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property version
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property sections
|
||||||
|
|
||||||
|
def initialize(
|
||||||
|
@name : String,
|
||||||
|
@description : String?,
|
||||||
|
@authors : Array(String),
|
||||||
|
@date : Time,
|
||||||
|
@version : String,
|
||||||
|
@sections : Array(Section)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def full_name : String
|
||||||
|
"#{@name}:#{@version}"
|
||||||
|
end
|
||||||
|
|
||||||
|
ECR.def_to_s "#{__DIR__}/firmware/documentation.md.ecr"
|
||||||
|
|
||||||
|
def build_opcodes : {names: Hash(String, UInt8), opcodes: Hash(UInt8, Opcode)}
|
||||||
|
names = {} of String => UInt8
|
||||||
|
opcodes = {} of UInt8 => Opcode
|
||||||
|
i = 0_u8
|
||||||
|
@sections.each do |s|
|
||||||
|
s.opcodes.each do |opc|
|
||||||
|
j = if opc.opcode
|
||||||
|
opc.opcode.not_nil!
|
||||||
|
else
|
||||||
|
x = i
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
x
|
||||||
|
end
|
||||||
|
raise "Duplicate opcode: #{j}" if opcodes.has_key?(j)
|
||||||
|
|
||||||
|
names[opc.name] = j
|
||||||
|
opcodes[j] = opc.to_opcode(opc.opcode ? nil : j)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
{names: names, opcodes: opcodes}
|
||||||
|
end
|
||||||
|
|
||||||
|
def compile(microcode_names : Hash(String, UInt8), data = Assembler::Data.new) : Bytes
|
||||||
|
sorted_opcodes = build_opcodes[:opcodes].to_a.sort_by!(&.[0])
|
||||||
|
data.offset = (sorted_opcodes.last[0].to_u16 + 1) * 4
|
||||||
|
sorted_opcodes.each do |opc|
|
||||||
|
end_offset = data.offset + opc[1].microcode.body.size * 3
|
||||||
|
base = opc[0] * 4
|
||||||
|
data.bin[base] = (data.offset >> 8).to_u8
|
||||||
|
data.bin[base + 1] = (data.offset & 0xff).to_u8
|
||||||
|
data.bin[base + 2] = (end_offset >> 8).to_u8
|
||||||
|
data.bin[base + 3] = (end_offset & 0xff).to_u8
|
||||||
|
|
||||||
|
i = 0_u16
|
||||||
|
opc[1].microcode.body.each do |node|
|
||||||
|
j = data.offset + i
|
||||||
|
|
||||||
|
case node
|
||||||
|
when Parser::AST::LabelNode
|
||||||
|
data.constants[node.name] = Parser::AST::NumberArg.new(data.offset &- 3)
|
||||||
|
when Parser::AST::CallNode
|
||||||
|
data.bin[j] = microcode_names[node.name]
|
||||||
|
if node.arg
|
||||||
|
arg = node.arg.not_nil!.resolve(data)
|
||||||
|
|
||||||
|
data.bin[j + 1] = arg.not_nil![0]
|
||||||
|
data.bin[j + 2] = arg.not_nil![1]
|
||||||
|
end
|
||||||
|
|
||||||
|
i += 3
|
||||||
|
else
|
||||||
|
raise "Unexpected node type: #{typeof(node)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
data.offset = end_offset
|
||||||
|
end
|
||||||
|
|
||||||
|
data.bin
|
||||||
|
end
|
||||||
|
|
||||||
|
class Section
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property name
|
||||||
|
|
||||||
|
@[YAML::Field(emit_null: true)]
|
||||||
|
property description
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property opcodes
|
||||||
|
|
||||||
|
def initialize(@name : String, @description : String?, @opcodes : Array(Opcode))
|
||||||
|
end
|
||||||
|
|
||||||
|
class Opcode
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property name
|
||||||
|
|
||||||
|
@[YAML::Field(emit_null: true)]
|
||||||
|
property description
|
||||||
|
|
||||||
|
@[YAML::Field(emit_null: true)]
|
||||||
|
property example
|
||||||
|
|
||||||
|
@[YAML::Field(emit_null: true)]
|
||||||
|
property opcode
|
||||||
|
|
||||||
|
@[YAML::Field(emit_null: true)]
|
||||||
|
property arg
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property stack
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property microcode
|
||||||
|
|
||||||
|
def initialize(
|
||||||
|
@name : String,
|
||||||
|
@description : String?,
|
||||||
|
@example : String?,
|
||||||
|
@opcode : UInt8?,
|
||||||
|
@arg : String?,
|
||||||
|
@stack : Stack,
|
||||||
|
@microcode : String
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_opcode(opcode : UInt8? = nil) : ::Hence::Opcode
|
||||||
|
raise "Opcode already defined" if opcode && @opcode
|
||||||
|
|
||||||
|
Hence::Opcode.new(
|
||||||
|
@name,
|
||||||
|
Parser.parse(@microcode)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
class Stack
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property input
|
||||||
|
|
||||||
|
@[YAML::Field]
|
||||||
|
property output
|
||||||
|
|
||||||
|
def initialize(@input : Array(String), @output : Array(String))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
56
src/hence/firmware/documentation.md.ecr
Normal file
56
src/hence/firmware/documentation.md.ecr
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<%- sorted_opcodes = build_opcodes[:opcodes].to_a.sort_by!(&.[0]) -%>
|
||||||
|
---
|
||||||
|
title: <%=full_name %>
|
||||||
|
description: "<%= @description %>"
|
||||||
|
author: [<%= @authors.map { |a| "\"#{a}\"" }.join(", ") %>]
|
||||||
|
date: <%= @date.to_s("%Y-%m-%d") %>
|
||||||
|
version: "<%= @version %>"
|
||||||
|
---
|
||||||
|
|
||||||
|
# `<%= full_name %>`
|
||||||
|
|
||||||
|
<%= @description %>
|
||||||
|
|
||||||
|
## Opcodes
|
||||||
|
|
||||||
|
Opcode | Name
|
||||||
|
---|---
|
||||||
|
<%- sorted_opcodes.each do |opcode| -%>
|
||||||
|
`0x<%= opcode[0].to_s(16).rjust(2, '0') %>` | [`<%= opcode[1].name %>`](#<%= opcode[1].name %>)
|
||||||
|
<%- end -%>
|
||||||
|
|
||||||
|
## Sections
|
||||||
|
|
||||||
|
<%- @sections.each do |section| -%>
|
||||||
|
### <%= section.name %>
|
||||||
|
|
||||||
|
<%= section.description %>
|
||||||
|
|
||||||
|
<%- section.opcodes.each do |opcode| -%>
|
||||||
|
#### `<%= opcode.name %>`
|
||||||
|
|
||||||
|
<%= opcode.description %>
|
||||||
|
|
||||||
|
<% input = opcode.stack.input.join(", ") -%>
|
||||||
|
<% input += " " unless input.blank? -%>
|
||||||
|
<% output = opcode.stack.output.join(", ") -%>
|
||||||
|
<% output = " " + output unless output.blank? -%>
|
||||||
|
Opcode | Argument | Stack
|
||||||
|
---|---|---
|
||||||
|
`0x<%= sorted_opcodes.find! { |opc| opc[1].name == opcode.name }[0].to_s(16).rjust(2, '0') %>` | <%= opcode.arg && "`#{opcode.arg}`" %> | <%= (opcode.stack.input.size > 0 && opcode.stack.output.size > 0) ? "`#{input}--#{output}`" : nil %>
|
||||||
|
<%- if opcode.example -%>
|
||||||
|
|
||||||
|
```gnuassembler
|
||||||
|
; Example
|
||||||
|
<%= opcode.example -%>
|
||||||
|
```
|
||||||
|
<%- end -%>
|
||||||
|
|
||||||
|
```gnuassembler
|
||||||
|
; Microcode
|
||||||
|
<%= opcode.microcode -%>
|
||||||
|
```
|
||||||
|
|
||||||
|
<%- end -%>
|
||||||
|
<%- end -%>
|
||||||
|
> _Generated using the [hence](https://git.dergrimm.net/dergrimm/hence) firmware compiler._
|
13
src/hence/firmware/firmwares.cr
Normal file
13
src/hence/firmware/firmwares.cr
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module Hence
|
||||||
|
class Firmware
|
||||||
|
{% begin %}
|
||||||
|
{% base_dir = "#{__DIR__}/firmwares" %}
|
||||||
|
|
||||||
|
FIRMWARES = {
|
||||||
|
{% for firmware_name in run("./macros/get_firmwares.cr", base_dir).stringify.lines %}
|
||||||
|
{{ firmware_name }} => Firmware.from_yaml({{ read_file("#{base_dir.id}/#{firmware_name.id}.yml") }}),
|
||||||
|
{% end %}
|
||||||
|
} of String => Firmware
|
||||||
|
{% end %}
|
||||||
|
end
|
||||||
|
end
|
622
src/hence/firmware/firmwares/default.yml
Normal file
622
src/hence/firmware/firmwares/default.yml
Normal file
|
@ -0,0 +1,622 @@
|
||||||
|
---
|
||||||
|
name: default
|
||||||
|
description: Default hence opcode firmware
|
||||||
|
authors:
|
||||||
|
- Dominic Grimm
|
||||||
|
date: 2022-05-14
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
sections:
|
||||||
|
- name: flow-control
|
||||||
|
description: Flow control
|
||||||
|
opcodes:
|
||||||
|
- name: nop
|
||||||
|
description: No operation
|
||||||
|
example: |
|
||||||
|
nop ; literally nothing but wasting precious cycles
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: []
|
||||||
|
output: []
|
||||||
|
microcode: |
|
||||||
|
nop
|
||||||
|
- name: dbg
|
||||||
|
description: Debugs current stack
|
||||||
|
example: |
|
||||||
|
push 40
|
||||||
|
push 2
|
||||||
|
dbg ; => (2) [40, 2]
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: []
|
||||||
|
output: []
|
||||||
|
microcode: |
|
||||||
|
dbg
|
||||||
|
- name: stp
|
||||||
|
description: Stops execution
|
||||||
|
example: |
|
||||||
|
stp ; program stops execution here. push will not be called
|
||||||
|
push 1
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: []
|
||||||
|
output: []
|
||||||
|
microcode: |
|
||||||
|
tmps 0xfffc
|
||||||
|
tmpl 0x0
|
||||||
|
- name: jmp
|
||||||
|
description: Jumps to address
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [addr]
|
||||||
|
output: []
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0x0
|
||||||
|
- name: jif
|
||||||
|
description: Jumps to address if condition is true (1)
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [cond, addr]
|
||||||
|
output: []
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xc
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
tmpsr 0xc
|
||||||
|
tmplc 0x0
|
||||||
|
- name: stack-manipulation
|
||||||
|
description: Stack manipulation
|
||||||
|
opcodes:
|
||||||
|
- name: push
|
||||||
|
description: Pushes value to stack
|
||||||
|
opcode: null
|
||||||
|
arg: x
|
||||||
|
stack:
|
||||||
|
input: []
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
tmpsr 0x2
|
||||||
|
push
|
||||||
|
- name: drop
|
||||||
|
description: Drops top element from stack
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x]
|
||||||
|
output: []
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
- name: depth
|
||||||
|
description: Returns stack size / depth
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: []
|
||||||
|
output: [depth]
|
||||||
|
microcode: |
|
||||||
|
tmpsr 0x9
|
||||||
|
push
|
||||||
|
- name: pick
|
||||||
|
description: Duplicates top nth element stack value
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: ["n"]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
pick
|
||||||
|
push
|
||||||
|
- name: dup
|
||||||
|
description: Duplicates top element on stack
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x]
|
||||||
|
output: [x, x]
|
||||||
|
microcode: |
|
||||||
|
pick
|
||||||
|
push
|
||||||
|
- name: swap
|
||||||
|
description: Swaps top two elements on stack
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x2, x1]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
tmpsr 0xa
|
||||||
|
push
|
||||||
|
tmpsr 0xb
|
||||||
|
push
|
||||||
|
- name: over
|
||||||
|
description: Duplicates second top element onto stack
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x1, x2, x1]
|
||||||
|
microcode: |
|
||||||
|
tmps 1
|
||||||
|
pick
|
||||||
|
push
|
||||||
|
- name: rot
|
||||||
|
description: Rotates top three elements on stack
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2, x3]
|
||||||
|
output: [x2, x3, x1]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xc
|
||||||
|
tmpsr 0xb
|
||||||
|
push
|
||||||
|
tmpsr 0xc
|
||||||
|
push
|
||||||
|
tmpsr 0xa
|
||||||
|
push
|
||||||
|
- name: nip
|
||||||
|
description: Drops the first item below the top of stack
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x2]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
drop
|
||||||
|
tmpsr 0xa
|
||||||
|
push
|
||||||
|
- name: tuck
|
||||||
|
description: Copies the top stack item below the second stack item
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x2, x1, x2]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
tmpsr 0xa
|
||||||
|
push
|
||||||
|
tmpsr 0xb
|
||||||
|
push
|
||||||
|
tmpsr 0xa
|
||||||
|
push
|
||||||
|
- name: memory-access
|
||||||
|
description: Memory access
|
||||||
|
opcodes:
|
||||||
|
- name: load
|
||||||
|
description: Loads value from memory
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [addr]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
load
|
||||||
|
push
|
||||||
|
- name: store
|
||||||
|
description: Stores value to memory
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x, addr]
|
||||||
|
output: []
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
tmpsr 0xb
|
||||||
|
store
|
||||||
|
- name: io
|
||||||
|
description: IO access
|
||||||
|
opcodes:
|
||||||
|
- name: inp
|
||||||
|
description: Gets input from general IO
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: []
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
tmpsr 0xe
|
||||||
|
push
|
||||||
|
- name: out
|
||||||
|
description: Outputs value to general IO
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x]
|
||||||
|
output: []
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xf
|
||||||
|
- name: print
|
||||||
|
description: Prints number to STDOUT / LCD display
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x]
|
||||||
|
output: []
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
print
|
||||||
|
- name: emit
|
||||||
|
description: Prints input as unicode char to STDOUT / LCD display
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x]
|
||||||
|
output: []
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
emit
|
||||||
|
- name: arithmetic
|
||||||
|
description: Arithmetic
|
||||||
|
opcodes:
|
||||||
|
- name: not
|
||||||
|
description: Logical invert of top stack element
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x00
|
||||||
|
push
|
||||||
|
- name: and
|
||||||
|
description: Logical and of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x01
|
||||||
|
push
|
||||||
|
- name: nand
|
||||||
|
description: Logical nand of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x02
|
||||||
|
push
|
||||||
|
- name: or
|
||||||
|
description: Logical or of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x03
|
||||||
|
push
|
||||||
|
- name: nor
|
||||||
|
description: Logical nor of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x04
|
||||||
|
push
|
||||||
|
- name: xor
|
||||||
|
description: Logical xor of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x05
|
||||||
|
push
|
||||||
|
- name: xnor
|
||||||
|
description: Logical xnor of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x06
|
||||||
|
push
|
||||||
|
- name: shl
|
||||||
|
description: Shifts top stack element left by n bits
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x, n]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x07
|
||||||
|
push
|
||||||
|
- name: shr
|
||||||
|
description: Shifts top stack element right by n bits
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x, n]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x08
|
||||||
|
push
|
||||||
|
- name: add
|
||||||
|
description: Adds top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x09
|
||||||
|
push
|
||||||
|
- name: inc
|
||||||
|
description: Increments top stack element by 1
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
tmps 1
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x09
|
||||||
|
push
|
||||||
|
- name: sub
|
||||||
|
description: Subtracts top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x0a
|
||||||
|
push
|
||||||
|
- name: dec
|
||||||
|
description: Decrements top stack element by 1
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
tmps 1
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x0a
|
||||||
|
push
|
||||||
|
- name: mul
|
||||||
|
description: Multiplies top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x0b
|
||||||
|
push
|
||||||
|
- name: div
|
||||||
|
description: Divides top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x0c
|
||||||
|
push
|
||||||
|
- name: mod
|
||||||
|
description: Modulo of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x0d
|
||||||
|
push
|
||||||
|
- name: cmp
|
||||||
|
description: Compares top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x0e
|
||||||
|
push
|
||||||
|
- name: eq
|
||||||
|
description: Logical equality of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x0f
|
||||||
|
push
|
||||||
|
- name: neq
|
||||||
|
description: Logical inequality of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x10
|
||||||
|
push
|
||||||
|
- name: lt
|
||||||
|
description: Logical less than of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x11
|
||||||
|
push
|
||||||
|
- name: gt
|
||||||
|
description: Logical greater than of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x12
|
||||||
|
push
|
||||||
|
- name: leq
|
||||||
|
description: Logical less than or equal to of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x13
|
||||||
|
push
|
||||||
|
- name: geq
|
||||||
|
description: Logical greater than or equal to of top two stack elements
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x1, x2]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xb
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x14
|
||||||
|
push
|
||||||
|
- name: bol
|
||||||
|
description: Boolean of top stack element
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x15
|
||||||
|
push
|
||||||
|
- name: neg
|
||||||
|
description: Negates top stack element
|
||||||
|
opcode: null
|
||||||
|
arg: null
|
||||||
|
stack:
|
||||||
|
input: [x]
|
||||||
|
output: [x]
|
||||||
|
microcode: |
|
||||||
|
drop
|
||||||
|
tmpl 0xa
|
||||||
|
alu 0x16
|
||||||
|
push
|
1
src/hence/firmware/macros/get_firmwares.cr
Normal file
1
src/hence/firmware/macros/get_firmwares.cr
Normal file
|
@ -0,0 +1 @@
|
||||||
|
print Dir[Path[ARGV[0], "*.yml"]].map { |f| File.basename(f, ".yml") }.join("\n")
|
98
src/hence/microcode.cr
Normal file
98
src/hence/microcode.cr
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
require "colorize"
|
||||||
|
require "string_scanner"
|
||||||
|
|
||||||
|
module Hence
|
||||||
|
struct Microcode
|
||||||
|
def initialize(@runner : Emulator::Data ->)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(&@runner : Emulator::Data ->)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(data : Emulator::Data) : Emulator::Data
|
||||||
|
@runner.call(data)
|
||||||
|
|
||||||
|
data
|
||||||
|
end
|
||||||
|
|
||||||
|
MICROCODES = {
|
||||||
|
0x00_u8 => Microcode.new { }, # nop
|
||||||
|
0x01_u8 => Microcode.new do |d| # dbg
|
||||||
|
stack = if d.reg_sp == 0
|
||||||
|
[] of UInt16
|
||||||
|
else
|
||||||
|
d.reg_sp.times.map_with_index { |_, i| d.get_memory({{ 32_u16 * 1024 }} + i) }
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "#{"[DEBUG]".colorize(:red).mode(:bold)}: (#{d.reg_sp}) #{stack}"
|
||||||
|
print "Press enter to continue execution...".colorize.mode(:dim)
|
||||||
|
gets
|
||||||
|
end,
|
||||||
|
0x02_u8 => Microcode.new do |d| # jmp
|
||||||
|
d.reg_mccpc = d.reg_tmp
|
||||||
|
end,
|
||||||
|
0x03_u8 => Microcode.new do |d| # jif
|
||||||
|
if d.reg_tmp == 1
|
||||||
|
d.reg_mccpc = d.reg_a
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
0x04_u8 => Microcode.new do |d| # tmpl
|
||||||
|
d.set_register((d.reg_mccarg & 0xff).to_u8, d.reg_tmp)
|
||||||
|
end,
|
||||||
|
0x05_u8 => Microcode.new do |d| # tmplc
|
||||||
|
d.set_register((d.reg_mccarg & 0xff).to_u8, d.reg_tmp) if d.reg_a == 1
|
||||||
|
end,
|
||||||
|
0x06_u8 => Microcode.new do |d| # tmps
|
||||||
|
d.reg_tmp = d.reg_mccarg
|
||||||
|
end,
|
||||||
|
0x07_u8 => Microcode.new do |d| # tmpsr
|
||||||
|
d.reg_tmp = d.get_register((d.reg_mccarg & 0xff).to_u8)
|
||||||
|
end,
|
||||||
|
0x08_u8 => Microcode.new do |d| # push
|
||||||
|
d.set_memory({{ 32_u16 * 1024 }} + d.reg_sp, d.reg_tmp)
|
||||||
|
d.reg_sp += 1
|
||||||
|
end,
|
||||||
|
0x09_u8 => Microcode.new do |d| # drop
|
||||||
|
d.reg_tmp = d.get_memory({{ 32_u16 * 1024 }} + d.reg_sp - 1)
|
||||||
|
d.set_memory({{ 32_u16 * 1024 }} + d.reg_sp - 1, 0_u16)
|
||||||
|
d.reg_sp -= 1 if d.reg_sp.positive?
|
||||||
|
end,
|
||||||
|
0x0a_u8 => Microcode.new do |d| # pick
|
||||||
|
d.reg_tmp = d.get_memory({{ 32_u16 * 1024 }} + d.reg_sp - 1 - d.reg_tmp)
|
||||||
|
end,
|
||||||
|
0x0b_u8 => Microcode.new do |d| # alu
|
||||||
|
d.alu((d.reg_mccarg & 0xff).to_u8)
|
||||||
|
end,
|
||||||
|
0x0c_u8 => Microcode.new do |d| # load
|
||||||
|
d.reg_tmp = d.get_memory(d.reg_tmp)
|
||||||
|
end,
|
||||||
|
0x0d_u8 => Microcode.new do |d| # store
|
||||||
|
d.set_memory(d.reg_tmp, d.reg_a)
|
||||||
|
end,
|
||||||
|
0x0e_u8 => Microcode.new do |d| # print
|
||||||
|
print d.reg_tmp
|
||||||
|
end,
|
||||||
|
0x0f_u8 => Microcode.new do |d| # emit
|
||||||
|
print String.new(Bytes[d.reg_tmp])
|
||||||
|
end,
|
||||||
|
} of UInt8 => Microcode
|
||||||
|
MICROCODE_NAMES = {
|
||||||
|
"nop" => 0x00_u8,
|
||||||
|
"dbg" => 0x01_u8,
|
||||||
|
"jmp" => 0x02_u8,
|
||||||
|
"jif" => 0x03_u8,
|
||||||
|
"tmpl" => 0x04_u8,
|
||||||
|
"tmplc" => 0x05_u8,
|
||||||
|
"tmps" => 0x06_u8,
|
||||||
|
"tmpsr" => 0x07_u8,
|
||||||
|
"push" => 0x08_u8,
|
||||||
|
"drop" => 0x09_u8,
|
||||||
|
"pick" => 0x0a_u8,
|
||||||
|
"alu" => 0x0b_u8,
|
||||||
|
"load" => 0x0c_u8,
|
||||||
|
"store" => 0x0d_u8,
|
||||||
|
"print" => 0x0e_u8,
|
||||||
|
"emit" => 0x0f_u8,
|
||||||
|
} of String => UInt8
|
||||||
|
end
|
||||||
|
end
|
9
src/hence/opcode.cr
Normal file
9
src/hence/opcode.cr
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
module Hence
|
||||||
|
struct Opcode
|
||||||
|
property name
|
||||||
|
property microcode
|
||||||
|
|
||||||
|
def initialize(@name : String, @microcode : Parser::AST)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
361
src/hence/parser.cr
Normal file
361
src/hence/parser.cr
Normal file
|
@ -0,0 +1,361 @@
|
||||||
|
require "string_scanner"
|
||||||
|
|
||||||
|
module Hence
|
||||||
|
module Parser
|
||||||
|
extend self
|
||||||
|
|
||||||
|
def parse(source : String) : AST
|
||||||
|
s = StringScanner.new(source.gsub(/;.*|@.*|#.*/, nil))
|
||||||
|
body = [] of AST::Node
|
||||||
|
loop do
|
||||||
|
s.skip(/\s*/)
|
||||||
|
break if s.eos?
|
||||||
|
|
||||||
|
body <<
|
||||||
|
if constant_dirty_name = s.scan(/\S+[ \t]*=/)
|
||||||
|
constant_name = constant_dirty_name[..-2].rstrip
|
||||||
|
|
||||||
|
s.skip(/[ \t]*/)
|
||||||
|
|
||||||
|
constant_values = parse_args(s).not_nil!
|
||||||
|
raise "Constant can't have more than one value" if constant_values.size > 1
|
||||||
|
|
||||||
|
AST::ConstantDeclarationNode.new(constant_name, constant_values.first)
|
||||||
|
elsif label_dirty_name = s.scan(/\S+:/)
|
||||||
|
label_name = label_dirty_name.not_nil![..-2]
|
||||||
|
raise "Label name must be lowercase" if label_name != label_name.downcase
|
||||||
|
|
||||||
|
AST::LabelNode.new(label_name)
|
||||||
|
# else
|
||||||
|
# call_name = s.scan(/\S+/).not_nil!
|
||||||
|
|
||||||
|
# s.skip(/[ \t]*/)
|
||||||
|
|
||||||
|
# if call_name.starts_with?('.')
|
||||||
|
# AST::MacroCallNode.new(call_name[1..], self.parse_args(s) || [] of AST::Arg)
|
||||||
|
# else
|
||||||
|
# call_args = self.parse_args(s)
|
||||||
|
# raise "Op code can't be called with more than one argument" if call_args && call_args.size > 1
|
||||||
|
|
||||||
|
# AST::CallNode.new(call_name, call_args.try(&.first?))
|
||||||
|
# end
|
||||||
|
elsif macro_name_raw = s.scan(/\.\S+/)
|
||||||
|
s.skip(/[ \t]*/)
|
||||||
|
|
||||||
|
AST::MacroCallNode.new(macro_name_raw[1..], parse_args(s) || [] of AST::Arg)
|
||||||
|
else
|
||||||
|
call_name = s.scan(/\S+/).not_nil!
|
||||||
|
|
||||||
|
s.skip(/[ \t]*/)
|
||||||
|
|
||||||
|
call_args = parse_args(s) || [] of AST::Arg
|
||||||
|
raise "Op code can't be called with more than one argument" if call_args && call_args.size > 1
|
||||||
|
|
||||||
|
AST::CallNode.new(call_name, call_args.first?)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
AST.new(body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_binary_operation(op : String) : AST::BinaryOperation
|
||||||
|
case op
|
||||||
|
when "+"
|
||||||
|
AST::AddBinaryOperation
|
||||||
|
when "-"
|
||||||
|
AST::SubBinaryOperation
|
||||||
|
when "*"
|
||||||
|
AST::MulBinaryOperation
|
||||||
|
when "/"
|
||||||
|
AST::DivBinaryOperation
|
||||||
|
when "**"
|
||||||
|
AST::PowBinaryOperation
|
||||||
|
else
|
||||||
|
raise "Unexpected binary operation: #{op}"
|
||||||
|
end.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_binary_expression_arg(scanner : StringScanner) : AST::BinaryExpressionArg?
|
||||||
|
scanner.skip(/[ \t]*/)
|
||||||
|
return unless scanner.scan(/\(/)
|
||||||
|
|
||||||
|
content = scanner.scan_until(/\)/).not_nil![..-2].split(/\s/, remove_empty: true)
|
||||||
|
raise "Malformed binary expression" if content.size < 3 || content.size % 2 == 0
|
||||||
|
|
||||||
|
first_args = content.shift(3)
|
||||||
|
bin_expr = AST::BinaryExpressionArg.new(
|
||||||
|
parse_args(first_args[0] + "\n").not_nil!.first,
|
||||||
|
parse_args(first_args[2] + "\n").not_nil!.first,
|
||||||
|
parse_binary_operation(first_args[1])
|
||||||
|
)
|
||||||
|
|
||||||
|
until content.empty?
|
||||||
|
args = content.shift(2).not_nil!
|
||||||
|
bin_expr = AST::BinaryExpressionArg.new(
|
||||||
|
bin_expr,
|
||||||
|
parse_args(args[1] + "\n").not_nil!.first,
|
||||||
|
parse_binary_operation(args[0])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
bin_expr
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_binary_expression_arg(string : String) : AST::BinaryExpressionArg?
|
||||||
|
parse_binary_expression_arg(StringScanner.new(string))
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_args(scanner : StringScanner) : Array(AST::Arg)?
|
||||||
|
args_s = scanner.scan_until(/\n/)
|
||||||
|
|
||||||
|
if args_s.try(&.blank?)
|
||||||
|
[] of AST::Arg
|
||||||
|
else
|
||||||
|
s = StringScanner.new(args_s.not_nil!.strip)
|
||||||
|
args = [] of AST::Arg
|
||||||
|
|
||||||
|
until s.eos?
|
||||||
|
s.skip(/[ \t]*/)
|
||||||
|
raise "Can't seperate arguments because of no comma" if s.skip(/,/).nil? && args.size > 0
|
||||||
|
s.skip(/[ \t]*/)
|
||||||
|
|
||||||
|
args <<
|
||||||
|
if s.check(/\(/)
|
||||||
|
parse_binary_expression_arg(s).not_nil!
|
||||||
|
elsif string_raw = s.scan(/".*"/)
|
||||||
|
AST::StringArg.new(string_raw[1..-2].gsub(/\\n/, '\n'))
|
||||||
|
elsif number = s.scan(/\d[\d\w]*/)
|
||||||
|
AST::NumberArg.new(number.to_u16(prefix: true, underscore: true, strict: true))
|
||||||
|
else
|
||||||
|
AST::ConstantArg.new(s.scan(/\w+/).not_nil!)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
args
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_args(string : String) : Array(AST::Arg)?
|
||||||
|
parse_args(StringScanner.new(string))
|
||||||
|
end
|
||||||
|
|
||||||
|
struct AST
|
||||||
|
property body
|
||||||
|
|
||||||
|
def initialize(@body : Body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
@body.map(&.to_s).join('\n')
|
||||||
|
end
|
||||||
|
|
||||||
|
abstract struct Node
|
||||||
|
abstract def to_s : String
|
||||||
|
end
|
||||||
|
|
||||||
|
alias Body = Array(Node)
|
||||||
|
|
||||||
|
struct LabelNode < Node
|
||||||
|
property name
|
||||||
|
|
||||||
|
def initialize(@name : String)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
"#{@name}:"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
abstract class Arg
|
||||||
|
abstract def resolve(data : Assembler::Data) : Bytes
|
||||||
|
|
||||||
|
abstract def resolve_as_number(data : Assembler::Data) : UInt16
|
||||||
|
|
||||||
|
abstract def to_s : String
|
||||||
|
end
|
||||||
|
|
||||||
|
class NumberArg < Arg
|
||||||
|
property number
|
||||||
|
|
||||||
|
def initialize(@number : UInt16)
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve(_data : Assembler::Data) : Bytes
|
||||||
|
n = Utils.split_uint16(@number)
|
||||||
|
Bytes[n[0], n[1]]
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_as_number(_data : Assembler::Data) : UInt16
|
||||||
|
@number
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
@number.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ConstantArg < Arg
|
||||||
|
property name
|
||||||
|
|
||||||
|
def initialize(@name : String)
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve(data : Assembler::Data) : Bytes
|
||||||
|
data.constants[@name].resolve(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_as_number(data : Assembler::Data) : UInt16
|
||||||
|
data.constants[@name].resolve_as_number(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
@name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class StringArg < Arg
|
||||||
|
property string
|
||||||
|
|
||||||
|
def initialize(@string : String)
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve(_data : Assembler::Data) : Bytes
|
||||||
|
if @string.bytesize == 1
|
||||||
|
Bytes[0_u8, @string.bytes[0]]
|
||||||
|
else
|
||||||
|
Bytes.new(@string.bytes.to_unsafe, @string.bytesize)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_as_number(_data : Assembler::Data) : UInt16
|
||||||
|
slice = resolve(_data)
|
||||||
|
|
||||||
|
Utils.merge_uint16(slice[0], slice[1])
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
"\"#{@string}\""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
abstract struct BinaryOperation
|
||||||
|
abstract def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||||
|
|
||||||
|
abstract def to_s : String
|
||||||
|
end
|
||||||
|
|
||||||
|
struct AddBinaryOperation < BinaryOperation
|
||||||
|
def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||||
|
NumberArg.new(left.resolve_as_number(data) &+ right.resolve_as_number(data))
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
"+"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
struct SubBinaryOperation < BinaryOperation
|
||||||
|
def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||||
|
NumberArg.new(left.resolve_as_number(data) &- right.resolve_as_number(data))
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
"-"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
struct MulBinaryOperation < BinaryOperation
|
||||||
|
def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||||
|
NumberArg.new(left.resolve_as_number(data) &* right.resolve_as_number(data))
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
"*"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
struct DivBinaryOperation < BinaryOperation
|
||||||
|
def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||||
|
NumberArg.new(left.resolve_as_number(data) // right.resolve_as_number(data))
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
"/"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
struct PowBinaryOperation < BinaryOperation
|
||||||
|
def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||||
|
NumberArg.new(left.resolve_as_number(data) &** right.resolve_as_number(data))
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
"**"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class BinaryExpressionArg < Arg
|
||||||
|
property left
|
||||||
|
property right
|
||||||
|
property op
|
||||||
|
|
||||||
|
def initialize(@left : Arg, @right : Arg, @op : BinaryOperation)
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve(data : Assembler::Data) : Bytes
|
||||||
|
@op.resolve(@left, @right, data).resolve(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_as_number(data : Assembler::Data) : UInt16
|
||||||
|
@op.resolve(@left, @right, data).resolve_as_number(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
# ameba:disable Lint/RedundantStringCoercion
|
||||||
|
"(#{@left.to_s} #{@op.to_s} #{@right.to_s})"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
struct ConstantDeclarationNode < Node
|
||||||
|
property name
|
||||||
|
property value
|
||||||
|
|
||||||
|
def initialize(@name : String, @value : Arg)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
"#{@name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
struct MacroCallNode < Node
|
||||||
|
property name
|
||||||
|
property args
|
||||||
|
|
||||||
|
def initialize(@name : String, @args : Array(Arg))
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
".#{@name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
struct CallNode < Node
|
||||||
|
property name
|
||||||
|
property arg
|
||||||
|
|
||||||
|
def initialize(@name : String, @arg : Arg? = nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s : String
|
||||||
|
if @arg
|
||||||
|
# ameba:disable Lint/RedundantStringCoercion
|
||||||
|
"#{@name} #{@arg.to_s}"
|
||||||
|
else
|
||||||
|
@name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
16
src/hence/utils.cr
Normal file
16
src/hence/utils.cr
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
module Hence
|
||||||
|
module Utils
|
||||||
|
extend self
|
||||||
|
|
||||||
|
macro split_uint16(x)
|
||||||
|
{
|
||||||
|
({{ x }} >> 8).to_u8,
|
||||||
|
({{ x }} & 0xff_u8).to_u8
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
macro merge_uint16(x, y)
|
||||||
|
({{ x }}.to_u16 << 8) | {{ y }}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
5
src/hence/version.cr
Normal file
5
src/hence/version.cr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
require "version_from_shard"
|
||||||
|
|
||||||
|
module Hence
|
||||||
|
VersionFromShard.declare
|
||||||
|
end
|
Loading…
Reference in a new issue