mirror of
https://github.com/nickmqb/wyre.git
synced 2025-12-05 18:20:36 -08:00
Initial commit
This commit is contained in:
commit
032919c0c1
28 changed files with 6884 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
*
|
||||
!*/
|
||||
!.gitignore
|
||||
!.gitattributes
|
||||
!*.mu
|
||||
!*.args
|
||||
!*.h
|
||||
!*.md
|
||||
!*.w
|
||||
!LICENSE
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal 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
74
README.md
Normal 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
127
compiler/args_parser.mu
Normal 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
337
compiler/ast.mu
Normal 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
|
||||
}
|
||||
104
compiler/command_line_args_parser.mu
Normal file
104
compiler/command_line_args_parser.mu
Normal 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
17
compiler/crash_handler.mu
Normal 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
7
compiler/cstdlib.mu
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
exit(__status int) void #Foreign("exit")
|
||||
|
||||
CrashHandler {
|
||||
enable() {
|
||||
// Dummy
|
||||
}
|
||||
}
|
||||
609
compiler/emulator.mu
Normal file
609
compiler/emulator.mu
Normal 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
511
compiler/emulator_init.mu
Normal 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
82
compiler/error_helper.mu
Normal 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
8
compiler/external.h
Normal 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
123
compiler/indexer.mu
Normal 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
89
compiler/main.mu
Normal 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
1085
compiler/parser.mu
Normal file
File diff suppressed because it is too large
Load diff
109
compiler/range_finder.mu
Normal file
109
compiler/range_finder.mu
Normal 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
375
compiler/tape.mu
Normal 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
1409
compiler/type_checker.mu
Normal file
File diff suppressed because it is too large
Load diff
262
compiler/type_checker_builtins.mu
Normal file
262
compiler/type_checker_builtins.mu
Normal 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
|
||||
}
|
||||
}
|
||||
201
compiler/type_checker_utils.mu
Normal file
201
compiler/type_checker_utils.mu
Normal 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 _"))
|
||||
}
|
||||
}
|
||||
917
compiler/verilog_generator.mu
Normal file
917
compiler/verilog_generator.mu
Normal 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
27
compiler/wyre.args
Normal 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
100
docs/feature_overview.md
Normal 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
47
docs/getting_started.md
Normal 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
19
docs/language_guide.md
Normal 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
135
examples/features.w
Normal 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
15
examples/leds.w
Normal 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
|
||||
}
|
||||
64
examples/vga_test_pattern.w
Normal file
64
examples/vga_test_pattern.w
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue