Initial commit

This commit is contained in:
nickmqb 2020-09-26 00:35:42 +02:00
commit 032919c0c1
28 changed files with 6884 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
*
!*/
!.gitignore
!.gitattributes
!*.mu
!*.args
!*.h
!*.md
!*.w
!LICENSE

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 nickmqb
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.

74
README.md Normal file
View file

@ -0,0 +1,74 @@
# Wyre
Ergonomic hardware definition language that compiles to Verilog.
## Features
* Strongly typed
* Type inference
* Inline module instantiations
* Order independent declarations
* Minimalistic syntax
* Newline as statement separator
* Names before types and compact type notation
* C-style { block syntax }
* Compact bit literals
* `match` expressions
* Large data literals (data blocks)
* Builtin functions for transforming data (e.g. slice, swizzle)
* Structs
* Column accurate compile error reporting
[Learn more about these features](docs/feature_overview.md).
## Example
What does Wyre look like? Here is a basic example design with 2 modules, a clock input, a button and 3 leds.
top(clk $1, button $1) {
out reg leds $3
posedge clk {
if button {
leds <= '111
} else {
leds <= inc(a: leds).o
}
}
}
inc(a $3) {
out o := a + 1
}
## Background
A while back I got a Lattice iCE40 FPGA to learn more about how computers work at a low level. I've had a lot of fun making designs for it in Verilog. During the process I kept track of gripes with the toolchain, and ended up with a list of mostly minor things, but nonetheless a list that had a fair amount of items. I felt that there were enough to justify building some new tooling, and Wyre is the result.
Compared to Verilog, Wyre aims to cut down on verbosity, improve design iteration speed and reduce errors (via strong typing). Wyre compiles to Verilog, so any design can be fed through an existing Verilog-based toolchain.
Some other hardware definition languages try to abstract away the hardware in some ways, with features like memory inference and various generative constructs. These higher level features are explicit non-goals for Wyre. Instead, Wyre aims to stay close to the hardware.
The Wyre compiler is written in the [Muon programming language](https://github.com/nickmqb/muon), of which I am the author and which is my main open source project. Syntactically, Wyre and Muon have a quite a bit in common. So if you like Wyre, be sure to [check out Muon](https://github.com/nickmqb/muon) too!
## Getting started
For a quick summary of features, check out [Feature overview](docs/feature_overview.md) (recommended).
For in-depth documentation, have a look at the [Language guide](docs/language_guide.md).
To learn more about how to install and use the compiler, go to [Getting started](docs/getting_started.md). Wyre works on Windows and Linux. I haven't tested it on macOS, but it will likely work.
You can also view [more examples](examples).
## Roadmap
Feedback is welcome! If you have ideas for new features or for improving existing features, let me know [by creating an issue](TODO).
## Twitter
To stay up-to-date on Wyre, consider [following me on Twitter](https://twitter.com/nickmqb).
## License
[MIT](LICENSE)

127
compiler/args_parser.mu Normal file
View file

@ -0,0 +1,127 @@
//tab_size=4
SourceFile struct #RefType {
path string
text string
}
Args struct #RefType {
sources List<SourceFile>
top string
outputPath string
indent int
maxErrors int
printVersion bool
printHelp bool
}
tryReadSourceFile(path string) {
sb := new StringBuilder{}
if !File.tryReadToStringBuilder(path, sb) {
return null
}
sb.writeChar('\0')
return new SourceFile { path: path, text: sb.compactToString() }
}
parseArgs(parser CommandLineArgsParser, isCompiler bool) {
args := new Args { sources: new List<SourceFile>{} }
token := parser.readToken()
if token == "" {
args.printHelp = true
}
hasIndent := false
hasMaxErrors := false
while token != "" {
if token.startsWith("-") {
if token == "--top" {
token = parser.readToken()
if token != "" {
args.top = token
} else {
parser.expected("module name")
}
} else if token == "--output" {
token = parser.readToken()
if token != "" {
args.outputPath = token
} else {
parser.expected("path")
}
} else if token == "--indent" {
num := parseInt(parser)
if num.hasValue {
if num.value >= 0 {
args.indent = num.value
hasIndent = true
} else {
parser.error("Expected: number, >= 0")
}
}
} else if token == "--max-errors" {
num := parseInt(parser)
if num.hasValue {
if num.value > 0 {
args.maxErrors = num.value
hasMaxErrors = true
} else {
parser.error("Expected: number, > 0")
}
}
} else if token == "--version" {
args.printVersion = true
} else if token == "--help" {
args.printHelp = true
} else {
parser.error(format("Invalid flag: {}", token))
}
} else {
sf := tryReadSourceFile(token)
if sf != null {
args.sources.add(sf)
} else {
parser.error(format("Could not read file: {}", token))
}
}
token = parser.readToken()
}
if args.printHelp || args.printVersion {
return args
}
if args.sources.count == 0 {
parser.expected("One or more source files")
}
if args.top == "" {
parser.expected("--top [module]")
}
if args.outputPath == "" && isCompiler {
parser.expected("--output [path]")
}
if !hasIndent && isCompiler {
parser.expected("--indent [number]")
}
if !hasMaxErrors {
parser.expected("--max-errors [number]")
}
return args
}
parseInt(parser CommandLineArgsParser) {
token := parser.readToken()
if token == "" {
parser.expected("number")
return Maybe<int>{}
}
val := int.tryParse(token)
if !val.hasValue {
parser.error("Expected: number")
}
return val
}

337
compiler/ast.mu Normal file
View file

@ -0,0 +1,337 @@
//tab_size=4
Token struct #RefType {
type TokenType
value string
span IntRange
outerSpan IntRange
indent int
}
TokenType enum {
identifier
numberLiteral
operator
openParen
closeParen
openBrace
closeBrace
openBracket
closeBracket
comma
end
invalid
}
Error struct {
unit CodeUnit
span IntRange
text string
at(unit CodeUnit, span IntRange, text string) {
assert(span.from <= span.to)
return Error { unit: unit, span: span, text: text }
}
atIndex(unit CodeUnit, index int, text string) {
return Error { unit: unit, span: IntRange(index, index), text: text }
}
}
Node tagged_pointer {
CodeUnit
ConstDef
StructDef
FieldDef
ModuleDef
ModuleInputDef
Block
AssignStatement
ClockStatement
IfStatement
UnaryOperatorExpression
DotExpression
BinaryOperatorExpression
TernaryOperatorExpression
MatchExpression
MatchExpressionCase
CallExpression
StructInitializerExpression
IndexExpression
NumberExpression
ParenExpression
BraceExpression
ArrayExpression
Token
hash(n Node) {
return cast(transmute(pointer_cast(n, pointer), usize) >> 3, uint)
}
}
CodeUnit struct #RefType {
path string
source string
contents List<Node>
id int
}
ConstDef struct #RefType {
colon Token
name Token
type Token
assign Token
expr Node
unit CodeUnit
// Non AST
id int
flags ConstFlags
}
ConstFlags enum #Flags {
typeCheckStarted
typeCheckDone
}
StructDef struct #RefType {
name Token
keyword Token
body Block
fields List<FieldDef>
unit CodeUnit
// Non AST
id int
symbols Map<string, FieldDef>
flags StructFlags
}
StructFlags enum #Flags {
//typeCheckStarted
typeCheckDone
}
FieldDef struct #RefType {
name Token
type Token
// Non AST
fieldIndex int
}
ModuleDef struct #RefType {
name Token
blackboxKeyword Token
openParen Token
inputs List<ModuleInputDef>
inputsContents List<Node>
closeParen Token
body Block
unit CodeUnit
// Non AST
id int
symbols Map<string, Node>
outputs List<AssignStatement>
flags ModuleFlags
numCalls int
numInputSlots int
numRegSlots int
}
ModuleFlags enum #Flags {
top
typeCheckStarted
typeCheckDone
}
ModuleInputDef struct #RefType {
name Token
type Token
// Non AST
flags ModuleInputFlags
localId int
}
ModuleInputFlags enum #Flags {
static
}
Block struct #RefType {
openBrace Token
contents List<Node>
closeBrace Token
}
ClockStatement struct #RefType {
keyword Token
name Token
body Block
}
IfStatement struct #RefType {
ifKeyword Token
expr Node
ifBody Block
elseKeyword Token
elseBranch Node // IfStatement or Block
}
AssignStatement struct #RefType {
outKeyword Token
regKeyword Token
nameExpr Node
type Token
op Token
expr Node
module ModuleDef
// Non AST
flags AssignFlags
localId int
lhsFieldIndex int
outputIndex int
}
AssignFlags enum #Flags {
reg
regUpdate
wire
static
typeCheckStarted
typeCheckDone
}
UnaryOperatorExpression struct #RefType {
op Token
expr Node
}
DotExpression struct #RefType {
lhs Node
dot Token
rhs Token
}
BinaryOperatorExpression struct #RefType {
lhs Node
op Token
rhs Node
}
TernaryOperatorExpression struct #RefType {
conditionExpr Node
question Token
trueExpr Node
colon Token
falseExpr Node
}
MatchExpression struct #RefType {
keyword Token
target Node
openBrace Token
cases List<MatchExpressionCase>
contents List<Node>
closeBrace Token
}
MatchExpressionCase struct #RefType {
valueExpr Node
colon Token
resultExpr Node
}
CallExpression struct #RefType {
target Node
openParen Token
args List<CallArg>
contents List<Node>
closeParen Token
builtin BuiltinCall
// Non-AST
callId int
calleeLocalIdToArgIndex Array<int>
}
BuiltinCall enum {
none
rep
slice
chunk
swizzle
}
CallArg struct #RefType {
name Token
colon Token
expr Node
}
StructInitializerExpression struct #RefType {
target Node
openBrace Token
args List<CallArg>
contents List<Node>
closeBrace Token
// Non-AST
fieldIndexToArgIndex Array<int>
}
ParenExpression struct #RefType {
openParen Token
expr Node
closeParen Token
}
BraceExpression struct #RefType {
openBrace Token
args List<Node>
contents List<Node>
closeBrace Token
}
IndexExpression struct #RefType {
target Node
openBracket Token
upperExpr Node
colon Token
lowerExpr Node
closeBracket Token
}
NumberExpression struct #RefType {
token Token
//valueSpan IntRange
flags NumberFlags
value ulong
dontCare ulong
width int
}
NumberFlags enum #Flags {
none = 0
valid = 1
exactWidth = 2
dontCare = 4
}
ArrayExpression struct #RefType {
openBracket Token
contents List<Node>
data List<byte>
closeBracket Token
}
Compilation struct #RefType {
sources List<SourceFile>
flags CompilationFlags
units List<CodeUnit>
symbols Map<string, Node>
entities List<Node>
typeMap Map<Node, Tag>
constMap Map<Node, Value>
errors List<Error>
nonSyntaxErrorStart int
elapsedSeconds double
}
CompilationFlags enum #Flags {
generate
simulate
}

View file

@ -0,0 +1,104 @@
//tab_size=4
CommandLineArgsParser struct #RefType {
index int
args Array<string>
errors List<CommandLineArgsParserError>
}
CommandLineArgsParserError struct {
index int
innerSpan IntRange
text string
}
CommandLineInfo struct {
text string
argSpans Array<IntRange>
}
CommandLineArgsParser {
from(args Array<string>, errors List<CommandLineArgsParserError>) {
assert(args.count >= 1)
return CommandLineArgsParser { index: 1, args: args, errors: errors }
}
readToken(self CommandLineArgsParser) {
if self.index >= self.args.count {
return ""
}
result := self.args[self.index]
self.index += 1
return result
}
expected(self CommandLineArgsParser, text string) {
self.errors.add(CommandLineArgsParserError { index: self.index, text: format("Expected: {}", text) })
}
error(self CommandLineArgsParser, text string) {
arg := self.args[self.index - 1]
self.errors.add(CommandLineArgsParserError { index: self.index - 1, innerSpan: IntRange(0, arg.length), text: text })
}
getCommandLineInfo(self CommandLineArgsParser) {
result := CommandLineInfo { argSpans: new Array<IntRange>(self.args.count) }
sb := new StringBuilder{}
insertSep := false
for i := 0; i < self.args.count {
if insertSep {
sb.write(" ")
} else {
insertSep = true
}
a := self.args[i]
from := sb.count
delta := 0
if a.indexOfChar(' ') >= 0 {
sb.write("\"")
sb.write(a)
sb.write("\"")
delta = 1
} else {
sb.write(a)
}
result.argSpans[i] = IntRange(from + delta, sb.count - delta)
}
result.text = sb.toString()
return result
}
getNumColumns_(s string, tabSize int) {
cols := 0
for i := 0; i < s.length {
if s[i] == '\t' {
cols += tabSize
} else {
cols += 1
}
}
return cols
}
getErrorDesc(e CommandLineArgsParserError, info CommandLineInfo) {
span := IntRange{}
extraSpaces := 0
if e.index >= info.argSpans.count {
at := info.argSpans[info.argSpans.count - 1].to
span = IntRange(at, at)
extraSpaces = 1
} else {
offset := info.argSpans[e.index].from
span = IntRange(offset + e.innerSpan.from, offset + e.innerSpan.to)
}
indent := getNumColumns_(info.text.slice(0, span.from), 4) + extraSpaces
width := getNumColumns_(info.text.slice(span.from, span.to), 4) + extraSpaces
return format("{}\n{}\n{}{}",
e.text,
info.text.replace("\t", " "),
string.repeatChar(' ', indent),
string.repeatChar('~', max(1, width)))
}
}

17
compiler/crash_handler.mu Normal file
View file

@ -0,0 +1,17 @@
:crashAllocator IAllocator #Mutable
CrashHandler {
enable() {
::crashAllocator = Memory.newArenaAllocator(64 * 1024)
signal(SIGSEGV, pointer_cast(crashHandler, pointer))
}
crashHandler(sig int) {
::currentAllocator = ::crashAllocator
prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0)
system("xfce4-terminal --hold -x gdb -tui -p $PPID")
while true {
usleep(1000_u * 1000_u)
}
}
}

7
compiler/cstdlib.mu Normal file
View file

@ -0,0 +1,7 @@
exit(__status int) void #Foreign("exit")
CrashHandler {
enable() {
// Dummy
}
}

609
compiler/emulator.mu Normal file
View file

@ -0,0 +1,609 @@
ModuleInstance struct #RefType {
def ModuleDef
fullName string
localName string
localState Array<int> // localId -> stateId
calls Array<int> // callId -> moduleInstanceId
caller ModuleInstance
callExpr CallExpression
genGlobalName string
genLocalName string
genLocalsSet Set<string>
genLocals Map<NodeWithCtx, string>
}
SlotInfo struct {
name string
tag Tag
inst ModuleInstance
node Node
field int
isStatic bool
isReg bool
}
EmulatorState struct #RefType {
comp Compilation
symbols Map<string, Node>
typeMap Map<Node, Tag>
constMap Map<Node, Value>
entities List<Node>
errors List<Error>
moduleInstances List<ModuleInstance>
rs List<Value>
ws List<Value>
infos List<SlotInfo>
evalOrder List<int>
nextId Map<Node, int>
started Array<bool>
done Array<bool>
inst ModuleInstance
evalCtxField int
evalCtxOutput int
tape List<Instruction>
resetProgram List<Instruction>
stepProgram List<Instruction>
stack List<Value>
cycle long
}
Emulator {
init(comp Compilation, top ModuleDef) {
s := new EmulatorState {
comp: comp,
symbols: comp.symbols,
typeMap: comp.typeMap,
constMap: comp.constMap,
entities: comp.entities,
errors: comp.errors,
moduleInstances: new List<ModuleInstance>{},
nextId: new Map.create<Node, int>(),
rs: new List<Value>{},
ws: new List<Value>{},
infos: new List<SlotInfo>{},
evalOrder: new List<int>{},
evalCtxField: -1,
evalCtxOutput: -1,
resetProgram: new List<Instruction>{},
stepProgram: new List<Instruction>{},
stack: new List<Value>{},
cycle: -1,
}
EmulatorAllocator.top(s, top)
commitValues(s)
EmulatorOrderCalculator.comp(s)
if s.errors.count > 0 {
return s
}
s.tape = s.resetProgram
for si in s.evalOrder {
if s.infos[si].isStatic || s.infos[si].isReg {
EmulatorStep.slot(s, si)
}
}
if s.comp.flags & CompilationFlags.simulate != 0 {
s.tape = s.stepProgram
for si in s.evalOrder {
if !s.infos[si].isStatic && !s.infos[si].isReg {
EmulatorStep.slot(s, si)
}
}
for mi in s.moduleInstances {
EmulatorStep.module(s, mi)
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
}
}
return s
}
reset(s EmulatorState) {
EmulatorRunner.run(s, s.resetProgram)
Emulator.commitValues(s)
}
step(s EmulatorState, clk string, val ulong) {
EmulatorRunner.setInput(s, s.moduleInstances[0], clk, val)
EmulatorRunner.run(s, s.stepProgram)
}
commitValues(s EmulatorState) {
for i := 0; i < s.ws.count {
s.rs[i] = s.ws[i]
}
}
}
EmulatorStep {
module(s EmulatorState, inst ModuleInstance) {
s.inst = inst
block(s, inst.def.body)
}
block(s EmulatorState, block Block) {
for n in block.contents {
match n {
ClockStatement: clock(s, n)
IfStatement: if_(s, n)
AssignStatement: assign(s, n)
}
}
}
clock(s EmulatorState, st ClockStatement) {
token(s, st.name) // TODO: does not actually check edges, just looks at current state
if st.keyword.value != "posedge" {
emit(s, Opcode.invert)
}
pc := emit(s, Opcode.jumpIfZero)
block(s, st.body)
patch(s, pc)
}
if_(s EmulatorState, st IfStatement) {
expression(s, st.expr)
pc := emit(s, Opcode.jumpIfZero)
block(s, st.ifBody)
if st.elseBranch.is(IfStatement) {
ifEnd := emit(s, Opcode.jump)
patch(s, pc)
if_(s, st.elseBranch.as(IfStatement))
patch(s, ifEnd)
} else if st.elseBranch.is(Block) {
ifEnd := emit(s, Opcode.jump)
patch(s, pc)
block(s, st.elseBranch.as(Block))
patch(s, ifEnd)
} else {
patch(s, pc)
}
}
assign(s EmulatorState, st AssignStatement) {
if st.flags & AssignFlags.regUpdate != 0 {
si := s.inst.localState[st.localId]
if st.nameExpr.is(Token) {
tag := s.typeMap.get(st.expr)
if tag.kind == TagKind.number {
expression(s, st.expr)
emiti(s, Opcode.store, si)
} else if tag.kind == TagKind.struct_ {
def := s.entities[tag.q].as(StructDef)
for f, i in def.fields {
s.evalCtxField = i
expression(s, st.expr)
emiti(s, Opcode.store, si + i)
}
s.evalCtxField = -1
} else {
abandon()
}
} else if st.nameExpr.is(DotExpression) {
tag := s.typeMap.get(st.expr)
if tag.kind == TagKind.number {
expression(s, st.expr)
emiti(s, Opcode.store, si + st.lhsFieldIndex)
} else {
abandon()
}
} else if st.nameExpr.is(CallExpression) {
call := st.nameExpr.as(CallExpression)
if call.builtin == BuiltinCall.slice {
assignSlice(s, st, call, si)
} else {
abandon()
}
} else {
abandon()
}
}
}
slot(s EmulatorState, si int) {
info := s.infos[si]
node := info.node
s.evalCtxField = info.field
match node {
ModuleInputDef: {
caller := info.inst.caller
e := info.inst.callExpr
s.inst = caller
expression(s, e.args[e.calleeLocalIdToArgIndex[node.localId]].expr)
}
AssignStatement: {
s.inst = info.inst
expression(s, node.expr)
}
}
emiti(s, Opcode.store, si)
}
pushValue(s EmulatorState, val Value) {
if val.kind == ValueKind.ulong_ {
emitz(s, Opcode.push, val.z)
} else if val.kind == ValueKind.byteArray {
emitz(s, Opcode.pushArray, val.z)
} else {
abandon()
}
}
expression(s EmulatorState, e Node) {
val := s.constMap.getOrDefault(e)
if val.kind != ValueKind.none {
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
pushValue(s, val)
} else {
expressionInner(s, e)
}
}
expressionInner(s EmulatorState, e Node) {
match e {
Token: token(s, e)
NumberExpression: number(s, e)
UnaryOperatorExpression: unaryOperator(s, e)
BinaryOperatorExpression: binaryOperator(s, e)
DotExpression: dot(s, e)
TernaryOperatorExpression: ternaryOperator(s, e)
MatchExpression: match_(s, e)
ParenExpression: paren(s, e)
IndexExpression: index(s, e)
CallExpression: call(s, e)
StructInitializerExpression: structInit(s, e)
BraceExpression: brace(s, e)
}
}
token(s EmulatorState, e Token) {
name := e.value
node := s.inst != null ? s.inst.def.symbols.getOrDefault(name) : null
match node {
ModuleInputDef: {
si := s.inst.localState[node.localId] + max(s.evalCtxField, 0)
emiti(s, Opcode.load, si)
}
AssignStatement: {
si := s.inst.localState[node.localId] + max(s.evalCtxField, 0)
if si >= 0 {
emiti(s, Opcode.load, si)
} else {
expression(s, node.expr)
}
}
default: {
node = s.symbols.getOrDefault(name)
match node {
ConstDef: {
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
pushValue(s, s.constMap.get(node.expr))
}
}
}
}
}
number(s EmulatorState, e NumberExpression) {
emitz(s, Opcode.push, e.value)
}
unaryOperator(s EmulatorState, e UnaryOperatorExpression) {
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
expression(s, e.expr)
op := e.op.value
if op == "-" {
emit(s, Opcode.neg)
mask(s, s.typeMap.get(e))
} else if op == "~" {
emit(s, Opcode.invert)
mask(s, s.typeMap.get(e))
} else if op == "zx" {
// OK
} else {
abandon()
}
}
binaryOperator(s EmulatorState, e BinaryOperatorExpression) {
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
expression(s, e.lhs)
expression(s, e.rhs)
op := e.op.value
if op == "+" {
emit(s, Opcode.add)
} else if op == "-" {
emit(s, Opcode.sub)
} else if op == "&" {
emit(s, Opcode.and)
} else if op == "|" {
emit(s, Opcode.or)
} else if op == "^" {
emit(s, Opcode.xor)
} else if op == "*" {
emit(s, Opcode.mul)
} else if op == "==" {
emit(s, Opcode.eq)
} else if op == "!=" {
emit(s, Opcode.neq)
} else if op == "<=" {
emit(s, Opcode.lte)
} else if op == "<" {
emit(s, Opcode.lt)
} else if op == ">=" {
emit(s, Opcode.gte)
} else if op == ">" {
emit(s, Opcode.gt)
} else if op == "<<" {
emit(s, Opcode.shl)
} else if op == ">>" {
emit(s, Opcode.shr)
} else {
abandon()
}
mask(s, s.typeMap.get(e))
}
dot(s EmulatorState, e DotExpression) {
tag := s.typeMap.get(e.lhs)
if tag.kind == TagKind.struct_ {
assert(s.evalCtxField == -1)
def := s.entities[tag.q].as(StructDef)
s.evalCtxField = def.symbols.get(e.rhs.value).fieldIndex
expression(s, e.lhs)
s.evalCtxField = -1
} else if tag.kind == TagKind.moduleOut {
assert(s.evalCtxOutput == -1)
def := s.entities[tag.q].as(ModuleDef)
s.evalCtxOutput = def.symbols.get(e.rhs.value).as(AssignStatement).outputIndex
expression(s, e.lhs)
s.evalCtxOutput = -1
} else {
abandon()
}
}
ternaryOperator(s EmulatorState, e TernaryOperatorExpression) {
expression(s, e.conditionExpr)
pc := emit(s, Opcode.jumpIfZero)
expression(s, e.trueExpr)
trueEnd := emit(s, Opcode.jump)
patch(s, pc)
expression(s, e.falseExpr)
patch(s, trueEnd)
}
match_(s EmulatorState, e MatchExpression) {
jumps := new List<int>{}
expression(s, e.target)
for c in e.cases {
emit(s, Opcode.dup)
expression(s, c.valueExpr)
emit(s, Opcode.eq)
next := emit(s, Opcode.jumpIfZero)
emit(s, Opcode.discard)
expression(s, c.resultExpr)
jumps.add(emit(s, Opcode.jump))
patch(s, next)
}
emit(s, Opcode.discard)
emit(s, Opcode.push)
for j in jumps {
patch(s, j)
}
}
paren(s EmulatorState, e ParenExpression) {
expression(s, e.expr)
}
index(s EmulatorState, e IndexExpression) {
upper := s.constMap.get(e.upperExpr).z
lower := e.lowerExpr != null ? s.constMap.get(e.lowerExpr).z : upper
expression(s, e.target)
bits := (upper - lower + 1)
if bits < 64 {
emitz(s, Opcode.index, (lower << 32) | (bits & 0xffffffff_uL))
} else {
assert(upper == 63 && lower == 0)
}
}
call(s EmulatorState, e CallExpression) {
if e.builtin == BuiltinCall.rep {
rep(s, e)
return
} else if e.builtin == BuiltinCall.slice {
slice(s, e)
return
} else if e.builtin == BuiltinCall.chunk {
chunk(s, e)
return
} else if e.builtin == BuiltinCall.swizzle {
swizzle(s, e)
return
}
assert(e.builtin == BuiltinCall.none)
assert(s.evalCtxOutput >= 0)
target := s.moduleInstances[s.inst.calls[e.callId]]
assert(target.def.blackboxKeyword == null)
output := target.def.outputs[s.evalCtxOutput]
si := target.localState[output.localId] + max(s.evalCtxField, 0)
emiti(s, Opcode.load, si)
}
rep(s EmulatorState, e CallExpression) {
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
tag := s.typeMap.get(e.args[0].expr)
assert(tag.kind == TagKind.number && tag.q > 0)
n := TypeChecker.unpackInt(s.constMap.get(e.args[1].expr))
expression(s, e.args[0].expr)
for i := 1; i < n {
emit(s, Opcode.dup)
emiti(s, Opcode.shlOr, tag.q)
}
}
slice(s EmulatorState, e CallExpression) {
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
dest := s.typeMap.get(e.args[0].expr)
expression(s, e.args[0].expr)
expression(s, e.args[1].expr)
expression(s, e.args[2].expr)
emiti(s, Opcode.slice, TypeChecker.unpackWidth(dest))
}
assignSlice(s EmulatorState, st AssignStatement, e CallExpression, si int) {
expression(s, e.args[1].expr)
expression(s, e.args[2].expr)
expression(s, st.expr)
emiti(s, Opcode.storeSlice, si)
}
chunk(s EmulatorState, e CallExpression) {
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
target := s.typeMap.get(e.args[0].expr)
targetSize := TypeChecker.unpackWidth(target)
index := TypeChecker.unpackInt(s.constMap.get(e.args[1].expr))
numChunks := TypeChecker.unpackInt(s.constMap.get(e.args[2].expr))
assert(targetSize % numChunks == 0)
chunkSize := targetSize / numChunks
offset := index * chunkSize
expression(s, e.args[0].expr)
emiti(s, Opcode.push, offset)
emiti(s, Opcode.push, chunkSize)
emiti(s, Opcode.slice, targetSize)
}
swizzle(s EmulatorState, e CallExpression) {
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
targetWidth := TypeChecker.unpackWidth(s.typeMap.get(e.args[0].expr))
seqSize := TypeChecker.unpackInt(s.constMap.get(e.args[1].expr))
readStep := TypeChecker.unpackInt(s.constMap.get(e.args[2].expr))
blockSize := TypeChecker.unpackInt(s.constMap.get(e.args[3].expr))
expression(s, e.args[0].expr)
emiti(s, Opcode.push, targetWidth)
emiti(s, Opcode.push, seqSize)
emiti(s, Opcode.push, readStep)
emiti(s, Opcode.push, blockSize)
emit(s, Opcode.swizzle)
}
structInit(s EmulatorState, e StructInitializerExpression) {
assert(s.evalCtxField >= 0)
tag := s.typeMap.get(e)
assert(tag.kind == TagKind.struct_)
def := s.entities[tag.q].as(StructDef)
argIndex := e.fieldIndexToArgIndex[s.evalCtxField]
if argIndex >= 0 {
arg := e.args[argIndex].expr
prevCtxField := s.evalCtxField
s.evalCtxField = -1
expression(s, arg)
s.evalCtxField = prevCtxField
} else {
emit(s, Opcode.push)
}
}
brace(s EmulatorState, e BraceExpression) {
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
for arg, i in e.args {
tag := s.typeMap.get(arg)
assert(tag.kind == TagKind.number && tag.q > 0)
expression(s, arg)
if i > 0 {
emiti(s, Opcode.shlOr, tag.q)
}
}
}
emit(s EmulatorState, op Opcode) {
index := s.tape.count
s.tape.add(Instruction { op: op })
return index
}
emiti(s EmulatorState, op Opcode, z int) {
index := s.tape.count
s.tape.add(Instruction { op: op, z: checked_cast(z, ulong) })
return index
}
emitz(s EmulatorState, op Opcode, z ulong) {
index := s.tape.count
s.tape.add(Instruction { op: op, z: z })
return index
}
patch(s EmulatorState, index int) {
op := s.tape[index].op
assert(op == Opcode.jump || op == Opcode.jumpIfZero)
s.tape[index].z = checked_cast(s.tape.count - index - 1, ulong)
}
mask(s EmulatorState, tag Tag) {
assert(tag.kind == TagKind.number && tag.q <= 64)
if tag.q > 0 && tag.q < 64 {
emitz(s, Opcode.mask, (1_uL << tag.q) - 1)
}
}
}
Util {
writeByteHexTo(b byte, sb StringBuilder) {
if b < 16 {
sb.write("0")
}
ulong.writeHexTo(b, sb)
}
valEquals(a Value, b Value) {
if a.kind != b.kind {
return false
}
if a.kind == ValueKind.ulong_ {
return a.z == b.z
} else if a.kind == ValueKind.byteArray {
return a.z == b.z || arrayEquals(EmulatorRunner.unpackArray(a), EmulatorRunner.unpackArray(b))
} else {
abandon()
}
}
arrayEquals(a Array<T>, b Array<T>) {
if a.count != b.count {
return false
}
for i := 0; i < a.count {
if a[i] != b[i] {
return false
}
}
return true
}
shlArray(a Array<byte>, n int) {
assert(0 <= n && n < 8)
if n == 0 {
return
}
m := 8 - n
for i := 0; i < a.count - 1 {
a[i] = cast((a[i] >> n) | (a[i + 1] << m), byte)
}
a[a.count - 1] = cast(a[a.count - 1] >> n, byte)
}
}

511
compiler/emulator_init.mu Normal file
View file

@ -0,0 +1,511 @@
EmulatorAllocator {
top(s EmulatorState, def ModuleDef) {
for i := 0; i < def.numRegSlots {
s.rs.add(Value{})
s.infos.add(SlotInfo{})
}
module(s, def, "")
assert(s.ws.count == def.numRegSlots)
}
module(s EmulatorState, def ModuleDef, name string) int {
fullName := s.inst != null ? formatName(s.inst.fullName, name) : name
s.inst = new ModuleInstance {
def: def,
localName: name,
fullName: fullName,
localState: new Array<int>(def.symbols.count + 1),
calls: new Array<int>(def.numCalls + 1),
}
for i := 0; i <= def.symbols.count {
s.inst.localState[i] = -1
}
id := s.moduleInstances.count
s.moduleInstances.add(s.inst)
for inp in def.inputs {
s.inst.localState[inp.localId] = s.rs.count
tag := s.typeMap.get(inp)
isStatic := inp.flags & ModuleInputFlags.static != 0
if tag.kind == TagKind.number {
s.rs.add(createValue(s, tag, true))
s.infos.add(SlotInfo { name: formatName(s.inst.fullName, inp.name.value), tag: tag, inst: s.inst, node: inp, field: -1, isStatic: isStatic })
} else if tag.kind == TagKind.struct_ {
struct_ := s.entities[tag.q].as(StructDef)
for f, fi in struct_.fields {
s.rs.add(Value { kind: ValueKind.ulong_ })
fname := formatName(formatName(s.inst.fullName, inp.name.value), f.name.value)
ftag := s.typeMap.get(f)
s.infos.add(SlotInfo { name: fname, tag: ftag, inst: s.inst, node: inp, field: fi })
}
} else {
abandon()
}
}
block(s, def.body)
return id
}
block(s EmulatorState, st Block) {
for n in st.contents {
match n {
ClockStatement: block(s, n.body)
IfStatement: if_(s, n)
AssignStatement: assign(s, n)
}
}
}
if_(s EmulatorState, st IfStatement) {
expression(s, st.expr)
block(s, st.ifBody)
if st.elseBranch.is(IfStatement) {
if_(s, st.elseBranch.as(IfStatement))
} else if st.elseBranch.is(Block) {
block(s, st.elseBranch.as(Block))
}
}
assign(s EmulatorState, st AssignStatement) {
if st.flags & AssignFlags.reg != 0 {
index := s.ws.count
s.inst.localState[st.localId] = index
tag := s.typeMap.get(st)
if tag.kind == TagKind.number {
s.ws.add(createValue(s, tag, true))
s.infos[index] = SlotInfo { name: formatName(s.inst.fullName, st.nameExpr.as(Token).value), tag: tag, inst: s.inst, node: st, field: -1, isReg: true }
} else if tag.kind == TagKind.struct_ {
def := s.entities[tag.q].as(StructDef)
for f, fi in def.fields {
s.ws.add(Value { kind: ValueKind.ulong_ })
fname := formatName(formatName(s.inst.fullName, st.nameExpr.as(Token).value), f.name.value)
ftag := s.typeMap.get(f)
s.infos[index] = SlotInfo { name: fname, tag: ftag, inst: s.inst, node: st, field: fi, isReg: true }
index += 1
}
} else {
abandon()
}
} else if st.flags & AssignFlags.wire != 0 {
tag := s.typeMap.get(st)
isStatic := st.flags & AssignFlags.static != 0
if tag.kind == TagKind.number {
s.inst.localState[st.localId] = s.rs.count
s.rs.add(createValue(s, tag, true))
s.infos.add(SlotInfo { name: formatName(s.inst.fullName, st.nameExpr.as(Token).value), tag: tag, inst: s.inst, node: st, field: -1, isStatic: isStatic })
} else if tag.kind == TagKind.struct_ {
s.inst.localState[st.localId] = s.rs.count
def := s.entities[tag.q].as(StructDef)
for f, fi in def.fields {
s.rs.add(Value { kind: ValueKind.ulong_ })
fname := formatName(formatName(s.inst.fullName, st.nameExpr.as(Token).value), f.name.value)
ftag := s.typeMap.get(f)
s.infos.add(SlotInfo { name: fname, tag: ftag, inst: s.inst, node: st, field: fi, isStatic: isStatic })
}
} else if tag.kind == TagKind.moduleOut {
// OK
} else {
abandon()
}
}
if st.nameExpr.is(Token) && st.expr.is(CallExpression) {
// Special case to assign better name to module instance
call(s, st.expr.as(CallExpression), st.nameExpr.as(Token).value)
} else if st.expr != null {
expression(s, st.expr)
}
}
createValue(s EmulatorState, tag Tag, allocArray bool) {
if tag.kind == TagKind.number && tag.q > 64 {
return Value { kind: ValueKind.byteArray, z: allocArray ? transmute(new Array<byte>((tag.q + 7) / 8), ulong) : 0 }
} else if tag.kind == TagKind.number {
return Value { kind: ValueKind.ulong_ }
} else {
abandon()
}
}
expression(s EmulatorState, e Node) {
match e {
Token: {}
NumberExpression: {}
UnaryOperatorExpression: {
expression(s, e.expr)
}
BinaryOperatorExpression: {
expression(s, e.lhs)
expression(s, e.rhs)
}
DotExpression: {
expression(s, e.lhs)
expression(s, e.rhs)
}
TernaryOperatorExpression: {
expression(s, e.conditionExpr)
expression(s, e.trueExpr)
expression(s, e.falseExpr)
}
MatchExpression: {
expression(s, e.target)
for c in e.cases {
expression(s, c.valueExpr)
expression(s, c.resultExpr)
}
}
ParenExpression: {
expression(s, e.expr)
}
IndexExpression: {
expression(s, e.target)
expression(s, e.upperExpr)
expression(s, e.lowerExpr)
}
BraceExpression: {
for arg in e.args {
expression(s, arg)
}
}
CallExpression: {
call(s, e, "")
}
StructInitializerExpression: {
for arg in e.args {
expression(s, arg.expr)
}
}
ArrayExpression: {}
null: {}
}
}
call(s EmulatorState, e CallExpression, name string) {
for arg in e.args {
if arg.expr != null {
expression(s, arg.expr)
}
}
if e.builtin != BuiltinCall.none {
return
}
def := s.symbols.get(e.target.as(Token).value).as(ModuleDef)
if name == "" {
id := s.nextId.getOrDefault(def)
s.nextId.addOrUpdate(def, id + 1)
name = format("{}_{}", def.name.value, id)
assert(s.symbols.getOrDefault(name) == null) // TODO: error message
assert(s.inst.def.symbols.getOrDefault(name) == null) // TODO: error message
}
prevInst := s.inst
instanceId := module(s, def, name)
s.inst.caller = prevInst
s.inst.callExpr = e
s.inst = prevInst
s.inst.calls[e.callId] = instanceId
}
formatName(a string, b string) {
if a == "" {
return b
}
return format("{}.{}", a, b)
}
}
EmulatorOrderCalculator {
comp(s EmulatorState) {
s.started = new Array<bool>(s.rs.count)
s.done = new Array<bool>(s.rs.count)
top := s.moduleInstances[0]
for inp in top.def.inputs {
si := top.localState[inp.localId]
tag := s.infos[si].tag
if tag.kind == TagKind.number {
s.started[si] = true
s.done[si] = true
} else if tag.kind == TagKind.struct_ {
struct_ := s.entities[tag.q].as(StructDef)
for f, fi in struct_.fields {
s.started[si + fi] = true
s.done[si + fi] = true
}
}
}
for mi in s.moduleInstances {
module(s, mi)
}
}
module(s EmulatorState, inst ModuleInstance) {
s.inst = inst
if inst.def.blackboxKeyword == null {
block(s, inst.def.body)
} else {
for inp in inst.def.inputs {
input(s, inp)
}
}
}
block(s EmulatorState, st Block) {
for n in st.contents {
match n {
ClockStatement: block(s, n.body)
IfStatement: if_(s, n)
AssignStatement: assign(s, n)
}
}
}
if_(s EmulatorState, st IfStatement) {
expression(s, st.expr)
block(s, st.ifBody)
if st.elseBranch.is(IfStatement) {
if_(s, st.elseBranch.as(IfStatement))
} else if st.elseBranch.is(Block) {
block(s, st.elseBranch.as(Block))
}
}
assign(s EmulatorState, st AssignStatement) {
if st.flags & AssignFlags.reg != 0 && st.flags & AssignFlags.regUpdate == 0 && st.expr != null {
tag := s.typeMap.get(st)
if tag.kind == TagKind.number {
expression(s, st.expr)
s.evalOrder.add(s.inst.localState[st.localId])
} else if tag.kind == TagKind.struct_ {
def := s.entities[tag.q].as(StructDef)
for f, fi in def.fields {
s.evalCtxField = fi
expression(s, st.expr)
s.evalOrder.add(s.inst.localState[st.localId] + fi)
}
s.evalCtxField = -1
} else {
abandon()
}
}
if st.flags & AssignFlags.wire != 0 && st.outKeyword != null && s.inst == s.moduleInstances[0] {
tag := s.typeMap.get(st)
if tag.kind == TagKind.number {
wire(s, st)
} else if tag.kind == TagKind.struct_ {
def := s.entities[tag.q].as(StructDef)
for f, i in def.fields {
s.evalCtxField = i
wire(s, st)
}
s.evalCtxField = -1
} else if tag.kind == TagKind.moduleOut {
// OK
} else {
abandon()
}
}
if st.flags & AssignFlags.regUpdate != 0 {
si := s.inst.localState[st.localId]
if st.nameExpr.is(Token) {
tag := s.typeMap.get(st.expr)
if tag.kind == TagKind.number {
expression(s, st.expr)
} else if tag.kind == TagKind.struct_ {
def := s.entities[tag.q].as(StructDef)
for f, i in def.fields {
s.evalCtxField = i
expression(s, st.expr)
}
s.evalCtxField = -1
} else {
abandon()
}
} else if st.nameExpr.is(DotExpression) {
tag := s.typeMap.get(st.expr)
if tag.kind == TagKind.number {
expression(s, st.expr)
} else {
abandon()
}
} else if st.nameExpr.is(CallExpression) {
call := st.nameExpr.as(CallExpression)
if call.builtin == BuiltinCall.slice {
expression(s, st.expr)
} else {
abandon()
}
} else {
abandon()
}
}
}
expression(s EmulatorState, e Node) {
match e {
Token: token(s, e)
NumberExpression: {}
UnaryOperatorExpression: {
expression(s, e.expr)
}
BinaryOperatorExpression: {
expression(s, e.lhs)
expression(s, e.rhs)
}
DotExpression: dot(s, e)
TernaryOperatorExpression: {
expression(s, e.conditionExpr)
expression(s, e.trueExpr)
expression(s, e.falseExpr)
}
MatchExpression: {
expression(s, e.target)
for c in e.cases {
expression(s, c.valueExpr)
expression(s, c.resultExpr)
}
}
ParenExpression: {
expression(s, e.expr)
}
IndexExpression: {
expression(s, e.target)
expression(s, e.upperExpr)
expression(s, e.lowerExpr)
}
BraceExpression: {
for arg in e.args {
expression(s, arg)
}
}
CallExpression: call(s, e)
StructInitializerExpression: structInit(s, e)
ArrayExpression: {}
null: {}
}
}
input(s EmulatorState, inp ModuleInputDef) {
si := s.inst.localState[inp.localId] + max(s.evalCtxField, 0)
if s.done[si] {
return
}
if !s.started[si] {
caller := s.inst.caller
callExpr := s.inst.callExpr
s.started[si] = true
prevInst := s.inst
s.inst = caller
expression(s, callExpr.args[callExpr.calleeLocalIdToArgIndex[inp.localId]].expr)
s.evalOrder.add(si)
s.inst = prevInst
s.done[si] = true
} else {
logicLoop(s, inp.name)
}
}
wire(s EmulatorState, st AssignStatement) {
si := s.inst.localState[st.localId]
if si == -1 {
expression(s, st.expr)
return
}
si += max(s.evalCtxField, 0)
if s.done[si] {
return
} else if !s.started[si] {
s.started[si] = true
expression(s, st.expr)
s.evalOrder.add(si)
s.done[si] = true
} else {
logicLoop(s, st.nameExpr)
}
}
assignRef(s EmulatorState, st AssignStatement) {
si := s.inst.localState[st.localId]
if st.flags & AssignFlags.reg != 0 {
assert(s.evalCtxOutput == -1)
} else if st.flags & AssignFlags.wire != 0 {
wire(s, st)
} else {
abandon()
}
}
token(s EmulatorState, e Token) {
name := e.value
node := s.inst != null ? s.inst.def.symbols.getOrDefault(name) : null
match node {
ModuleInputDef: {
input(s, node)
}
AssignStatement: {
assignRef(s, node)
}
null: {}
}
}
dot(s EmulatorState, e DotExpression) {
tag := s.typeMap.get(e.lhs)
if tag.kind == TagKind.struct_ {
assert(s.evalCtxField == -1)
def := s.entities[tag.q].as(StructDef)
s.evalCtxField = def.symbols.get(e.rhs.value).fieldIndex
expression(s, e.lhs)
s.evalCtxField = -1
} else if tag.kind == TagKind.moduleOut {
assert(s.evalCtxOutput == -1)
def := s.entities[tag.q].as(ModuleDef)
s.evalCtxOutput = def.symbols.get(e.rhs.value).as(AssignStatement).outputIndex
expression(s, e.lhs)
s.evalCtxOutput = -1
} else {
abandon()
}
}
call(s EmulatorState, e CallExpression) {
if e.builtin != BuiltinCall.none {
assert(s.evalCtxOutput == -1)
for arg in e.args {
expression(s, arg.expr)
}
return
}
assert(s.evalCtxOutput >= 0)
target := s.moduleInstances[s.inst.calls[e.callId]]
if target.def.blackboxKeyword != null {
return
}
output := target.def.outputs[s.evalCtxOutput]
prevInst := s.inst
prevCtxOutput := s.evalCtxOutput
s.inst = target
s.evalCtxOutput = -1
assignRef(s, output)
s.inst = prevInst
s.evalCtxOutput = prevCtxOutput
}
structInit(s EmulatorState, e StructInitializerExpression) {
assert(s.evalCtxField >= 0)
tag := s.typeMap.get(e)
assert(tag.kind == TagKind.struct_)
def := s.entities[tag.q].as(StructDef)
argIndex := e.fieldIndexToArgIndex[s.evalCtxField]
if argIndex >= 0 {
arg := e.args[argIndex].expr
prevCtxField := s.evalCtxField
s.evalCtxField = -1
expression(s, arg)
s.evalCtxField = prevCtxField
}
}
logicLoop(s EmulatorState, e Node) {
s.errors.add(Error.at(s.inst.def.unit, RangeFinder.find(e), "Logic loop detected"))
}
}

82
compiler/error_helper.mu Normal file
View file

@ -0,0 +1,82 @@
//tab_size=4
LocationInfo struct {
line int
span IntRange
columnSpan IntRange
lineText string
}
ErrorHelper {
// TODO PERF: This is O(N), so it's not ideal to call this from within a loop.
spanToLocationInfo(source string, span IntRange) {
assert(span.from <= span.to)
from := span.from
lines := 0
lineStart := 0
i := 0
while i < from {
ch := source[i]
if ch == '\n' {
lines += 1
lineStart = i + 1
}
i += 1
}
i = from
lineEnd := 0
while true {
ch := source[i]
if ch == '\n' || ch == '\r' || ch == '\0' {
lineEnd = i
break
}
i += 1
}
to := min(span.to, lineEnd)
return LocationInfo {
line: lines + 1,
span: IntRange(from, to),
columnSpan: IntRange(from - lineStart, to - lineStart),
lineText: source.slice(lineStart, lineEnd)
}
}
getNumColumns(s string, tabSize int) {
cols := 0
for i := 0; i < s.length {
if s[i] == '\t' {
cols += tabSize
} else {
cols += 1
}
}
return cols
}
getErrorDesc(path string, source string, span IntRange, text string) {
li := spanToLocationInfo(source, span)
indent := getNumColumns(li.lineText.slice(0, li.columnSpan.from), 4)
width := getNumColumns(li.lineText.slice(li.columnSpan.from, li.columnSpan.to), 4)
pathInfo := path != "" ? format("\n-> {}:{}", path, li.line) : ""
return format("{}{}\n{}\n{}{}",
text,
pathInfo,
li.lineText.replace("\t", " "),
string.repeatChar(' ', indent),
string.repeatChar('~', max(1, width)))
}
compareErrors(a Error, b Error) {
if a.unit == null {
return -1
}
if b.unit == null {
return 1
}
if a.unit.id != b.unit.id {
return int.compare(a.unit.id, b.unit.id)
}
return int.compare(a.span.from, b.span.from)
}
}

8
compiler/external.h Normal file
View file

@ -0,0 +1,8 @@
#include <stdint.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <signal.h>

123
compiler/indexer.mu Normal file
View file

@ -0,0 +1,123 @@
IndexerState struct #RefType {
comp Compilation
unit CodeUnit
module ModuleDef
}
Indexer {
// Global pass to construct symbol tables
comp(comp Compilation) {
comp.symbols = new Map.create<string, Node>()
comp.entities = new List<Node>{}
comp.nonSyntaxErrorStart = comp.errors.count
s := new IndexerState {
comp: comp,
}
for u in comp.units {
s.unit = u
unit(s, u)
}
}
unit(s IndexerState, unit CodeUnit) {
comp := s.comp
for n in unit.contents {
match n {
ConstDef: {
if n.name != null {
name := n.name.value
if comp.symbols.tryAdd(name, n) {
n.id = comp.entities.count
comp.entities.add(n)
} else {
duplicateSymbol(s, n.name)
}
}
}
StructDef: {
name := n.name.value
if comp.symbols.tryAdd(name, n) {
n.id = comp.entities.count
comp.entities.add(n)
} else {
duplicateSymbol(s, n.name)
}
}
ModuleDef: {
name := n.name.value
if comp.symbols.tryAdd(name, n) {
n.id = comp.entities.count
comp.entities.add(n)
} else {
duplicateSymbol(s, n.name)
}
s.module = n
n.symbols = new Map.create<string, Node>()
n.outputs = new List<AssignStatement>{}
for input in n.inputs {
if s.module.symbols.tryAdd(input.name.value, input) {
input.localId = s.module.symbols.count
} else {
duplicateSymbol(s, input.name)
}
}
block(s, n.body)
}
default: {}
}
}
}
block(s IndexerState, st Block) {
for n in st.contents {
match n {
ClockStatement: block(s, n.body)
IfStatement: if_(s, n)
AssignStatement: assign(s, n)
default: {}
}
}
}
if_(s IndexerState, st IfStatement) {
block(s, st.ifBody)
if st.elseBranch.is(IfStatement) {
if_(s, st.elseBranch.as(IfStatement))
} else if st.elseBranch.is(Block) {
block(s, st.elseBranch.as(Block))
}
}
assign(s IndexerState, st AssignStatement) {
module := s.module
if st.regKeyword != null {
st.flags |= AssignFlags.reg
if st.nameExpr.is(Token) {
nameToken := st.nameExpr.as(Token)
name := nameToken.value
if s.module.symbols.tryAdd(name, st) {
st.localId = s.module.symbols.count
} else {
duplicateSymbol(s, nameToken)
}
}
} else if st.op != null && st.op.value == "<=" {
st.flags |= AssignFlags.regUpdate
} else {
st.flags |= AssignFlags.wire
if st.nameExpr.is(Token) {
nameToken := st.nameExpr.as(Token)
name := nameToken.value
if s.module.symbols.tryAdd(name, st) {
st.localId = s.module.symbols.count
} else {
duplicateSymbol(s, nameToken)
}
}
}
}
duplicateSymbol(s IndexerState, token Token) {
s.comp.errors.add(Error.at(s.unit, token.span, "A symbol with the same name has already been defined"))
}
}

89
compiler/main.mu Normal file
View file

@ -0,0 +1,89 @@
//tab_size=4
main() {
::currentAllocator = Memory.newArenaAllocator(256 * 1024 * 1024)
CrashHandler.enable()
argErrors := new List<CommandLineArgsParserError>{}
parser := new CommandLineArgsParser.from(Environment.getCommandLineArgs(), argErrors)
args := parseArgs(parser, true)
if argErrors.count > 0 {
info := parser.getCommandLineInfo()
for argErrors {
Stderr.writeLine(CommandLineArgsParser.getErrorDesc(it, info))
}
exit(1)
}
if args.printVersion || args.printHelp {
Stdout.writeLine(format("Wyre compiler, version {}", compilerVersion))
if args.printHelp {
Stdout.writeLine("For documentation, see: https://github.com/nickmqb/wyre")
}
exit(1)
}
comp := new Compilation {
sources: args.sources,
errors: new List<Error>{},
units: new List<CodeUnit>{},
}
for sf, i in comp.sources {
unit := Parser.unit(sf.text, comp.errors, args.indent)
unit.path = sf.path
unit.id = i
comp.units.add(unit)
}
Indexer.comp(comp)
top := comp.symbols.getOrDefault(args.top)
if top.is(ModuleDef) {
top.as(ModuleDef).flags |= ModuleFlags.top
} else {
comp.errors.add(Error { text: format("Top module not found: {}", args.top) })
}
TypeChecker.comp(comp)
es := cast(null, EmulatorState)
if comp.errors.count == 0 {
es = Emulator.init(comp, top.as(ModuleDef))
Emulator.reset(es)
}
// Sort errors, but always show syntax errors first
comp.errors.slice(0, comp.nonSyntaxErrorStart).stableSort(ErrorHelper.compareErrors)
comp.errors.slice(comp.nonSyntaxErrorStart, comp.errors.count).stableSort(ErrorHelper.compareErrors)
if comp.errors.count > 0 {
for e, i in comp.errors {
if i >= args.maxErrors {
break
}
if e.unit != null {
Stdout.writeLine(ErrorHelper.getErrorDesc(e.unit.path, e.unit.source, e.span, e.text))
} else {
Stdout.writeLine(e.text)
}
}
if comp.errors.count > args.maxErrors {
Stdout.writeLine(format("{} errors ({} shown)", comp.errors.count, args.maxErrors))
} else {
Stdout.writeLine(format("{} errors", comp.errors.count))
}
exit(1)
}
out := VerilogGenerator.comp(comp, es)
if !File.tryWriteString(args.outputPath, out.sb.compactToString()) {
Stderr.writeLine(format("Could not write to output file: {}", args.outputPath))
exit(1)
}
Stdout.writeLine(format("Generated output: {}", args.outputPath))
}

1085
compiler/parser.mu Normal file

File diff suppressed because it is too large Load diff

109
compiler/range_finder.mu Normal file
View file

@ -0,0 +1,109 @@
RangeFinderState struct #RefType {
range IntRange
}
RangeFinder {
find(e Node) {
s := RangeFinderState { range: IntRange { from: int.maxValue, to: int.minValue } }
node(ref s, e)
return s.range
}
node(s RangeFinderState, e Node) {
match e {
Token: {
token(s, e)
}
NumberExpression: {
token(s, e.token)
}
UnaryOperatorExpression: {
token(s, e.op)
node(s, e.expr)
}
BinaryOperatorExpression: {
node(s, e.lhs)
token(s, e.op)
node(s, e.rhs)
}
DotExpression: {
node(s, e.lhs)
token(s, e.dot)
token(s, e.rhs)
}
TernaryOperatorExpression: {
node(s, e.conditionExpr)
token(s, e.question)
node(s, e.trueExpr)
token(s, e.colon)
node(s, e.falseExpr)
}
MatchExpression: {
token(s, e.keyword)
node(s, e.target)
token(s, e.openBrace)
for c in e.contents {
node(s, c)
}
token(s, e.closeBrace)
}
MatchExpressionCase: {
node(s, e.valueExpr)
token(s, e.colon)
node(s, e.resultExpr)
}
CallExpression: {
node(s, e.target)
token(s, e.openParen)
for c in e.contents {
node(s, c)
}
token(s, e.closeParen)
}
StructInitializerExpression: {
node(s, e.target)
token(s, e.openBrace)
for c in e.contents {
node(s, c)
}
token(s, e.closeBrace)
}
ParenExpression: {
token(s, e.openParen)
node(s, e.expr)
token(s, e.closeParen)
}
BraceExpression: {
token(s, e.openBrace)
for c in e.contents {
node(s, c)
}
token(s, e.closeBrace)
}
IndexExpression: {
node(s, e.target)
token(s, e.openBracket)
node(s, e.upperExpr)
token(s, e.colon)
node(s, e.lowerExpr)
token(s, e.closeBracket)
}
ArrayExpression: {
token(s, e.openBracket)
for c in e.contents {
node(s, c)
}
token(s, e.closeBracket)
}
null: {}
}
}
token(s RangeFinderState, e Token) {
if e == null {
return
}
s.range.from = min(s.range.from, e.span.from)
s.range.to = max(s.range.to, e.span.to)
}
}

375
compiler/tape.mu Normal file
View file

@ -0,0 +1,375 @@
Opcode enum {
push
pushArray
load
store
mask
index
shlOr
jumpIfZero
jump
nop
dup
discard
neg
invert
add
sub
and
or
xor
mul
eq
neq
lt
lte
gt
gte
shl
shr
slice
storeSlice
swizzle
toString(op Opcode) {
if op == Opcode.push {
return "push"
} else if op == Opcode.pushArray {
return "pushArray"
} else if op == Opcode.load {
return "load"
} else if op == Opcode.store {
return "store"
} else if op == Opcode.mask {
return "mask"
} else if op == Opcode.index {
return "index"
} else if op == Opcode.shlOr {
return "shlOr"
} else if op == Opcode.jumpIfZero {
return "jumpIfZero"
} else if op == Opcode.jump {
return "jump"
} else if op == Opcode.nop {
return "nop"
} else if op == Opcode.dup {
return "dup"
} else if op == Opcode.discard {
return "discard"
} else if op == Opcode.neg {
return "neg"
} else if op == Opcode.invert {
return "invert"
} else if op == Opcode.add {
return "add"
} else if op == Opcode.sub {
return "sub"
} else if op == Opcode.and {
return "and"
} else if op == Opcode.or {
return "or"
} else if op == Opcode.xor {
return "xor"
} else if op == Opcode.mul {
return "mul"
} else if op == Opcode.eq {
return "eq"
} else if op == Opcode.neq {
return "neq"
} else if op == Opcode.lt {
return "lt"
} else if op == Opcode.lte {
return "lte"
} else if op == Opcode.gt {
return "gt"
} else if op == Opcode.gte {
return "gte"
} else if op == Opcode.shl {
return "shl"
} else if op == Opcode.shr {
return "shr"
} else if op == Opcode.slice {
return "slice"
} else if op == Opcode.storeSlice {
return "storeSlice"
} else if op == Opcode.swizzle {
return "swizzle"
} else {
return "?"
}
}
}
Instruction struct {
op Opcode
z ulong
}
EmulatorRunner {
run(s EmulatorState, program List<Instruction>) {
stack := s.stack
assert(stack.count == 0)
pc := 0
while pc < program.count {
ins := program[pc]
top := stack.count - 1
lhs := stack.count - 2
if ins.op == Opcode.dup {
stack.add(stack[top])
} else if ins.op == Opcode.discard {
stack.setCountChecked(top)
} else if ins.op == Opcode.push {
stack.add(Value { kind: ValueKind.ulong_, z: ins.z })
} else if ins.op == Opcode.pushArray {
stack.add(Value { kind: ValueKind.byteArray, z: ins.z })
} else if ins.op == Opcode.load {
stack.add(s.rs[cast(ins.z, uint)])
} else if ins.op == Opcode.store {
setSlot(s, cast(ins.z, int), stack[top])
stack.setCountChecked(top)
} else if ins.op == Opcode.mask {
assert(stack[top].kind == ValueKind.ulong_)
stack[top].z &= ins.z
} else if ins.op == Opcode.index {
shr := ins.z >> 32
mask := (1_uL << (ins.z & 0xffffffff_uL)) - 1
stack[top].z = (unpack(stack[top]) >> shr) & mask
} else if ins.op == Opcode.shlOr {
binaryOperator(s, stack, (unpack(stack[lhs]) << ins.z) | unpack(stack[top]))
} else if ins.op == Opcode.jumpIfZero {
if stack[top].z == 0 {
pc += cast(ins.z, int)
}
stack.setCountChecked(top)
} else if ins.op == Opcode.jump {
pc += cast(ins.z, int)
} else if ins.op == Opcode.neg {
stack[top].z = -unpack(stack[top])
} else if ins.op == Opcode.invert {
stack[top].z = ~unpack(stack[top])
} else if ins.op == Opcode.add {
binaryOperator(s, stack, unpack(stack[lhs]) + unpack(stack[top]))
} else if ins.op == Opcode.sub {
binaryOperator(s, stack, unpack(stack[lhs]) - unpack(stack[top]))
} else if ins.op == Opcode.and {
binaryOperator(s, stack, unpack(stack[lhs]) & unpack(stack[top]))
} else if ins.op == Opcode.or {
binaryOperator(s, stack, unpack(stack[lhs]) | unpack(stack[top]))
} else if ins.op == Opcode.xor {
binaryOperator(s, stack, xor(unpack(stack[lhs]), unpack(stack[top])))
} else if ins.op == Opcode.mul {
binaryOperator(s, stack, unpack(stack[lhs]) * unpack(stack[top]))
} else if ins.op == Opcode.eq {
binaryOperator(s, stack, unpack(stack[lhs]) == unpack(stack[top]) ? 1_uL : 0)
} else if ins.op == Opcode.neq {
binaryOperator(s, stack, unpack(stack[lhs]) != unpack(stack[top]) ? 1_uL : 0)
} else if ins.op == Opcode.lte {
binaryOperator(s, stack, unpack(stack[lhs]) <= unpack(stack[top]) ? 1_uL : 0)
} else if ins.op == Opcode.lt {
binaryOperator(s, stack, unpack(stack[lhs]) < unpack(stack[top]) ? 1_uL : 0)
} else if ins.op == Opcode.gte {
binaryOperator(s, stack, unpack(stack[lhs]) >= unpack(stack[top]) ? 1_uL : 0)
} else if ins.op == Opcode.gt {
binaryOperator(s, stack, unpack(stack[lhs]) > unpack(stack[top]) ? 1_uL : 0)
} else if ins.op == Opcode.shl {
binaryOperator(s, stack, unpack(stack[lhs]) << unpack(stack[top]))
} else if ins.op == Opcode.shr {
binaryOperator(s, stack, unpack(stack[lhs]) >> unpack(stack[top]))
} else if ins.op == Opcode.slice {
value := slice(s, stack[stack.count - 3], ins.z, unpack(stack[lhs]), unpack(stack[top]))
stack.setCountChecked(stack.count - 3)
stack.add(value)
} else if ins.op == Opcode.storeSlice {
storeSlice(s, cast(ins.z, int), unpack(stack[stack.count - 3]), unpack(stack[lhs]), stack[top])
stack.setCountChecked(stack.count - 3)
} else if ins.op == Opcode.swizzle {
value := swizzle(s, stack[stack.count - 5], cast(unpack(stack[stack.count - 4]), int), cast(unpack(stack[stack.count - 3]), int), cast(unpack(stack[lhs]), int), cast(unpack(stack[top]), int))
stack.setCountChecked(stack.count - 5)
stack.add(value)
} else {
abandon()
}
pc += 1
}
assert(stack.count == 0)
}
binaryOperator(s EmulatorState, stack List<Value>, result ulong) {
stack[stack.count - 2].z = result
stack.setCountChecked(stack.count - 1)
}
setInput(s EmulatorState, inst ModuleInstance, input string, val ulong) {
si := inst.localState[inst.def.symbols.get(input).as(ModuleInputDef).localId]
setSlot(s, si, Value { kind: ValueKind.ulong_, z: val })
s.started[si] = true
s.done[si] = true
}
setSlot(s EmulatorState, si int, val Value) {
if si < s.ws.count {
dest := ref s.ws[si]
if dest.kind == ValueKind.ulong_ {
dest.z = unpackAsULong(val)
} else if dest.kind == ValueKind.byteArray {
copyToArray(val, dest)
} else {
abandon()
}
} else {
assert(val.kind == ValueKind.ulong_ || val.kind == ValueKind.byteArray)
s.rs[si] = val
}
}
slice(s EmulatorState, target Value, targetWidth ulong, offset ulong, width ulong) {
if width > 64 {
return bigSlice(s, target, targetWidth, offset, width)
}
data := unpackAsArray(ref target)
dataWidth := min(checked_cast(data.count, ulong) * 8, targetWidth)
result := Value { kind: ValueKind.ulong_ }
to := offset
offset = min(offset + width, dataWidth)
while offset > to {
offset -= 1
result.z <<= 1
index := cast(offset >> 3, uint)
mask := 1 << (offset & 7)
if data[index] & mask != 0 {
result.z |= 1
}
}
return result
}
storeSlice(s EmulatorState, si int, offset ulong, width ulong, val Value) {
dest := ref s.ws[si]
data := unpackAsArray(dest)
z := unpackAsULong(val)
dataWidth := checked_cast(min(data.count * 8, TypeChecker.unpackWidth(s.infos[si].tag)), ulong)
to := min(offset + width, dataWidth)
while offset < to {
index := cast(offset >> 3, uint)
mask := 1 << (offset & 7)
if z & 1 == 1 {
data[index] = cast(data[index] | mask, byte)
} else {
data[index] = cast(data[index] & ~mask, byte)
}
offset += 1
z >>= 1
}
}
bigSlice(s EmulatorState, target Value, targetWidth ulong, offset ulong, width ulong) {
data := unpackAsArray(ref target)
size := cast(data.count, ulong) * 8
from := min(offset, size)
to := min(offset + width, size)
fromByte := cast(from / 8, int)
toByte := cast((to + 7) / 8, int)
fromBit := from % 8
toBit := to % 8
if fromBit == 0 && toBit == 0 && target.kind == ValueKind.byteArray {
return packArray(new unpackArray(target).slice(fromByte, toByte))
}
count := toByte - fromByte
result := new Array<byte>(count)
data.copySlice(0, count, result, 0)
Util.shlArray(result, cast(fromBit, int))
return packArray(result)
}
swizzle(s EmulatorState, target Value, targetWidth int, seqSize int, readStep int, blockSize int) {
data := unpackAsArray(ref target)
result := new Array<byte>(targetWidth / 8)
blocks := targetWidth / blockSize
cycles := readStep / seqSize
stepsPerCycle := blockSize / readStep
w := 0
for b := 0; b < blocks {
for i := 0; i < cycles {
for j := 0; j < stepsPerCycle {
r := b * blockSize + i * seqSize + j * readStep
for k := 0; k < seqSize {
readIndex := r >> 3
readByte := readIndex < data.count ? data[readIndex] : 0
readMask := 1 << (r & 7)
if readByte & readMask != 0 {
writeMask := 1 << (w & 7)
result[w>> 3] = cast(result[w >> 3] | writeMask, byte)
}
r += 1
w += 1
}
}
}
}
assert(w == targetWidth)
return targetWidth > 64 ? EmulatorRunner.packArray(result) : EmulatorRunner.packULong(result)
}
unpack(v Value) {
assert(v.kind == ValueKind.ulong_)
return v.z
}
unpackArray(v Value) {
assert(v.kind == ValueKind.byteArray)
return transmute(v.z, Array<byte>)
}
unpackAsULong(v Value) {
if v.kind == ValueKind.ulong_ {
return v.z
} else if v.kind == ValueKind.byteArray {
z := 0_uL
data := transmute(v.z, Array<byte>)
Memory.memcpy(pointer_cast(ref z, pointer), data.dataPtr, cast(min(data.count, sizeof(ulong)), uint))
return z
} else {
abandon()
}
}
unpackAsArray(v *Value) {
if v.kind == ValueKind.ulong_ {
return Array<byte> { dataPtr: pointer_cast(ref v.z, pointer), count: 8 }
} else if v.kind == ValueKind.byteArray {
return transmute(v.z, Array<byte>)^
} else {
abandon()
}
}
copyToArray(v Value, dest *Value) {
assert(dest.kind == ValueKind.byteArray)
to := transmute(dest.z, Array<byte>)
if v.kind == ValueKind.ulong_ {
to.clearValues()
Memory.memcpy(to.dataPtr, pointer_cast(ref v.z, pointer), cast(min(to.count, sizeof(ulong)), uint))
} else if v.kind == ValueKind.byteArray {
from := transmute(v.z, Array<byte>)
if from != to {
from.copySlice(0, from.count, to, 0)
to.slice(from.count, to.count).clearValues()
}
} else {
abandon()
}
}
packArray(data Array<byte>) {
return Value { kind: ValueKind.byteArray, z: transmute(pointer_cast(data, pointer), ulong) }
}
packULong(data Array<byte>) {
z := 0_uL
Memory.memcpy(pointer_cast(ref z, pointer), data.dataPtr, cast(min(data.count, sizeof(ulong)), uint))
return Value { kind: ValueKind.ulong_, z: z }
}
}

1409
compiler/type_checker.mu Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,262 @@
TypeChecker {
rep(s TypeCheckerState, e CallExpression) {
builtinArgs(s, e, 2)
target := fixedNumberArg(s, e, 0)
nArg := constantArg(s, e, 1)
result := TypeCheckResult{}
if nArg.tag.isValid() {
n := tryUnpackInt(nArg.value)
if n.hasValue && 0 < n.value {
if target.tag.isValid() {
w := target.tag.q * n.value
if w <= 64 {
result.tag = Tag { kind: TagKind.number, q: w }
if target.value.kind == ValueKind.ulong_ {
result.value = Value { kind: ValueKind.ulong_, z: 0 }
for i := 0; i < n.value {
result.value.z <<= target.tag.q
result.value.z |= target.value.z
}
}
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Unsupported expression; type of expression cannot be larger than $64"))
}
}
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.args[1].expr), "Expected: constant, > 0"))
}
}
return result
}
slice(s TypeCheckerState, e CallExpression) {
if s.module == null {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.target), "Cannot use slice() in constant initializer"))
}
if s.isStaticExpr {
// OK
} else if s.comp.flags & CompilationFlags.simulate == 0 {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.target), "slice() can only be used in static initializer or in simulator"))
}
builtinArgs(s, e, 3)
fixedNumberArg(s, e, 0)
numberArg(s, e, 1)
widthArg := constantArg(s, e, 2)
result := TypeCheckResult{}
if widthArg.tag.isValid() {
width := tryUnpackInt(widthArg.value)
if s.isStaticExpr {
if width.hasValue && 0 < width.value {
result.tag = Tag { kind: TagKind.number, q: width.value }
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.args[2].expr), "Expected: constant, > 0"))
}
} else {
if width.hasValue && 1 <= width.value && width.value <= 64 {
result.tag = Tag { kind: TagKind.number, q: width.value }
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.args[2].expr), "Expected: constant between 1 and 64 inclusive"))
}
}
}
return result
}
assignSlice(s TypeCheckerState, st AssignStatement, e CallExpression) {
if s.comp.flags & CompilationFlags.simulate == 0 {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.target), "slice() can only be used in static initializer or in simulator"))
}
builtinArgs(s, e, 3)
if e.args.count >= 1 {
regArg := e.args[0].expr
if regArg.is(Token) {
nameToken := regArg.as(Token)
name := nameToken.value
sym := s.module.symbols.getOrDefault(nameToken.value)
if sym != null {
if sym.is(AssignStatement) && (sym.as(AssignStatement).flags & AssignFlags.reg) != 0 {
reg := sym.as(AssignStatement)
st.localId = reg.localId
ensureAssignDone(s, reg, nameToken)
regTag := s.typeMap.getOrDefault(reg)
if regTag.kind == TagKind.number && regTag.q > 0 {
// OK
} else {
expectedFixedNumberExpression(s, regArg)
}
} else {
s.errors.add(Error.at(s.unit, nameToken.span, "Expected: register"))
}
} else {
badSymbol(s, nameToken)
}
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(regArg), "Expected: register"))
}
}
numberArg(s, e, 1)
widthArg := constantArg(s, e, 2)
tag := Tag{}
if widthArg.tag.isValid() {
width := tryUnpackInt(widthArg.value)
if width.hasValue && 1 <= width.value && width.value <= 64 {
tag = Tag { kind: TagKind.number, q: width.value }
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.args[2].expr), "Expected: constant between 1 and 64 inclusive"))
return TypeCheckResult{}
}
}
if st.expr != null {
rhs := expressionWithGap(s, st.expr, true, tag)
if canAssign(s, rhs.tag, rhs.value, tag) {
// OK
} else {
badAssign(s, st.op, rhs.tag, tag)
}
}
}
chunk(s TypeCheckerState, e CallExpression) {
if s.module == null {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.target), "Unsupported expression: cannot use chunk() in constant initializer"))
}
if !s.isStaticExpr {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.target), "chunk() can only be used in static initializer"))
}
builtinArgs(s, e, 3)
arrayArg := fixedNumberArg(s, e, 0)
indexArg := constantArg(s, e, 1)
numChunksArg := constantArg(s, e, 2)
result := TypeCheckResult{}
if arrayArg.tag.isValid() && indexArg.tag.isValid() && numChunksArg.tag.isValid() {
numBits := unpackWidth(arrayArg.tag)
index := tryUnpackInt(indexArg.value)
numChunks := tryUnpackInt(numChunksArg.value)
if numChunks.hasValue && numChunks.value > 0 && (numBits % numChunks.value) == 0 {
result.tag = Tag { kind: TagKind.number, q: numBits / numChunks.value }
if index.hasValue {
if index.value < numChunks.value {
// OK
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.args[1].expr), "Expected: constant; must be lower than number of chunks"))
}
}
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.args[2].expr), format("Expected: constant; must be a divisor of target type {}", tagString(s.comp, arrayArg.tag))))
}
}
return result
}
swizzle(s TypeCheckerState, e CallExpression) {
if s.module == null {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.target), "Unsupported expression: cannot use swizzle() in constant initializer"))
}
if !s.isStaticExpr {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.target), "swizzle() can only be used in static initializer"))
}
builtinArgs(s, e, 4)
arrayArg := fixedNumberArg(s, e, 0)
seqSizeArg := constantArg(s, e, 1)
stepArg := constantArg(s, e, 2)
blockSizeArg := constantArg(s, e, 3)
if arrayArg.tag.isValid() && seqSizeArg.tag.isValid() && stepArg.tag.isValid() && blockSizeArg.tag.isValid() {
numBits := unpackWidth(arrayArg.tag)
seqSize := tryUnpackInt(seqSizeArg.value)
step := tryUnpackInt(stepArg.value)
blockSize := tryUnpackInt(blockSizeArg.value)
if numBits % 8 == 0 {
if blockSize.hasValue && blockSize.value > 0 && (numBits % blockSize.value) == 0 {
if step.hasValue && step.value > 0 && (blockSize.value % step.value) == 0 {
if seqSize.hasValue && seqSize.value > 0 && (step.value % seqSize.value) == 0 {
return TypeCheckResult { tag: arrayArg.tag }
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.args[1].expr), "Expected: constant; must be a divisor of step size"))
}
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.args[2].expr), "Expected: constant; must be a divisor of block size"))
}
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.args[3].expr), "Expected: constant; must be a divisor of target type size"))
}
} else {
s.errors.add(Error.at(s.unit, RangeFinder.find(e.args[0].expr), "Target type size must be a multiple of 8"))
}
}
return TypeCheckResult{}
}
builtinArgs(s TypeCheckerState, e CallExpression, count int) {
if e.args.count != count {
s.errors.add(Error.at(s.unit, e.openParen.span, format("Expected: {} args but got {} args", count, e.args.count)))
}
for arg, i in e.args {
if arg.name != null {
s.errors.add(Error.at(s.unit, arg.name.span, "Expected: expression"))
}
if i >= count {
expression(s, arg.expr)
}
}
}
fixedNumberArg(s TypeCheckerState, e CallExpression, index int) {
if index >= e.args.count {
return TypeCheckResult{}
}
arg := e.args[index]
if arg.expr == null {
return TypeCheckResult{}
}
tr := expression(s, arg.expr)
if tr.tag.isValid() {
if tr.tag.kind == TagKind.number && tr.tag.q > 0 {
// OK
} else {
expectedFixedNumberExpression(s, arg.expr)
return TypeCheckResult{}
}
}
return tr
}
numberArg(s TypeCheckerState, e CallExpression, index int) {
if index >= e.args.count {
return TypeCheckResult{}
}
arg := e.args[index]
if arg.expr == null {
return TypeCheckResult{}
}
tr := expression(s, arg.expr)
if tr.tag.isValid() {
if tr.tag.kind == TagKind.number {
// OK
} else {
expectedNumberExpression(s, arg.expr)
return TypeCheckResult{}
}
}
return tr
}
constantArg(s TypeCheckerState, e CallExpression, index int) {
if index >= e.args.count {
return TypeCheckResult{}
}
arg := e.args[index]
if arg.expr == null {
return TypeCheckResult{}
}
tr := expression(s, arg.expr)
if tr.tag.isValid() {
if tr.tag.kind == TagKind.number && tr.value.kind == ValueKind.ulong_ {
// OK
} else {
expectedConstant(s, arg.expr)
return TypeCheckResult{}
}
}
return tr
}
}

View file

@ -0,0 +1,201 @@
TypeChecker {
tag(s TypeCheckerState, name string) {
if name[0] == '$' {
pr := int.tryParse(name.slice(1, name.length))
if pr.hasValue && pr.value > 0 {
return Tag { kind: TagKind.number, q: pr.value }
}
} else {
sym := s.symbols.getOrDefault(name)
if sym != null && sym.is(StructDef) {
return Tag { kind: TagKind.struct_, q: sym.as(StructDef).id }
}
}
return Tag{}
}
tagString(comp Compilation, tag Tag) {
if tag.kind == TagKind.number {
if tag.q != 0 {
return format("${}", tag.q)
} else {
return format("$*", tag.q)
}
} else if tag.kind == TagKind.moduleOut {
return format("[{}]", comp.entities[tag.q].as(ModuleDef).name.value)
} else if tag.kind == TagKind.struct_ {
return format("[{}]", comp.entities[tag.q].as(StructDef).name.value)
}
return "[unknown]"
}
canAssign(s TypeCheckerState, from Tag, fromValue Value, to Tag) {
if to.kind != TagKind.unknown {
if from.kind == TagKind.number {
return to.kind == TagKind.number && (to.q == from.q || (from.q == 0 && canConvertFreeConst(s, fromValue, to.q)))
} else if from.kind == TagKind.moduleOut || from.kind == TagKind.struct_ {
return to.kind == from.kind && to.q == from.q
}
}
return true
}
// Returns a value between 0 and 64
// 0 => all zeroes; 1 to 64 => bit N-1 is the highest bit
highestBit(n ulong) {
result := 0
while n != 0 {
n >>= 1
result += 1
}
return result
}
canConvertFreeConst(s TypeCheckerState, val Value, width int) {
if val.kind == ValueKind.ulong_ {
return highestBit(val.z) <= width //|| highestBit(~val.z) < width
}
return false
}
isFixedNumberOrStruct(tag Tag) {
return (tag.kind == TagKind.number && tag.q > 0) || tag.kind == TagKind.struct_
}
isFixedNumberOrStructOrModuleOut(tag Tag) {
return (tag.kind == TagKind.number && tag.q > 0) || tag.kind == TagKind.struct_ || tag.kind == TagKind.moduleOut
}
numSlots(comp Compilation, tag Tag) int {
if tag.kind == TagKind.number {
return 1
} else if tag.kind == TagKind.struct_ {
return comp.entities[tag.q].as(StructDef).fields.count
}
abandon()
}
isValidEntityName(s string) {
return s[0] != '$'
}
isValidEntityName_nonStatic(s string) {
return s[0] != '$' && s[0] != '#'
}
unpackWidth(tag Tag) {
assert(tag.kind == TagKind.number)
assert(tag.q > 0)
return tag.q
}
unpackInt(value Value) {
assert(value.kind == ValueKind.ulong_)
assert(value.z <= cast(int.maxValue, ulong))
return cast(value.z, int)
}
tryUnpackInt(value Value) {
assert(value.kind == ValueKind.ulong_)
if value.z <= cast(int.maxValue, ulong) {
return Maybe.from(cast(value.z, int))
}
return Maybe<int>{}
}
badSymbol(s TypeCheckerState, token Token) {
s.errors.add(Error.at(s.unit, token.span, format("Undefined symbol: {}", token.value)))
}
duplicateSymbol(s TypeCheckerState, token Token) {
s.errors.add(Error.at(s.unit, token.span, "A symbol with the same name has already been defined"))
}
statementNotAllowedInsideBlackbox(s TypeCheckerState, e Node) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Statement is not allowed inside blackbox module"))
}
badAssign(s TypeCheckerState, at Token, from Tag, to Tag) {
s.errors.add(Error.at(s.unit, at.span, format("Cannot assign {} to {}", tagString(s.comp, from), tagString(s.comp, to))))
}
badConversion(s TypeCheckerState, e Node, from Tag, to Tag) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), format("Cannot convert {} to {}", tagString(s.comp, from), tagString(s.comp, to))))
}
badConstConversion(s TypeCheckerState, e Node, from Tag, to Tag) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), format("Cannot convert constant of type {} to {}", tagString(s.comp, from), tagString(s.comp, to))))
}
unsupportedRegUpdate(s TypeCheckerState, e Node) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Expected: register, register.field or slice(...)"))
}
unsupportedUnaryOp(s TypeCheckerState, op Token, tag Tag) {
s.errors.add(Error.at(s.unit, op.span, format("Unsupported expression: cannot apply operator {} to argument of type {}", op.value, tagString(s.comp, tag))))
}
unsupportedBinaryOp(s TypeCheckerState, op Token, lhs Tag, rhs Tag) {
s.errors.add(Error.at(s.unit, op.span, format("Unsupported expression: cannot apply operator {} to arguments of type {} and {}", op.value, tagString(s.comp, lhs), tagString(s.comp, rhs))))
}
badUnaryOp(s TypeCheckerState, op Token, tag Tag) {
s.errors.add(Error.at(s.unit, op.span, format("Cannot apply operator {} to argument of type {}", op.value, tagString(s.comp, tag))))
}
badBinaryOp(s TypeCheckerState, op Token, lhs Tag, rhs Tag) {
s.errors.add(Error.at(s.unit, op.span, format("Cannot apply operator {} to arguments of type {} and {}", op.value, tagString(s.comp, lhs), tagString(s.comp, rhs))))
}
badUnify(s TypeCheckerState, token Token, te Tag, fe Tag) {
s.errors.add(Error.at(s.unit, token.span, format("Cannot unify values of type {} and {}", tagString(s.comp, te), tagString(s.comp, fe))))
}
badBinaryOperandConversion(s TypeCheckerState, span IntRange, tag Tag) {
s.errors.add(Error.at(s.unit, span, format("Cannot convert operand to type {}", tagString(s.comp, tag))))
}
badGap(s TypeCheckerState, op Token) {
s.errors.add(Error.at(s.unit, op.span, format("Cannot apply operator {} because target type is inferred", op.value)))
}
badGapArgument(s TypeCheckerState, op Token, arg Tag, target Tag) {
s.errors.add(Error.at(s.unit, op.span, format("Cannot apply operator {} to argument of type {} and target of type {}", op.value, tagString(s.comp, arg), tagString(s.comp, target))))
}
expectedFixedNumber(s TypeCheckerState, e Node) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Expected: fixed width numeric type"))
}
expectedFixedNumberExpression(s TypeCheckerState, e Node) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Expected: expression of fixed width numeric type"))
}
expectedFixedNumberOrStruct(s TypeCheckerState, e Node) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Expected: fixed width numeric type or struct type"))
}
expectedFixedNumberOrStructExpression(s TypeCheckerState, e Node) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Expected: expression of fixed width numeric type or struct"))
}
expectedNumberExpression(s TypeCheckerState, e Node) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Expected: expression of numeric type"))
}
expectedConstant(s TypeCheckerState, e Node) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Expected: constant"))
}
expectedStaticConstant(s TypeCheckerState, e Node) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Expected: expression that evaluates to constant during initialization"))
}
invalidNamePrefix(s TypeCheckerState, e Node) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Invalid name; must start with letter, _ or #"))
}
invalidNamePrefix_nonStatic(s TypeCheckerState, e Node) {
s.errors.add(Error.at(s.unit, RangeFinder.find(e), "Invalid name; must start with letter or _"))
}
}

View file

@ -0,0 +1,917 @@
GeneratorState struct #RefType {
comp Compilation
symbols Map<string, Node>
typeMap Map<Node, Tag>
constMap Map<Node, Value>
entities List<Node>
errors List<Error>
es EmulatorState
inst ModuleInstance
evalCtxOutput int
evalCtxField int
isNonIndexable bool
out CodeBuilder
first CodeBuilder
assignments CodeBuilder
regUpdates CodeBuilder
globals Set<string>
}
CodeBuilder struct #RefType {
sb StringBuilder
indent int
lineSep string
skipSep bool
create() {
return CodeBuilder { sb: new StringBuilder{} }
}
}
NodeWithCtx struct {
node Node
ctxOutput int
ctxField int
hash(self NodeWithCtx) {
return xor(xor(transmute(pointer_cast(self.node, pointer), uint), cast(self.ctxOutput, uint) << 25), cast(self.ctxField, uint))
}
equals(a NodeWithCtx, b NodeWithCtx) {
return a.node == b.node && a.ctxOutput == b.ctxOutput && a.ctxField == b.ctxField
}
}
VerilogGenerator {
comp(comp Compilation, es EmulatorState) {
s := new GeneratorState {
comp: comp,
symbols: comp.symbols,
typeMap: comp.typeMap,
constMap: comp.constMap,
entities: comp.entities,
errors: comp.errors,
es: es,
evalCtxOutput: -1,
evalCtxField: -1,
out: new CodeBuilder.create(),
globals: new Set.create<string>(),
}
s.globals.add("module")
s.globals.add("endmodule")
s.globals.add("begin")
s.globals.add("end")
s.globals.add("if")
s.globals.add("else")
s.globals.add("posedge")
s.globals.add("negedge")
s.globals.add("wire")
s.globals.add("reg")
s.globals.add("input")
s.globals.add("output")
s.globals.add("inout")
s.globals.add("case")
for u in s.comp.units {
for node in u.contents {
if node.is(ModuleDef) {
def := node.as(ModuleDef)
if def.blackboxKeyword != null {
s.globals.tryAdd(def.name.value)
}
}
}
}
for mi in es.moduleInstances {
if mi.def.blackboxKeyword == null {
mi.genLocals = new Map.create<NodeWithCtx, string>()
mi.genLocalsSet = new Set.create<string>()
name := mi.fullName != "" ? mi.fullName.replace(".", "_") : mi.def.name.value
mi.genGlobalName = uniqueName(s, mi, s.globals, name)
} else {
mi.genGlobalName = mi.def.name.value
}
}
for mi in es.moduleInstances {
s.globals.tryRemove(mi.genGlobalName)
}
write(s, format("// Generated by Wyre compiler {}\n", compilerVersion))
for mi in es.moduleInstances {
if mi.def.blackboxKeyword == null {
module(s, mi)
}
}
return s.out
}
module(s GeneratorState, inst ModuleInstance) {
s.inst = inst
def := inst.def
for i := 1; i < inst.calls.count {
mi := s.es.moduleInstances[inst.calls[i]]
mi.genLocalName = uniqueName(s, s.inst, s.inst.genLocalsSet, mi.localName)
}
write(s, "\nmodule ")
write(s, inst.genGlobalName)
write(s, "(")
indent(s, ",")
for inp in def.inputs {
if inp.flags & ModuleInputFlags.static == 0 {
tag := s.typeMap.get(inp)
if tag.kind == TagKind.number {
input(s, inp, nameOf(s, s.inst, inp, -1, -1), tag)
} else if tag.kind == TagKind.struct_ {
sdef := s.entities[tag.q].as(StructDef)
for f, fi in sdef.fields {
input(s, inp, nameOf(s, s.inst, inp, -1, fi), s.typeMap.get(f))
}
} else {
abandon()
}
}
}
for o in def.outputs {
tag := s.typeMap.get(o)
if tag.kind == TagKind.number {
output(s, o, nameOf(s, s.inst, o, -1, -1), tag)
} else if tag.kind == TagKind.struct_ {
sdef := s.entities[tag.q].as(StructDef)
for f, fi in sdef.fields {
s.evalCtxField = fi
output(s, o, nameOf(s, s.inst, o, -1, fi), s.typeMap.get(f))
}
s.evalCtxField = -1
} else {
abandon()
}
}
unindent(s)
beginLine(s)
write(s, ");\n")
s.assignments = new CodeBuilder.create()
s.regUpdates = new CodeBuilder.create()
s.first = push(s, s.regUpdates)
firstPos := s.first.sb.count
block(s, def.body)
if s.regUpdates.sb.count > 0 {
beginLine(s)
}
for i := 1; i < inst.calls.count {
callInst(s, s.es.moduleInstances[inst.calls[i]])
}
restore(s, s.first)
if s.first.sb.count != firstPos {
beginLine(s)
}
str := s.assignments.sb.compactToString()
if str != "" {
write(s, str)
beginLine(s)
}
str = s.regUpdates.sb.compactToString()
if str != "" {
write(s, str)
}
if inst.calls.count == 0 {
beginLine(s)
}
write(s, "\nendmodule\n")
}
input(s GeneratorState, inp ModuleInputDef, genName string, tag Tag) {
beginLine(s)
write(s, "input ")
numberTag(s, tag)
write(s, genName)
}
output(s GeneratorState, o AssignStatement, genName string, tag Tag) {
isReg := o.regKeyword != null
beginLine(s)
write(s, "output ")
if isReg {
write(s, "reg ")
}
numberTag(s, tag)
write(s, genName)
if isReg {
if o.expr != null && o.flags & AssignFlags.regUpdate == 0 {
write(s, " = ")
staticValue(s, s.inst, o.localId, tag)
} else {
write(s, " = 0")
}
}
}
callInst(s GeneratorState, target ModuleInstance) {
def := target.def
callExpr := target.callExpr
beginLine(s)
write(s, "(* keep *) ")
write(s, target.genGlobalName)
write(s, " ")
if def.blackboxKeyword != null {
if hasStaticInputs(s, def) {
write(s, "#(")
indent(s, ",")
for inp in def.inputs {
if inp.flags & ModuleInputFlags.static != 0 {
tag := s.typeMap.get(inp)
beginLine(s)
write(s, ".")
write(s, trimHash(inp.name.value))
write(s, "(")
staticValue(s, target, inp.localId, tag)
write(s, ")")
}
}
unindent(s)
beginLine(s)
write(s, ") ")
}
}
write(s, target.genLocalName)
write(s, "(")
indent(s, ",")
for inp in def.inputs {
if inp.flags & ModuleInputFlags.static == 0 {
tag := s.typeMap.get(inp)
if tag.kind == TagKind.number {
beginLine(s)
write(s, ".")
write(s, def.blackboxKeyword == null ? nameOf(s, target, inp, -1, -1) : inp.name.value)
write(s, "(")
expression(s, callExpr.args[callExpr.calleeLocalIdToArgIndex[inp.localId]].expr)
write(s, ")")
} else if tag.kind == TagKind.struct_ {
sdef := unpackStruct(s, tag)
for f, i in sdef.fields {
beginLine(s)
write(s, ".")
write(s, def.blackboxKeyword == null ? nameOf(s, target, inp, -1, i) : inp.name.value)
write(s, "(")
s.evalCtxField = i
expression(s, callExpr.args[callExpr.calleeLocalIdToArgIndex[inp.localId]].expr)
write(s, ")")
}
s.evalCtxField = -1
} else {
abandon()
}
}
}
for o, oi in def.outputs {
tag := s.typeMap.get(o)
if tag.kind == TagKind.number {
beginLine(s)
write(s, ".")
write(s, def.blackboxKeyword == null ? nameOf(s, target, o, -1, -1) : o.nameExpr.as(Token).value)
write(s, "(")
write(s, nameOf(s, s.inst, callExpr, oi, -1))
write(s, ")")
} else if tag.kind == TagKind.struct_ {
sdef := unpackStruct(s, tag)
for f, fi in sdef.fields {
beginLine(s)
write(s, ".")
write(s, def.blackboxKeyword == null ? nameOf(s, target, o, -1, fi) : o.nameExpr.as(Token).value)
write(s, "(")
write(s, nameOf(s, s.inst, callExpr, oi, fi))
write(s, ")")
}
} else {
abandon()
}
}
unindent(s)
beginLine(s)
write(s, ");")
prev := push(s, s.first)
for o, oi in def.outputs {
tag := s.typeMap.get(o)
if tag.kind == TagKind.number {
beginLine(s)
write(s, "wire ")
numberTag(s, tag)
write(s, nameOf(s, s.inst, callExpr, oi, -1))
write(s, ";")
} else if tag.kind == TagKind.struct_ {
sdef := unpackStruct(s, tag)
for f, fi in sdef.fields {
beginLine(s)
write(s, "wire ")
numberTag(s, s.typeMap.get(f))
write(s, nameOf(s, s.inst, callExpr, oi, fi))
write(s, ";")
}
} else {
abandon()
}
}
restore(s, prev)
beginLine(s)
}
block(s GeneratorState, block Block) {
for n in block.contents {
match n {
ClockStatement: clock(s, n)
IfStatement: if_(s, n)
AssignStatement: assign(s, n)
}
}
}
clock(s GeneratorState, st ClockStatement) {
beginLine(s)
write(s, "always @(")
write(s, st.keyword.value)
write(s, " ")
write(s, st.name.value)
write(s, ") begin")
indent(s, "")
block(s, st.body)
unindent(s)
beginLine(s)
write(s, "end")
}
if_(s GeneratorState, st IfStatement) {
beginLine(s)
write(s, "if (")
expression(s, st.expr)
write(s, ") begin")
indent(s, "")
block(s, st.ifBody)
unindent(s)
if st.elseBranch != null {
beginLine(s)
write(s, "end else begin")
indent(s, "")
if st.elseBranch.is(IfStatement) {
if_(s, st.elseBranch.as(IfStatement))
} else if st.elseBranch.is(Block) {
block(s, st.elseBranch.as(Block))
}
unindent(s)
}
beginLine(s)
write(s, "end")
}
assign(s GeneratorState, st AssignStatement) {
if (st.flags & AssignFlags.reg) != 0 && st.outKeyword == null {
prev := push(s, s.first)
name := st.nameExpr.as(Token).value
tag := s.typeMap.get(st)
if tag.kind == TagKind.number {
reg(s, st, nameOf(s, s.inst, st, -1, -1), tag)
} else if tag.kind == TagKind.struct_ {
def := s.entities[tag.q].as(StructDef)
for f, fi in def.fields {
s.evalCtxField = fi
reg(s, st, nameOf(s, s.inst, st, -1, fi), s.typeMap.get(f))
}
s.evalCtxField = -1
} else {
abandon()
}
restore(s, prev)
}
if (st.flags & AssignFlags.wire) != 0 && (st.flags & AssignFlags.static) == 0 {
prev := push(s, s.first)
tag := s.typeMap.get(st)
if tag.kind == TagKind.number {
wire(s, st, nameOf(s, s.inst, st, -1, -1), tag)
} else if tag.kind == TagKind.struct_ {
def := s.entities[tag.q].as(StructDef)
for f, fi in def.fields {
s.evalCtxField = fi
wire(s, st, nameOf(s, s.inst, st, -1, fi), s.typeMap.get(f))
}
s.evalCtxField = -1
} else if tag.kind == TagKind.moduleOut {
// OK
} else {
abandon()
}
restore(s, prev)
}
if st.flags & AssignFlags.regUpdate != 0 {
nameExpr := st.nameExpr
match nameExpr {
Token: {
sym := s.inst.def.symbols.get(nameExpr.value)
tag := s.typeMap.get(sym)
if tag.kind == TagKind.number {
regUpdate(s, nameOf(s, s.inst, sym, -1, -1), st.expr)
} else if tag.kind == TagKind.struct_ {
def := s.entities[tag.q].as(StructDef)
for f, fi in def.fields {
s.evalCtxField = fi
regUpdate(s, nameOf(s, s.inst, sym, -1, fi), st.expr)
}
s.evalCtxField = -1
} else {
abandon()
}
}
DotExpression: {
dot := nameExpr
nameToken := dot.lhs.as(Token)
sym := s.inst.def.symbols.getOrDefault(nameToken.value)
tag := s.typeMap.get(sym)
sdef := unpackStruct(s, tag)
fieldIndex := sdef.symbols.get(dot.rhs.value).fieldIndex
regUpdate(s, nameOf(s, s.inst, sym, -1, fieldIndex), st.expr)
}
}
}
}
reg(s GeneratorState, st AssignStatement, genName string, tag Tag) {
beginLine(s)
write(s, "(* keep *) reg ")
numberTag(s, tag)
write(s, genName)
if st.expr != null && st.flags & AssignFlags.regUpdate == 0 {
write(s, " = ")
staticValue(s, s.inst, st.localId, tag)
} else {
write(s, " = 0")
}
write(s, ";")
}
wire(s GeneratorState, st AssignStatement, genName string, tag Tag) {
if st.outKeyword == null {
beginLine(s)
write(s, "wire ")
numberTag(s, tag)
write(s, genName)
write(s, ";")
}
prev := push(s, s.assignments)
beginLine(s)
write(s, "assign ")
write(s, genName)
write(s, " = ")
expression(s, st.expr)
write(s, ";")
restore(s, prev)
}
regUpdate(s GeneratorState, genName string, expr Node) {
beginLine(s)
write(s, genName)
write(s, " <= ")
expression(s, expr)
write(s, ";")
}
expression(s GeneratorState, e Node) {
val := s.constMap.getOrDefault(e)
if val.kind != ValueKind.none {
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
tag := s.typeMap.get(e)
value(s, tag, val)
} else {
expressionInner(s, e)
}
}
expressionInner(s GeneratorState, e Node) {
match e {
Token: token(s, e)
NumberExpression: number(s, e)
UnaryOperatorExpression: unaryOperator(s, e)
BinaryOperatorExpression: binaryOperator(s, e)
DotExpression: dot(s, e)
TernaryOperatorExpression: ternaryOperator(s, e)
MatchExpression: match_(s, e)
ParenExpression: paren(s, e)
IndexExpression: index(s, e)
CallExpression: call(s, e)
StructInitializerExpression: structInit(s, e)
BraceExpression: brace(s, e)
}
}
token(s GeneratorState, e Token) {
name := e.value
sym := s.inst.def.symbols.getOrDefault(name)
if s.evalCtxOutput == -1 {
match sym {
ModuleInputDef: {
if sym.flags & ModuleInputFlags.static == 0 {
write(s, nameOf(s, s.inst, sym, -1, s.evalCtxField))
} else {
staticValue(s, s.inst, sym.localId, s.typeMap.get(sym))
}
}
AssignStatement: {
if sym.flags & AssignFlags.static == 0 {
write(s, nameOf(s, s.inst, sym, -1, s.evalCtxField))
} else {
staticValue(s, s.inst, sym.localId, s.typeMap.get(sym))
}
}
}
} else {
assign := sym.as(AssignStatement)
assert(assign.expr.is(CallExpression))
expression(s, assign.expr)
}
}
number(s GeneratorState, e NumberExpression) {
s.isNonIndexable = true
sb := s.out.sb
assert(s.evalCtxField == -1 && s.evalCtxOutput == -1)
assert(e.dontCare != 0)
tag := s.typeMap.get(e)
assert(tag.kind == TagKind.number)
assert(tag.q > 0)
mask := 1_uL << (tag.q - 1)
tag.q.writeTo(sb)
sb.write("'b")
while mask != 0 {
if mask & e.dontCare != 0 {
sb.write("x")
} else if mask & e.value != 0 {
sb.write("1")
} else {
sb.write("0")
}
mask >>= 1
}
}
unaryOperator(s GeneratorState, e UnaryOperatorExpression) {
s.isNonIndexable = true
write(s, e.op.value)
write(s, "(")
expression(s, e.expr)
write(s, ")")
}
binaryOperator(s GeneratorState, e BinaryOperatorExpression) {
s.isNonIndexable = true
write(s, "(")
expression(s, e.lhs)
write(s, ") ")
write(s, e.op.value)
write(s, " (")
expression(s, e.rhs)
write(s, ")")
}
dot(s GeneratorState, e DotExpression) {
tag := s.typeMap.get(e.lhs)
if tag.kind == TagKind.struct_ {
assert(s.evalCtxField == -1)
def := s.entities[tag.q].as(StructDef)
s.evalCtxField = def.symbols.get(e.rhs.value).fieldIndex
expression(s, e.lhs)
s.evalCtxField = -1
} else if tag.kind == TagKind.moduleOut {
assert(s.evalCtxOutput == -1)
def := s.entities[tag.q].as(ModuleDef)
s.evalCtxOutput = def.symbols.get(e.rhs.value).as(AssignStatement).outputIndex
expression(s, e.lhs)
s.evalCtxOutput = -1
} else {
abandon()
}
}
ternaryOperator(s GeneratorState, e TernaryOperatorExpression) {
s.isNonIndexable = true
write(s, "(")
expression(s, e.conditionExpr)
write(s, ") ? (")
expression(s, e.trueExpr)
write(s, ") : (")
expression(s, e.falseExpr)
write(s, ")")
}
match_(s GeneratorState, e MatchExpression) {
eb := new CodeBuilder.create()
prev := push(s, eb)
s.isNonIndexable = false
expression(s, e.target)
restore(s, prev)
target := ""
if !s.isNonIndexable {
target = eb.sb.compactToString()
} else {
target = newLocal(s, s.typeMap.get(e.target), eb.sb.compactToString())
}
indent(s, "")
for cs in e.cases {
beginLine(s)
write(s, "(((")
write(s, target)
write(s, ") == ")
expression(s, cs.valueExpr)
write(s, ") ? (")
expression(s, cs.resultExpr)
write(s, ") :")
}
write(s, " ")
resultTag := s.typeMap.get(e)
write(s, format("{}'b{}", resultTag.q, string.repeatChar('x', resultTag.q)))
write(s, string.repeatChar(')', e.cases.count))
unindent(s)
s.isNonIndexable = true
}
paren(s GeneratorState, e ParenExpression) {
s.isNonIndexable = true
write(s, "(")
expression(s, e.expr)
write(s, ")")
}
index(s GeneratorState, e IndexExpression) {
eb := new CodeBuilder.create()
prev := push(s, eb)
s.isNonIndexable = false
expression(s, e.target)
restore(s, prev)
if !s.isNonIndexable {
write(s, eb.sb.compactToString())
} else {
write(s, newLocal(s, s.typeMap.get(e.target), eb.sb.compactToString()))
}
sb := s.out.sb
sb.write("[")
upper := s.constMap.get(e.upperExpr).z
upper.writeTo(sb)
if e.lowerExpr != null {
sb.write(":")
lower := s.constMap.get(e.lowerExpr).z
lower.writeTo(sb)
}
sb.write("]")
s.isNonIndexable = true
}
call(s GeneratorState, e CallExpression) {
if e.builtin == BuiltinCall.rep {
rep(s, e)
return
}
assert(e.builtin == BuiltinCall.none)
assert(s.evalCtxOutput >= 0)
write(s, nameOf(s, s.inst, e, s.evalCtxOutput, s.evalCtxField))
}
rep(s GeneratorState, e CallExpression) {
s.isNonIndexable = true
n := TypeChecker.unpackInt(s.constMap.get(e.args[1].expr))
write(s, "{ ")
sep := false
for i := 0; i < n {
if sep {
write(s, ", ")
} else {
sep = true
}
expression(s, e.args[0].expr)
}
write(s, " }")
}
structInit(s GeneratorState, e StructInitializerExpression) {
assert(s.evalCtxField >= 0)
def := unpackStruct(s, s.typeMap.get(e))
argIndex := e.fieldIndexToArgIndex[s.evalCtxField]
if argIndex >= 0 {
arg := e.args[argIndex].expr
prevCtxField := s.evalCtxField
s.evalCtxField = -1
expression(s, arg)
s.evalCtxField = prevCtxField
} else {
s.isNonIndexable = true
write(s, "0")
}
}
brace(s GeneratorState, e BraceExpression) {
s.isNonIndexable = true
write(s, "{ ")
sep := false
for arg in e.args {
if sep {
write(s, ", ")
} else {
sep = true
}
expression(s, arg)
}
write(s, " }")
}
newLocal(s GeneratorState, tag Tag, valStr string) {
prev := push(s, s.first)
beginLine(s)
write(s, "wire ")
numberTag(s, tag)
name := uniqueName(s, s.inst, s.inst.genLocalsSet, "local")
write(s, name)
write(s, ";")
restore(s, prev)
prev = push(s, s.assignments)
beginLine(s)
write(s, "assign ")
write(s, name)
write(s, " = ")
write(s, valStr)
write(s, ";")
restore(s, prev)
return name
}
staticValue(s GeneratorState, instance ModuleInstance, localId int, tag Tag) {
si := instance.localState[localId] + max(0, s.evalCtxField)
value(s, tag, s.es.rs[si])
}
value(s GeneratorState, tag Tag, value Value) {
s.isNonIndexable = true
assert(tag.kind == TagKind.number)
if value.kind == ValueKind.ulong_ {
if tag.q > 0 {
write(s, format("{}'d{}", tag.q, value.z))
} else {
write(s, format("{}", value.z))
}
} else if value.kind == ValueKind.byteArray {
data := EmulatorRunner.unpackArray(value)
write(s, format("{}'h", tag.q))
from := min((tag.q + 7) / 8, data.count)
for i := from - 1; i >= 0; i -= 1 {
Util.writeByteHexTo(data[i], s.out.sb)
}
} else {
abandon()
}
}
numberTag(s GeneratorState, tag Tag) {
assert(tag.kind == TagKind.number)
assert(tag.q > 0)
if tag.q > 1 {
write(s, format("[{}:0] ", tag.q - 1))
}
}
nameOf(s GeneratorState, inst ModuleInstance, node Node, ctxOutput int, ctxField int) string {
nc := NodeWithCtx { node: node, ctxOutput: ctxOutput, ctxField: ctxField }
existingName := inst.genLocals.maybeGet(nc)
if existingName.hasValue {
return existingName.value
}
sb := StringBuilder{}
match node {
ModuleInputDef: {
sb.write(trimHash(node.name.value))
if ctxField != -1 {
sb.write("_")
sb.write(unpackStruct(s, s.typeMap.get(node)).fields[ctxField].name.value)
}
}
AssignStatement: {
assert(s.typeMap.get(node).kind != TagKind.moduleOut)
sb.write(trimHash(node.nameExpr.as(Token).value))
if ctxField != -1 {
sb.write("_")
sb.write(unpackStruct(s, s.typeMap.get(node)).fields[ctxField].name.value)
}
}
CallExpression: {
target := s.es.moduleInstances[inst.calls[node.callId]]
output := target.def.outputs[ctxOutput]
sb.write(target.genLocalName)
sb.write("_")
sb.write(target.def.blackboxKeyword == null ? nameOf(s, target, output, -1, ctxField) : output.nameExpr.as(Token).value)
}
}
name := uniqueName(s, inst, inst.genLocalsSet, sb.compactToString())
inst.genLocals.add(nc, name)
return name
}
uniqueName(s GeneratorState, inst ModuleInstance, set Set<string>, origName string) {
name := origName
i := 1
while true {
if !s.globals.contains(name) && !inst.genLocalsSet.contains(name) {
set.add(name)
return name
}
name = format("{}_{}", origName, i)
i += 1
}
}
hasStaticInputs(s GeneratorState, def ModuleDef) {
result := false
for inp in def.inputs {
result ||= (inp.flags & ModuleInputFlags.static) != 0
}
return result
}
trimHash(s string) {
if s[0] == '#' {
return s.slice(1, s.length)
}
return s
}
beginLine(s GeneratorState) {
if !s.out.skipSep {
write(s, s.out.lineSep)
} else {
s.out.skipSep = false
}
write(s, "\n")
for i := 0; i < s.out.indent {
s.out.sb.writeChar('\t')
}
}
write(s GeneratorState, str string) {
s.out.sb.write(str)
}
indent(s GeneratorState, lineSep string) {
s.out.lineSep = lineSep
s.out.skipSep = true
s.out.indent += 1
}
unindent(s GeneratorState) {
s.out.lineSep = ""
s.out.indent -= 1
}
push(s GeneratorState, cb CodeBuilder) {
prev := s.out
s.out = cb
return prev
}
restore(s GeneratorState, cb CodeBuilder) {
s.out = cb
}
unpackStruct(s GeneratorState, tag Tag) {
assert(tag.kind == TagKind.struct_)
return s.entities[tag.q].as(StructDef)
}
unpackModule(s GeneratorState, tag Tag) {
assert(tag.kind == TagKind.moduleOut)
return s.entities[tag.q].as(ModuleDef)
}
}

27
compiler/wyre.args Normal file
View file

@ -0,0 +1,27 @@
../../muon/lib/core.mu
../../muon/lib/basic.mu
../../muon/lib/stdio.mu
../../muon/lib/string.mu
../../muon/lib/containers.mu
../../muon/lib/memory.mu
../../muon/lib/environment.mu
../../muon/lib/random.mu
../../muon/lib/range.mu
../../muon/lib/sort.mu
cstdlib.mu
ast.mu
parser.mu
command_line_args_parser.mu
args_parser.mu
error_helper.mu
indexer.mu
type_checker.mu
type_checker_utils.mu
type_checker_builtins.mu
range_finder.mu
emulator_init.mu
emulator.mu
tape.mu
verilog_generator.mu
main.mu
--output-file wyre.c

100
docs/feature_overview.md Normal file
View file

@ -0,0 +1,100 @@
# Features
### Strongly typed
All types must match exactly, which helps prevent mistakes.
### Compact bit literals
Bit literals are used a lot in hardware designs, so Wyre makes it easy to use them, using the `'` symbol. Examples: `'0`, `'101`, `'00110011`, etc.
### Type inference
Types of outputs, wires and registers can be inferred in most cases. Example: `out foo := '111`. The compiler infers that foo is an output that is 3 bits wide (if you want to be explicit, you could also write: `out foo $3 := '111`).
### Inline module instantiations
Modules can be instantiated inline. Example:
some_module() {
// ...
// Instantiate module called 'adder' and connect inputs lhs, rhs and c_in
sum := adder(lhs: x, rhs: y, c_in: 0)
// ...
}
Assuming that `adder` has two outputs `val` and `c_out`, they can be accessed as `sum.val` and `sum.c_out`. There is no need to explicitly connect any output wires.
### Order independent declarations
Declarations (modules, wires, outputs, registers) can be ordered however you want. Symbols can be used before their declaration. Symbols can even be declared inside clock and if statements.
### Minimalistic syntax
Types have a compact notation: `$N`, where N is the number of bits. E.g. `$1` (single bit), `$10` (10 bits), `$1024` (1Kbit block).
Names go before types (following the trend of modern programming languages like Go, Rust, etc.).
Wyre uses C-style `{` block syntax `}` and uses newlines as statement separators. You can also separate module inputs with newlines instead of commas if you like.
There are only 9 keywords in Wyre! `reg`, `out`, `if`, `else`, `match`, `posedge`, `negedge`, `blackbox`, `struct`
### `match` expressions
`match` expressions are essentially a switch statement in expression form. For example:
alu(a $1, b $1, op $2) {
out val := match op {
'00: a & b
'01: a | b
'10: a ^ b
'11: a & ~b
}
}
### Large data literals (data blocks)
In some designs you might need to define a larger block of data, e.g. to initialize some memory. This is easy in Wyre, just specify your data as a hex string of bytes:
[ 01ff02fe03... ]
### Builtin functions for transforming data
Wyre provides builtin functions `slice`, `chunk` and `swizzle` to slice and transform data. This can be useful for splitting data across multiple memory banks, for example.
### Structs
Point struct {
x $8
y $8
}
// later on...
p := Point { x: 4, y: 5 }
q := p.x
// etc..
### Column accurate compile error reporting
Each compile error has exact location information, so you can quickly fix problems. A typical error looks like:
Undefined symbol: some_wire
-> ../examples/leds.w:14
out o := some_wire + 1
~~~~~~~~~
### But wait: there is more!
Check out the [Language guide](language_guide.md) to learn more about:
* `#inputs` and `#wires` (for initialization)
* `blackbox` modules
* `zx` operator (for zero extension)
* `rep` builtin function (repeat value)
* `---` symbol to specify disconnected inputs
* Constants
* All supported operators (unary, binary, ternary)
* Statements (`posedge`, `negedge`, `if`, `else`, etc.)
* More details on the features discussed on this page

47
docs/getting_started.md Normal file
View file

@ -0,0 +1,47 @@
# Getting started
## Building the compiler
You can choose between the following two options:
### From .mu source
1. [Install the Muon compiler](https://github.com/nickmqb/muon/blob/master/docs/getting_started.md).
1. Clone this repo
1. Go to the compiler directory (in this repo)
1. Compile .mu sources: `mu --args wyre.args`
1. Use a C compiler to compile wyre.c. E.g.:
* GCC: `gcc -o wyre wyre.c`
* MSVC: `cl /Zi wyre.c`
1. You now have a Wyre compiler!
### From .c
If you prefer to not install Muon, you can take a shortcut:
1. Clone this repo
1. Go to the dist directory
1. Compile wyre.c with a C compiler of your choice (see instructions above)
1. You now have a Wyre compiler!
## Running the compiler
The syntax for invoking the compiler is as follows:
wyre [flag] [source_file.w] [flag] [source_file.w] ...
Source files and flags may be mixed and may be specified in any order.
Supported flags:
* `--output [path]`. The output of the compiler, a Verilog file.
* `--top [module]`. Name of the topmost module in the design.
* `--indent [n]`. Tab size, in spaces ([learn more about significant whitespace](language_tour.md)). Set to 0 to ignore.
* `--max-errors [n]`. Maximum number of compile errors to display.
To compile the [led](../examples/led.w) example:
1. Navigate to the `examples` directory.
2. Run: `wyre led.w --output led.v --top top --indent 4 --max-errors 20`
Tip: to avoid having to specify all arguments every time you invoke the compiler, it is recommendeded to create a short shell script (.sh) or batch file (.bat) that contains the command.

19
docs/language_guide.md Normal file
View file

@ -0,0 +1,19 @@
# Language guide
## Syntax
Before we begin, a quick note on syntax.
In Wyre, whitespace before a line (i.e. indentation) is significant. Besides using { and } for blocks, Wyre expects the contents of each block to be indented (like in Python). This is done to improve compile error recovery and compile error message quality.
In order for this to work, Wyre needs to know your tab size. You must specify your tab size via the compiler command line argument `--indent [n]`.
If you don't want to use significant whitespace, simply pass `--indent 0` (but be warned that you might get lower quality compile error messages!).
## TODO
This section is stil being written! For the time being:
* Check out the [feature overview](feature_overview.md) if you haven't already.
* See the [features example](../examples/features.w).
* View other [examples](../examples).

135
examples/features.w Normal file
View file

@ -0,0 +1,135 @@
// This design demonstrates some features of Wyre.
subtract_module(x $8, y $8) {
out output := x - y
}
// Declare a module with inputs clk (1 bit wide), a and b (both 4 bits wide)
some_module(clk $1, a $4, b $4) {
// Wires
some_wire := a + b // some_wire is inferred to be $4 (i.e. 4 bits wide)
some_wire2 $4 := a + b // Equivalent
// Literals
foo := '10101010 // Bit literal. foo is inferred to be $8
bar $8 := 3 // Need to specify type because '3' doesn't have a specific width
bar2 := 3_$8 // _$N suffix can be used to treat number as specific width
hex $4 := 0xa // Need to specify type for same reason as above
hex2 := 0xa_$4 // Can use _$N suffix
// Outputs
out some_output := a | b // some_output is inferred to be $4
out some_output2 $4 := a | b // Equivalent
// Registers
reg some_reg = '1111 // some_reg is inferred to be $4; initialized to '1111 (15)
reg some_reg2 $4 = '1111 // Equivalent
reg some_reg3 $4 // Initial value is 0 if not specified
// Register updates; must happen inside clock statement (posedge/negedge keyword)
posedge clk {
some_reg <= 1
// Conditional updates
if a == '0101 {
some_reg2 <= 2
reg other_reg $4 <= 3 // Can declare registers anywhere; can combine declaration and update
nested_wire := some_reg + other_reg // Can declare wires anywhere (unaffected by clock and if statement)
} else {
some_reg2 <= 0xf
}
}
negedge clk {
some_reg3 <= b
}
// Unary operators
negated := -a
inverted := ~a
extended_wire $32 := zx a // a is zero extended to 32 bits total
// Binary operators: + - & | ^ == << >>
op_add := a + b
op_subtract := a - b
op_and := a & b
op_or := a | b
op_xor := a ^ b
op_compare := a == b // Or: != < <= > >=
op_shl := a << 1
op_shr := a >> 1
// Ternary operator
c := some_wire == 3 ? a : b
// Select & concatenate bits
msb := a[3]
rest := a[2:0]
recombined := { a[3], a[2:0] }
repeated_wire := rep(a[2:0], 10) // Use builtin function rep to repeat values. repeated_wire is inferred to be $30
// match
match_result := match some_wire[1:0] {
'00: a & b
'01: a | b
'10: a ^ b
'11: a & ~b
}
// Module instantiation
sub := subtract_module(x: 8, y: 5) // Connect inputs x and y
baz := sub.output // Use module output, type of baz is inferred to be $8
sub2 := subtract_module(x: ---, y: 5) // All inputs must be specified; to leave disconnected, use ---
sub3 := subtract_module(x: 'xxxxxx00, y: 5) // Or use bit literal with "don't care" value (x) for partially disconnected
}
// Module with a static input and a normal input
// The static input must be assigned a value that is constant at initialization time, and won't be part of the final hardware design
another_module(
#some_static_input $32
q $32
) {
// Static variables
#lower_half := #some_static_input[15:0] // Constant at initialization time, won't be part of final hardware design
// Define data block; #data is inferred to be $128
#data := [
c0cac01ac0cac01a
c0cac01ac0cac01a
]
// Swizzle #data; even bits followed by odd bits; swizzle(target, seq_size, step, block_size) (for details, see compiler/tape.mu, swizzle(...)).
#sw := swizzle(#data, 1, 2, 128)
// Divide #sw into two chunks ($64 each); chunk(target, chunk_index, number_of_chunks)
#part_a := chunk(#sw, 0, 2)
#part_b := chunk(#sw, 1, 2)
}
// Constants are global, and must be declared outside a module
some_constant $10 := 100
some_constant2 := 100 // Can omit type if you prefer
// Blackbox module declaration, for external modules. Example: a global buffer from Lattice iCE40 FPGA.
SB_GB blackbox(
USER_SIGNAL_TO_GLOBAL_BUFFER $1
) {
// Outputs are declared inside module body
out GLOBAL_BUFFER_OUTPUT $1
}
// Note: as you can see above, module inputs can be separated by newlines if you prefer. This also works for module instantiations.

15
examples/leds.w Normal file
View file

@ -0,0 +1,15 @@
top(clk $1, button $1) {
out reg leds $3
posedge clk {
if button {
leds <= '111
} else {
leds <= inc(a: leds).o
}
}
}
inc(a $3) {
out o := a + 1
}

View file

@ -0,0 +1,64 @@
// Scrolling test pattern for VGA mode 640x480x60hz
core(
clk $1 // clk must be 25.175mhz
) {
posedge clk {
reg x $10
reg y $10
reg frame $8
if x == 799 {
x <= 0
} else {
x <= x + 1
}
if x == 639 {
if y == 524 {
y <= 0
frame <= frame + 1
} else {
y <= y + 1
}
}
reg vis_x $1
reg vis_y $1
reg hs $1
reg vs $1
if x == 0 {
vis_x <= 1
}
if x == 640 {
vis_x <= 0
}
if x == 656 {
hs <= 1
}
if x == 752 {
hs <= 0
}
if y == 0 {
vis_y <= 1
}
if y == 480 {
vis_y <= 0
}
if y == 490 {
vs <= 1
}
if y == 492 {
vs <= 0
}
x_offset := x[5:0] + frame[5:0]
reg color <= { frame[7], y[5], x_offset[5] }
vis := vis_x & vis_y
// 4-bit output per color component
out vga_r := vis ? rep(color[0], 4) : '0000
out vga_g := vis ? rep(color[1], 4) : '0000
out vga_b := vis ? rep(color[2], 4) : '0000
out vga_hs := ~hs // hsync
out vga_vs := ~vs // vsync
}
}