Compare commits

...

111 Commits
master ... dev

Author SHA1 Message Date
Gabriel Tofvesson
644007b002
Update description of IR in README 2020-03-21 23:02:38 +01:00
Gabriel Tofvesson
a7aa9348ef Update description of IR in README 2020-03-21 22:39:57 +01:00
Gabriel Tofvesson
44328f7cb4 Improve README style 2020-03-21 21:09:55 +01:00
Gabriel Tofvesson
96f120a99d Simplify code 2019-05-07 17:28:34 +02:00
Gabriel Tofvesson
a21c5c3320 Update README 2019-05-06 10:31:33 +02:00
Gabriel Tofvesson
d9c7ec81c3 Increase bucket sizes 2019-05-06 10:22:55 +02:00
Gabriel Tofvesson
4e3c6d725b Implement script-based code generation 2019-05-06 10:22:37 +02:00
Gabriel Tofvesson
82d69b721e Add script support for program-memory generation 2019-05-05 22:11:09 +02:00
Gabriel Tofvesson
660f10c867 Add clarifying comments 2019-04-30 18:40:54 +02:00
Gabriel Tofvesson
d0b8f2d978 Optimize final bucketsort call 2019-04-30 18:30:14 +02:00
Gabriel Tofvesson
37f1afe452 Optimize boilerplate 2019-04-30 18:19:56 +02:00
Gabriel Tofvesson
a2420be4c8 Optimize for special cases 2019-04-30 17:59:03 +02:00
Gabriel Tofvesson
d71a2bdb97 Update README 2019-04-30 17:50:37 +02:00
Gabriel Tofvesson
13b4827969 Optimize for empty buckets and improve stability for full buckets 2019-04-30 17:50:04 +02:00
Gabriel Tofvesson
ae3d25bf65 Optimize sort4 based on practical tests 2019-04-29 23:16:26 +02:00
Gabriel Tofvesson
d067a72c38 Add clickable links to README 2019-04-29 22:45:03 +02:00
Gabriel Tofvesson
676109cbc3 Add clickable links to README 2019-04-29 22:43:29 +02:00
Gabriel Tofvesson
1adf41888a Add clickable links to README 2019-04-29 22:33:20 +02:00
Gabriel Tofvesson
c422e92ec7 Add clickable links to README 2019-04-29 22:11:46 +02:00
Gabriel Tofvesson
c6c866b86e Update README 2019-04-29 22:05:23 +02:00
Gabriel Tofvesson
7b060062af Improve bucketsort by removing redundant INSERTION_BOTTOM 2019-04-29 21:26:01 +02:00
Gabriel Tofvesson
800c7c065c Improve stability 2019-04-27 02:51:54 +02:00
Gabriel Tofvesson
9ad268b619 Prepare sort4 for possible post-sort optimization 2019-04-27 02:51:42 +02:00
Gabriel Tofvesson
926e572a53 Fully unrolled sort-loop 2019-04-27 02:34:22 +02:00
Gabriel Tofvesson
6b33921e8d Update README 2019-04-27 02:33:41 +02:00
Gabriel Tofvesson
ac78950784 Optimize bucketsort for unrolling by converting it to a zero-loss subroutine 2019-04-27 02:08:44 +02:00
Gabriel Tofvesson
40d1031392 Fix build script 2019-04-27 00:44:39 +02:00
Gabriel Tofvesson
0f5c426699 Optimize Sort4 2019-04-27 00:44:06 +02:00
Gabriel Tofvesson
fd188204b4 Implement M-bit selection of GR mux 2019-04-26 22:27:21 +02:00
Gabriel Tofvesson
2b9bdf568a Update build-script to be directory-independent 2019-04-26 22:27:21 +02:00
Gabriel Tofvesson
76c2fbb344 Move microcode projects to separate directory 2019-04-26 22:27:21 +02:00
Gabriel Tofvesson
50de68941d Update README 2019-04-24 23:00:00 +02:00
Gabriel Tofvesson
078f4b0736 Optimize bsrt2 2019-04-24 22:54:24 +02:00
Gabriel Tofvesson
99a3024b2a Implement bsrt using K1 jump table 2019-04-24 22:25:13 +02:00
Gabriel Tofvesson
4dd6bf6115 Update README 2019-04-24 20:11:08 +02:00
Gabriel Tofvesson
e67be67d57 Update README 2019-04-24 20:05:58 +02:00
Gabriel Tofvesson
708bd3a7b9 Update instructions according to new naming scheme 2019-04-24 20:05:58 +02:00
Gabriel Tofvesson
a441e4bad2 Rename arithmetic 32-bit shift/rotate instructions 2019-04-24 20:05:58 +02:00
Gabriel Tofvesson
8e337fe933 Implement special branch instructions 2019-04-24 19:56:24 +02:00
Gabriel Tofvesson
772472d932 Add example of K1 and K2 declarations to sort3 2019-04-24 19:47:05 +02:00
Gabriel Tofvesson
aa13da3219 Add support for K1 and K2 declarations to microcompiler 2019-04-24 19:46:42 +02:00
Gabriel Tofvesson
60b1278f6a Add support for K-table declarations to weaver 2019-04-24 19:03:57 +02:00
Gabriel Tofvesson
8b5d6e5ad4 Update README 2019-04-24 13:31:52 +02:00
Gabriel Tofvesson
fd6a442962 Implement bucket-sort without inline insertion sort 2019-04-24 13:26:19 +02:00
Gabriel Tofvesson
3ff3e8bd67 Update README 2019-04-19 23:52:45 +02:00
Gabriel Tofvesson
36c64a79bf Optimize bookkeeping and merge 2019-04-19 23:52:32 +02:00
Gabriel Tofvesson
150906141f Optimize merge 2019-04-19 23:52:09 +02:00
Gabriel Tofvesson
4db5192579 Implement optimized bucket-sort (1100 cycles) 2019-04-19 19:40:32 +02:00
Gabriel Tofvesson
59534c188f Update algorithm parameters 2019-04-19 12:27:18 +02:00
Gabriel Tofvesson
46db67a678 Clean code 2019-04-12 10:33:17 +02:00
GabrielTofvesson
d436b965d8 Implement dual-hash and merge-optimization 2019-04-11 21:01:13 +02:00
Gabriel Tofvesson
94b4e7760c Rearrange buckets 2019-04-11 18:47:20 +02:00
Gabriel Tofvesson
75efe84317 Update reference code 2019-04-11 10:02:41 +02:00
Gabriel Tofvesson
e4d45e470c Add LUT 2019-04-11 10:01:45 +02:00
Gabriel Tofvesson
ec2b562610 Redesign to sort signe values 2019-04-10 15:55:11 +02:00
GabrielTofvesson
db786624d7 Implement final merge of buckets 2019-04-10 02:59:33 +02:00
GabrielTofvesson
e48304d978 Implement bucket/insertion sort 2019-04-10 02:04:22 +02:00
Gabriel Tofvesson
51ef1d1d15 Merge branch 'dev' of gitlab.ida.liu.se:edvth289/TSEA28-Microkod into dev 2019-04-10 00:14:14 +02:00
Gabriel Tofvesson
66e1898a0d Start modifying BucketSort implementation 2019-04-10 00:12:48 +02:00
Gabriel Tofvesson
09ecc55898 Implement RESET uASM instruction 2019-04-10 00:11:55 +02:00
Gabriel Tofvesson
5586eec381 Fix number format bug for program-memory declarations 2019-04-10 00:09:23 +02:00
Gabriel Tofvesson
710927523e Weave random data into programs as part of compilation pipeline 2019-04-10 00:07:02 +02:00
Gabriel Tofvesson
0277e26422 Add clearer pipeline output info 2019-04-10 00:06:09 +02:00
Gabriel Tofvesson
00379b472b Add explicit memory type declaration to random generator 2019-04-10 00:04:33 +02:00
Gabriel Tofvesson
def8aa8e90 Add make rule 2019-04-10 00:03:17 +02:00
Edvard Thörnros
a977124736 Fix README.md 2019-04-09 15:23:56 +02:00
Gabriel Tofvesson
8012c9409e Update README.md 2019-04-08 22:16:02 +02:00
GabrielTofvesson
7edc6f7582 Update readme 2019-04-08 22:23:39 +02:00
GabrielTofvesson
9d0b798c20 Update readme 2019-04-08 22:16:54 +02:00
GabrielTofvesson
12e5aa4913 Remove NOP argument requirement 2019-04-08 22:12:11 +02:00
GabrielTofvesson
2a8eff03a0 Update gitignore to omit default build-script output file 2019-04-08 22:06:17 +02:00
GabrielTofvesson
866d4eeb6b Update bucket sort algorithm 2019-04-08 22:04:51 +02:00
GabrielTofvesson
2702d98253 Add support for program-memory initial-states in uASM file 2019-04-08 22:04:27 +02:00
GabrielTofvesson
cdc60b86d1 Add support for switching between program-memory and micro-memory 2019-04-08 22:03:33 +02:00
GabrielTofvesson
b240f29efa Update sorting algorithm to use constant definitions 2019-04-08 21:33:50 +02:00
GabrielTofvesson
9292c91352 Add support for constant definitions 2019-04-08 21:33:23 +02:00
GabrielTofvesson
680e6eaf23 Add documentation 2019-04-08 20:32:00 +02:00
Gabriel Tofvesson
d661391132 Start implementing Bucket Sort in uASM 2019-04-08 18:17:01 +02:00
Gabriel Tofvesson
bb5cb61556 Fix build-script formatting 2019-04-08 18:15:28 +02:00
Gabriel Tofvesson
5fcb5f2a7f Allow constant values for general ALU operations 2019-04-08 18:14:26 +02:00
Gabriel Tofvesson
343d028398 Update sample microprogram 2019-04-07 14:10:50 +02:00
Gabriel Tofvesson
6755bedf9a Fix instruction-merging with address references 2019-04-07 14:10:32 +02:00
Gabriel Tofvesson
35d15eb478 Update gitignore 2019-04-07 13:55:22 +02:00
Gabriel Tofvesson
ddd50eb165 Add automated build scripts 2019-04-07 13:54:18 +02:00
Gabriel Tofvesson
c2dc8dec0d Update sample microcode file 2019-04-07 13:53:52 +02:00
Gabriel Tofvesson
e900daa435 Remove debug print statements 2019-04-07 13:53:37 +02:00
Edvard Thörnros
d4638fc517 Merge branch 'dev' of gitlab.ida.liu.se:edvth289/TSEA28-Microkod into dev 2019-04-06 23:23:19 +02:00
Edvard Thörnros
59cbe4938a C++ sorting implementation. 2019-04-06 23:22:46 +02:00
Gabriel Tofvesson
ff924891f2 Add program to random-generate hex values 2019-04-06 23:16:13 +02:00
Gabriel Tofvesson
650cb46417 Add sample uASM program 2019-04-06 23:15:14 +02:00
Gabriel Tofvesson
2c9e19b6fe Add microcompiler 2019-04-06 23:14:45 +02:00
Gabriel Tofvesson
e2025ec55d Add support for microcode definition 2019-04-06 23:14:26 +02:00
Gabriel Tofvesson
fef6fa0ecb Update sorter algo 2019-04-06 23:13:55 +02:00
Gabriel Tofvesson
6fb40ff4e2 Decrement PC 2019-04-06 23:13:28 +02:00
Gabriel Tofvesson
d51763221a Add competing ASM type ;) 2019-04-05 23:21:42 +02:00
Gabriel Tofvesson
e6a0351942 Add machine-code weaver 2019-04-05 23:19:05 +02:00
Gabriel Tofvesson
e2f3ab756d Implement compilation from files 2019-04-05 21:34:49 +02:00
Gabriel Tofvesson
655832e672 Add gitignore 2019-04-05 21:27:19 +02:00
Gabriel Tofvesson
e6008f5d74 Finish alpha version of compiler 2019-04-05 21:26:21 +02:00
Gabriel Tofvesson
ca8716ac6d Add BLT and CMi 2019-04-05 17:12:09 +02:00
Edvard Thörnros
9b86c68ebc Fix bug in assembly. 2019-04-05 17:11:11 +02:00
Edvard Thörnros
883295bdfa Fix bug. 2019-04-05 17:00:21 +02:00
Edvard Thörnros
b94229c504 Add in CMI instruction. 2019-04-05 16:51:50 +02:00
Gabriel Tofvesson
52ae1cc1fa Update implementations 2019-04-05 16:24:26 +02:00
Gabriel Tofvesson
58f00e79d7 Merge branch 'dev' of gitlab.ida.liu.se:edvth289/TSEA28-Microkod into dev 2019-04-05 14:32:50 +02:00
Gabriel Tofvesson
3988f16ad4 Add compiler implemented in Kotlin 2019-04-05 14:06:30 +02:00
Gabriel Tofvesson
e40bc8675f Complete BEQ implementation 2019-04-05 12:55:39 +02:00
Gabriel Tofvesson
dd7169d6ca Fix HALT insn 2019-04-05 12:55:16 +02:00
Gabriel Tofvesson
5d30f44295 Remove redundant HALT insn 2019-04-05 12:53:45 +02:00
Gabriel Tofvesson
f5fdd52a71 Implement CMP and BEQ 2019-04-05 12:50:47 +02:00
Gabriel Tofvesson
4925cd654d Add CMP and BEQ 2019-04-05 12:50:20 +02:00
22 changed files with 3508 additions and 87 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
META-INF/
*.class
*.jar
*.out
build.mia

13
Makefile Normal file
View File

@ -0,0 +1,13 @@
KCOMPILER=kotlinc
KEXEC=kotlin
TEMPLATE=weaver.jar compiler.jar microcompiler.jar
$(TEMPLATE): %: $(%:.jar=.kt)
$(KCOMPILER) $(@:.jar=.kt) -d $@
all: weaver.jar compiler.jar microcompiler.jar
clean:
rm -f weaver.jar
rm -f compiler.jar
rm -f microcompiler.jar

616
README.md Normal file
View File

@ -0,0 +1,616 @@
# LMIA advanced development kit
This project was developed as a simple development kit for programming and
microprogramming the LMIA system.
## μASM instruction set
### NOP
No-operation. This wastes one clock cycle.
### MOV [regA] \[regB\]
Move value in *regA* to *regB*
*This operation uses the bus*
*If regB is [**LC**](#lc), no other [**LC**](#lc) operation can be specified in the
same cycle*
### MVN {[reg] | [const]}
Move the inverse of a value (from register or constant) into register
[**AR**](#ar).
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
### MVZ
Set [**AR**](#ar) to zero.
*Sets flags: [**Z**](#z), [**N**](#n)*
### ADD {[reg] | [const]}
Add value (from register or constant) to register [**AR**](#ar).
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
*Sets flags: [**Z**](#z), [**N**](#n), [**O**](#o), [**C**](#c)*
### SUB {[reg] | [const]}
Subtract value (from register or constant) from register [**AR**](#ar).
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
*Sets flags: [**Z**](#z), [**N**](#n), [**O**](#o), [**C**](#c)*
### AND {[reg] | [const]}
Perform bitwise AND with given value (from register or constant) and register
**AR**. *reg* is ignored and only prevalent due to a microcompiler quirk;
expect it to be removed in future releases.
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
*Sets flags: [**Z**](#z), [**N**](#n)*
### ORR {[reg] | [const]}
Perform bitwise Or with given value (from register or constant) and register
**AR**.
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
*Sets flags: [**Z**](#z), [**N**](#n)*
### ADN {[reg] | [const]}
Add value (from register or constant) to register [**AR**](#ar) without
updating flags.
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
### LSL
Performs a logical shift left of [**AR**](#ar).
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
*Sets flags: [**Z**](#z), [**N**](#n), [**C**](#c)*
### ISL
Performs a logical shift left of [**AR**](#ar) and [**HR**](#hr) as if they
were one 32-bit register where [**AR**](#ar) corresponds to the
most-significant bits.
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
*Sets flags: [**Z**](#z), [**N**](#n), [**C**](#c)*
### ASR
Performs an arithmetic shift right on [**AR**](#ar).
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
*Sets flags: [**Z**](#z), [**N**](#n), [**C**](#c)*
### ISR
Performs an arithmetic shift right of [**AR**](#ar) and [**HR**](#hr) as if
they were one 32-bit register where [**AR**](#ar) corresponds to the
most-significant bits.
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
*Sets flags: [**Z**](#z), [**N**](#n), [**C**](#c)*
### LSR
Performs a logical shift right of [**AR**](#ar).
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
*Sets flags: [**Z**](#z), [**N**](#n), [**C**](#c)*
### ROL
Performs an arithmetic rotate right on [**AR**](#ar).
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
*Sets flags: [**Z**](#z), [**N**](#n), [**C**](#c)*
### IRL
Performs an arithmetic shift left of [**AR**](#ar) and [**HR**](#hr) as if they
were one 32-bit register where [**AR**](#ar) corresponds to the
most-significant bits.
*This operation uses the bus*
*If a constant is passed, this operation cannot be parallellized*
*Sets flags: [**Z**](#z), [**N**](#n), [**C**](#c)*
### LCSET [const]
Set [**LC**](#lc) to value of constant.
### CONST [const]
Set [**AR**](#ar) to value of cosntant.
*This operation cannot be parallelized*
### INCPC
Increment value in [**PC**](#pc) by one.
### DECLC
Decrement value in [**LC**](#lc) by one.
### CALL [label]
Move value in [**uPC**](#upc) to [**uSP**](#usp) and set value in [**uPC**](#upc) to point
to the address of the given label.
### RET
Move value in [**uSP**](#usp) into [**uPC**](#upc).
### HALT
Stop execution and set value in [**uPC**](#upc) to **0**.
### BRA [label]
Perform an unconditional branch to the address of the given label.
### BNZ [label]
Branch to address of label if [**Z-flag**](#z) is **0**.
### BRZ [label]
Branch to address of label if [**Z-flag**](#z) is **1**.
### BRN [label]
Branch to address of label if [**N-flag**](#n) is **1**.
### BRC [label]
Branch to address of label if [**C-flag**](#c) is **1**.
### BRO [label]
Branch to address of label if [**O-flag**](#o) is **1**.
### BLS [label]
Branch to address of label if [**L-flag**](#l) is **1**.
### BNC [label]
Branch to address of label if [**C-flag**](#c) is **0**.
### BNO [label]
Branch to address of label if [**O-flag**](#o) is **0**.
### BOP
Branch to address specified by entry in optable pointed to by highest [**OP**](#op)
in [**IR**](#ir).
### BAM
Branch to address specified by entry in addressing mode pointed to by [**M**](#m)
in [**IR**](#ir).
### BST
Branch to start. This sets value in [**uPC**](#upc) to **0**.
### RESET [reg]
Sets all bits in the specified register to **1**.
*This operation uses the bus*
## Compiler/weaver directives
### \#define \[name] [const]
Define a compile-time constant. This will replace all (valid) constant
declarations with the given name with the value supplied here.
**NOTE**: Constant names are case-insensitive; i.e. *FOO* and *foo* are
functionally indistinguishable to the compiler.
### \#data \[address] [const]
Define an initial value in the program memory at the given address.
### $[label]
Define a compile-time label at the given position in the microprogram. Labels
can be referenced using an '@' symbol.
For example: `$BAR` would declare a label *BAR* which can be referenced with
`@BAR`.
**NOTE**: Label names are case-insensitive; i.e. `@FOO` and `@foo` are
functionally indistinguishable to the compiler.
### \#optable \[index] {[label] | [const]}
Declare an entry in the opcode jump table (**K1**).
**NOTE**: The given index must be at most 15 and at least 0 and in the case of
a constant being supplied as the value, this value my not be negative nor be
greater than 127.
### \#amode \[index] {[label] | [const]}
Declare an entry in the addressing mode jump table (**K2**).
**NOTE**: The given index must be at most 3 and at least 0 and in the case of
a constant being supplied as the value, this value my not be negative nor be
greater than 127.
### \#pmgen [python]
Define a script to be run for each address in program memory. This can be used
to generate program memory value mappings more efficiently than manually
defining values. The script is run in a for-loop with the index variable
`address`. I.e. the variable `address` can be used in the specified script to
refer to the current program memory address. To emit a value, simply print a
string in the format "\[address] [value]" where the value can be in the range
-32768 to 65535 (inclusive).
### \#emit
Define a script to emit uASM code at compile-time. Each following line preceded
by a `>` will be included in the script; the first line not preceded by `>`
(or EOF) will define the end of the script. To emit code, simply print the code
that should be emitted.
Example:
```
mov ar asr
#emit
>for i in range(4):
> print("lsl; mov ar ir")
> print("add pm")
sub gr
```
the above code will be converted into the the following code by the preprocessor:
```
mov ar asr
lsl; mov ar ir
add pm
lsl; mov ar ir
add pm
lsl; mov ar ir
add pm
lsl; mov ar ir
add pm
sub gr
```
## Flags
Flags - *aside from [**L**](#l)* - are set based on ALU operations, so they depend
on [**AR**](#ar) and the **BUS**. Henceforth, unless otherwise implied or
stated, [**AR**](#ar) will refer to the state/value of [**AR**](#ar) *after* an
ALU operation. Flags retain their state until an instruction which is declared
as modifying said flag is executed.
### Z
Set if [**AR**](#ar) == **0**.
### N
Set if sign bit in [**AR**](#ar) is set.
### O
Set if sign of [**AR**](#ar) differs from signs of both [**AR**](#ar) *before*
the arithmetic operation and of the **BUS**.
### C
Set if [**AR**](#ar) is less than or equal to [**AR**](#ar) *before* the
arithmetic operation.
### L
Set if [**LC**](#lc) == **0**.
## Registers
Available registers for read/write operations are documented below. Unless
otherwise specified, the registers are directly accessible via the bus for read
and write operations.
### AR
The accumulator register. This register can only be written to as the result of
an ALU operation. This is to say, that **AR** is indirectly writable via the ALU,
but is nonetheless directly readable via the bus.
### PM
The program-memory pseudo-register allows you to read/write values from the
currently accessed program memory address (see [**ASR**](#asr-1)).
### ASR
The address register; this register is used to specify which address of
program-memory to be accessible via [**PM**](#pm).
**NOTE**: This register cannot be read.
### HR
The help register. This is a general-purpose register which is useful for
storing ephemeral or intermediate values during a computation.
### IR
The instruction register. This register offers extra functionality such as
**K1**- and **K2**-table addressing via the [**OP**](#op) and [**M**](#m)
bits respectively. The [**GRx**](#grx) and [**M**](#m) bits can also be used
to address a specific general register via the [**GR**](#gr) multiplexer.
Bit-level layout of IR (from MSB to LSB, left-to-right):
| **OP** | **GRx** | **M** | **ADR** |
|:------:|:-------:|:------:|:-------:|
| 4 bits | 2 bits | 2 bits | 8 bits |
##### OP
Machine instruction. Indexes **K1**-table.
##### GRx
[**GR**](#gr) multiplexer selector.
##### M
Addressing mode. Indexes **K2**-table.
##### ADR
[**ASR**](#asr-1) address argument.
### GR
This is a shorthand for accessing the general register currently made available
by the GR multiplexer when said MUX is controlled by the [**GRx**](#grx) bits in
[**IR**](#ir).
**NOTE**: Only one GR can be accessed per cycle. Which register this is (of
the four available registers) is determined by the value in [**IR**](#ir).
### GRM
This is a shorthand for accessing the general register currently made available
by the GR multiplexer when said MUX is controlled by the [**M**](#m) bits in
[**IR**](#ir).
**NOTE**: Only one GR can be accessed per cycle. Which register this is (of
the four available registers) is determined by the value in [**IR**](#ir).
### LC
The loopcounter register is a special register that can only be modified in
three ways: all three ways are dictated by the L-field in the microprogram.
Additionally, the counter can remain unchanged by simply not specifying an
action to take for the register. The three ways of modifying it are as follows:
* Decrement counter by one. The corresponding uASM instruction for this is
`declc`
* Load value from bus. The uASM instruction being `mov [reg] LC`
* Load a 7-bit constant from micromemory. The uASM instruction for this is
`lcset [const]`
**NOTE**: LC cannot be read and can only be written to as detailed above. The
value in LC can, though, to some degree be inferred from the [**L-flag**](#l)
(see [*Flags*](#flags)).
### PC
The program counter. This register is only 8 bits wide. Writing a value larger
than 8 bits to this register will simply truncate the binary string to 8 bits.
Attemping to read a value from PC to a register of a larger width will result
in the value in PC populating the lowesr 8 bits and the rest being filled with
0's.
### uPC
The microprogram counter; sometimes also referred to as **MyPC**. This register
cannot directly be read. It can, though, be written to in a variety of ways.
These have been specified as *branch* instructions (such as [`BRA`](#bra-label)), as
well as subroutine instructions (such as [`ret`](#ret)).
**NOTE**: This register is not connected to the bus.
### uSP
The microstack pointer; sometimes also referred to as **MySPC**. This register
cannot be directly read or written to. To write to it, a call to a subroutine
must be issued (using [`call [label]`](#call-label)). The only way to "read" this
register is to issue a subroutine-return instruction ([`ret`](#ret)) which
copies the value in this register to [**uPC**](#upc).
**NOTE**: This register is not connected to the bus. The value in this register
cannot be directly acted upon insofar as it cannot be directly read or copied
to any other register than [**uPC**](#upc), meaning that the actual value in
this register (and consequently in [**uPC**](#upc) aswell) cannot be directly
accessed or transformed. I.e. the value in this register cannot be copied to,
for example, a general register and thus, its value can only be inferred, not
directly measured.
## Sorting algorithms
For the sorting competition, we have chosen to focus on implementing bucketsort
with an inline insertionsort when inserting values into corresponding buckets.
### bucksort.uc
This was the first attempt at a sorting implementation in uASM. It doesn't do
more than a simple hash and possibly updating some bucket-specific values.
Nonetheless, it formed a clear basis for future implementations.
Pros:
* N/A
Cons:
* N/A
**Average cycle count: N/A**
### bsrt.uc
The first successful uASM bucketsort implementation. It uses the aforementioned
inline insertionsort. Additionally, it employs a lookup table for pointing to
buckets; this had the benefit of significantly decreasing the cycles required
to hash values, as well as allowing for buckets of sizes other than even
exponents of 2 (as opposed to other implementations). On top of this, it used a
parallel-hashing system which allowed the rotation step of the hash algorithm
to be applied to two elements of the list (to be sorted) at once, further
decreasing the amount of cycles required to perform a hash (per element).
Pros:
* Variable bucket size
* Arbitrary bucket arrangement
* Parallel-hash implementation
* Uses the most recent merge algorithm (rated around 400 cycles)
Cons:
* Heavy bookkeeping due to lookup table paired with parallel-hash
* Inefficiency in merge due to lookup table (average loss of 80 cycles)
**Average cycle count: 1250**
### sort2.uc
A second iteration of the *bsrt* implementation, this one refines the dual-hash
by omitting the lookup table. This means that bookkeeping can be minimized, as
it was only necessary due to a lack of available registers. This, of course,
comes at the cost of not being able to have variable-sized buckets; i.e. their
sizes must an exponent of 2. A minor optimization that was created for this
algorithm was the hash-based bookkeeping, wherein a small optimization to
bookkeeping could be done during the hashing of the list elements. Any
bookkeeping which does not require the ALU can, in fact, be performed during the
hashing, since the rotation steps only require AR and HR to be untouched. All
other operations are permitted.
Pros:
* Merge: (320-330 cycles)
* Direct addressing fits optimally due to register limitations
* Optimized regster allocations
* Minimal bookkeeping since it is mostly performed during the hashing operation
Cons:
* Bucket sizes must be exponents of 2
* Fixed bucket indices based on hash
* Two merge operations required (negative + positive)
* 95 unused program-memory addresses
**Average cycle count: 1100**
### sort3.uc
A third iteration of the *bsrt* implementation: this time entirely scrapping
the bucket header, opting to delegate this behaviour to the lookup-table itself.
I.e. the lookup table no long points to the start of a bucket (as this can be
inferred later), but rather points to the last element of the bucket.
Additionally, this implementation scraps the insertion-sort performed in the
bucket sort, delegating this task to the merge stage of the sorting algorithm.
Pros:
* Constant-time bucket sort (406 cycles)
* More optimally used LUT
* No bookkeeping during bucket-sort
Cons:
* Non-constant-time merge
* Optimized LUT requires marginally more arithmetic operations over other variants
**Average cycle count: N/A (not fully implemented)**
### bsrt2.uc
A fourth iteration developed in parallel with *sort3* as a proof-of-concept of
the recently designed lookup-table replacement for hashing. This version is an
almost direct copy of *bsrt*, except without a LUT, a K1 jump-table and absolute
sizes in the bucket headers (as opposed to a pointer to the last element of a
bucket).
Pros:
* Highly optimized K1 jump-table
* Subroutined jump-table for possible reuse elsewhere
* 13 elements per bucket (+1 over *bsrt*)
* Low instruction count
Cons:
* Only sorts one element per iteration
**Average cycle count: 1050**
### sort4.uc
A fifth iteration of the common bucketsort algorithm. This one is, as its name
implies, based on the *sort2* algorithm. It improves upon it by making heavy
use of the `call` and `ret` instructions. I.e. by moving the entire bucketsort
implementation to a subroutine, it effectively allows 16 calls to sort values
per iteration of the outermost loop. This conversion to a subroutine is done at
zero cost to performance, as it exploits the fact that a call to a jumptable-
based hashing algorithm is made and rather than returning to the algorithm
after the hashing has taken place, the hash-table simply jumps to the
insertionsort immediately, after which a `ret` is used to return back to normal
execution. This has the effect of reducing a bucketsort of a single value be
two instructions to dereference the value to sort, during which a call to the
subroutine is made and after sorting, it continues execution at the instruction
immediately after the dereference.
Pros:
* Optimized K1 jump-table with inline bucketsort
* Highly un-rollable bucketsort implementation
* Highly efficient bus use (almost always saturated)
* Dynamic bucket placement, allowing for very fast merge operations
* Efficient use of general registers to reduce arithmetic operations requiring
constant values
Cons:
* Maximum of 6 elements per bucket
* 96 unused program-memory addresses (+1 per bucket)
**Average cycle count: 758**

172
bucksort.cpp Normal file
View File

@ -0,0 +1,172 @@
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define BUCKETS 0b1000
#define BUCKET_SIZE 20
#define LENGTH 32
int count = 0;
bool LT(int a, int b)
{
count++;
return a < b;
}
// TODO: parallellize rotate-left instruction (BSL)
// This can be done by partially unrolling the loop ;)
// Bucket/Insertion sort (except final merge). C-style pseudocode
void bi_sort(short *data){
short *buckets;
short pc_, ir_, ar, gr, hr, lc_, ir_;
for(pc_ = 0xE0; pc_ != 0; ++pc_){
// Fetch and hash
ar = data[pc_];
gr = data[pc_];
ar = ar >> 9;
ar = ar & 0b1111000;
hr = ar;
ar = buckets[ar]; // Index 0 is pointer to end of array
++ar;
buckets[hr] = ar; // Increment pointer (since we're inserting a new value)
--ar;
ir_ = pc_; // Push pc
pc_ = ar; // Copy start index to pc (for fast indexing)
ar = ar - hr; // Compute length
lc_ = ar; // Loop AR-times
--lc_;
while(lc_ != 0){
++pc_;
ar = data[pc_];
if((ar -= gr) < 0){
ar += gr;
data[pc_] = gr;
// Insert here
while(lc_ != 0){
++pc_;
gr = data[pc_];
data[pc_] = ar;
ar = gr;
--lc_;
}
goto END;
}
--lc_;
}
++pc_;
data[pc_] = gr; // GR was the biggest. Insert it at the end
END:
pc_ = ir_; // Pop pc
}
}
void sort(short *data, int length)
{
short buckets[BUCKETS][BUCKET_SIZE] = {};
short a_, pc, c, d;
// Bucketsort
for (a_ = 0; a_ < length; a_++)
{
c = data[a_];
d = (c >> 13) & 0b111;
pc = buckets[d][0];
pc++;
buckets[d][0] = pc;
buckets[d][pc] = c;
}
for (short q = 0; q < BUCKETS; q++)
{
short *curr = buckets[q];
int length = curr[0];
printf("buck: %d\tlength: %hd\n", q, curr[0]);
for (short i = 0; i < length; i++)
{
printf("%hd, ", curr[i + 1]);
}
printf("\n");
}
#if 0
i 1
while i < length(A)
x A[i]
j i - 1
while j >= 0 and A[j] > x
A[j+1] A[j]
j j - 1
end while
A[j+1] x
i i + 1
end while
#endif
// Insertion Sort
for (short q = 0; q < BUCKETS; q++)
{
short length = buckets[q][0];
short *curr = buckets[q] + 1;
a_ = 1;
while (a_ < length)
{
c = curr[a_];
pc = a_ - 1;
while (pc >= 0 && LT(curr[pc], c))
{
curr[pc+1] = curr[pc];
pc--;
}
curr[pc+1] = c;
a_++;
}
}
// Merge the buckets
pc = 0;
int h = 0b100;
for (short q = 0; q < BUCKETS; q++)
{
short *curr = buckets[h];
h = (h + 1) & 0b111;
a_ = curr[0];
while (a_ >= 1)
{
data[pc] = curr[a_];
pc++;
a_--;
}
}
}
int main(int *argc, char **argv)
{
short data[LENGTH] = {};
srand(clock());
for (int i = 0; i < LENGTH; i++)
{
data[i] = rand() % 0xFFFF;
}
sort(data, LENGTH);
printf("Num compares: %d\n", count);
for (int i = 0; i < LENGTH; i++)
{
printf("%hd, ", data[i] & 0xFFFF);
}
printf("\n");
return 0;
}

121
build.sh Executable file
View File

@ -0,0 +1,121 @@
#!/bin/bash
MODULE_PATH=$(realpath ${0%/*})
# JARs
UCOMP=microcompiler.jar
COMP=compiler.jar
WEAVER=weaver.jar
# Main classes
UCOMPMAIN=MicrocompilerKt
COMPMAIN=CompilerKt
WEAVERMAIN=WeaverKt
# Random number generator
RGEN=rand_gen.py
# Intermediate file
INTER=comp.out
usage(){
echo -e "Usage:\n\t$0 [TARGET] {TYPE | OUTFILE}\n\t$0 [TARGET] [MICROTARGET] [OUTFILE]\n\t$0 [TARGET] [TYPE] [OUTFILE] [COMBINE]\n\t$0 [TARGET] asm [OUTFILE] [COMBINE] [MICROTARGET]\n\nWhere:\n\tTARGET: main compilation target file\n\tTYPE: either \"asm\" or \"micro\"\n\tOUTFILE: output .mia file\n\tCOMBINE: existing .mia file to combine compilation output with\n\tMICROTARGET: explicit microinstruction target"
}
path_of(){
echo "$MODULE_PATH/$1"
}
if [ $# -eq 0 ]; then
>&2 echo "Not enough arguments!"
usage
exit
fi
TARGET=$1
case $# in
1)
TYPE="asm"
OUTPUT="build.mia"
;;
2)
if [ "$2" = "asm" ] || [ "$2" = "micro" ] ; then
TYPE=$2
OUTPUT="build.mia"
else
TYPE="asm"
OUTPUT=$2
fi
;;
3)
if [ "$2" = "asm" ] || [ "$2" = "micro" ]; then
TYPE=$2
OUTPUT=$3
else
TYPE="asm"
OUTPUT=$2
MICRO=$3
fi
;;
4)
TYPE=$2
OUTPUT=$3
COMBINE=$4
;;
5)
if [ "$2" != "asm" ]; then
>&2 echo "TYPE must be asm"
usage
exit
fi
TYPE=$2
OUTPUT=$3
COMBINE=$4
MICRO=$5
;;
*)
>&2 echo "Too many arguments!"
usage
exit
;;
esac
(cd $MODULE_PATH && make all)
if [ "$TYPE" = "asm" ]; then
KJAR=$COMP
KCLASS=$COMPMAIN
else
KJAR=$UCOMP
KCLASS=$UCOMPMAIN
fi
kotlin -classpath $(path_of $KJAR) $KCLASS $TARGET > $INTER || exit
python "$(path_of $RGEN)" >> $INTER
if [ "$COMBINE" = "" ]; then
if [ "$MICRO" != "" ]; then
kotlin -classpath $(path_of $UCOMP) $UCOMPMAIN $MICRO >> $INTER || exit
fi
kotlin -classpath $(path_of $WEAVER) $WEAVERMAIN $INTER $OUTPUT || exit
else
if [ "$MICRO" != "" ]; then
kotlin -classpath $(path_of $UCOMP) $UCOMPMAIN $MICRO >> $INTER || exit
fi
kotlin -classpath $(path_of $WEAVERJAR) $WEAVERMAIN $INTER $COMBINE $OUTPUT || exit
fi
# Remove intermediate compilation file
rm -f $INTER
echo "Compiled successfully to $OUTPUT"

View File

@ -1,31 +1,39 @@
AND GR1, 0
#AND GR1, 0
#
#LOAD GR0, *0xFE
#AND GR0, 0xF
#STORE GR0, *0xFF
#ADD GR1, *0xFF
#
#LOAD GR0, *0xFE
#LSR GR0, *4
#AND GR0, 0xF
#STORE GR0, *0xFF
#ADD GR1, *0xFF
#
#LOAD GR0, *0xFE
#LSR GR0, *8
#AND GR0, 0xF
#STORE GR0, *0xFF
#ADD GR1, *0xFF
#
#LOAD GR0, *0xFE
#LSR GR0, *12
#AND GR0, 0xF
#STORE GR0, *0xFF
#ADD GR1, *0xFF
#
#STORE GR1, *0xFF
#
LOAD GR0, *0x00
LOAD GR1, *0x00
LOAD GR0, *0xFE
AND GR0, 0xF
STORE GR0, *0xFF
ADD GR1, *0xFF
CMP GR0, GR1
BEQ @TEST
LOAD GR0, *0xFE
LSR GR0, *4
AND GR0, 0xF
STORE GR0, *0xFF
ADD GR1, *0xFF
LOAD GR0, *0xFF
LOAD GR0, *0xFE
LSR GR0, *8
AND GR0, 0xF
STORE GR0, *0xFF
ADD GR1, *0xFF
TEST:
HALT
LOAD GR0, *0xFE
LSR GR0, *12
AND GR0, 0xF
STORE GR0, *0xFF
ADD GR1, *0xFF
STORE GR1, *0xFF
HALT GR0, *0
STORE GR1, *0xFF

306
compiler.kt Normal file
View File

@ -0,0 +1,306 @@
import java.io.*
enum class OpCode(val opcode: Int, val useM: Boolean, val useADR: Boolean, val useReg: Boolean = true) {
LOAD(0, true, true),
STORE(1, true, true),
ADD(2, true, true),
SUB(3, true, true),
AND(4, true, true),
LSR(5, true, true),
BRA(6, false, true),
BNE(7, false, true),
CMP(8, true, false),
BEQ(9, false, true),
HALT(15, false, false, false);
companion object {
fun fromString(name: String) = OpCode.values().firstOrNull{ it.name == name }
}
}
enum class Mode(val id: Int) {
DIRECT(0), IMMEDIATE(1), INDIRECT(2), INDEXED(3)
}
abstract class Instruction(val words: Int) {
abstract fun getData(insns: Iterable<Instruction>): ShortArray
}
class Label(val name: String): Instruction(0) {
override fun getData(insns: Iterable<Instruction>) = ShortArray(0)
}
class Operation(val code: OpCode, val reg: Int, val m: Mode, val adr: AddressReference, val immediate: Short? = null): Instruction(if(m == Mode.IMMEDIATE) 2 else 1) {
constructor(code: OpCode, reg: Int): this(code, reg, Mode.DIRECT, AddressReference(0)){
if(code.useM || code.useADR)
throw IllegalArgumentException("Not enough parameters specified for instruction: ${code.name}")
}
constructor(code: OpCode, m: Mode, adr: AddressReference, immediate: Short? = null): this(code, 0, m, adr, immediate){
if(code.useReg)
throw IllegalArgumentException("Not enough parameters specified for instruction: ${code.name}")
}
constructor(code: OpCode): this(code, 0, Mode.DIRECT, AddressReference(0)){
if(code.useM || code.useADR || code.useReg)
throw IllegalArgumentException("Not enough parameters specified for instruction: ${code.name}")
}
init {
if(m == Mode.IMMEDIATE && immediate == null)
throw IllegalArgumentException("No immediate argument passed!")
}
override fun getData(insns: Iterable<Instruction>): ShortArray {
val array = ShortArray(words)
array[0] = code.opcode
.and(0b1111)
.shl(12)
.or(
if(code.useReg) reg.and(0b11).shl(10)
else 0
)
.or(
if(code.useM) m.id.and(0b11).shl(8)
else 0
)
.or(
if(code.useADR) adr.getAddress(insns).and(0b11111111)
else 0
)
.toShort()
if(m == Mode.IMMEDIATE) array[1] = immediate!!
return array
}
}
class AddressReference(private val label: Label?, private val absolute: Int?) {
constructor(label: Label): this(label, null)
constructor(absolute: Int): this(null, absolute)
fun getAddress(insns: Iterable<Instruction>): Int {
if(absolute != null) return absolute
var addrOff = 0
for(insn in insns)
if(insn == label) return addrOff
else addrOff += insn.words
throw RuntimeException("Found reference to undeclared label!")
}
}
class CompilationUnit {
private val instructions = ArrayList<Instruction>()
private val labels = ArrayList<Label>()
fun declareLabel(name: String) {
if(instructions.firstOrNull{ it is Label && it.name == name } != null)
throw IllegalStateException("Attempt to declare the same label twice!")
registerInstruction(getLabel(name))
}
fun getLabel(name: String): Label {
if(labels.firstOrNull{ it.name == name } == null)
labels.add(Label(name))
return labels.first{ it.name == name }
}
fun registerInstruction(insn: Instruction){
instructions.add(insn)
}
fun compile(): ShortArray {
var dat = 0
for(insn in instructions)
dat += insn.words
if(dat > 256)
throw RuntimeException("Instruction overflow")
val rawData = ShortArray(dat)
var index = 0
for(insn in instructions)
for(short in insn.getData(instructions))
rawData[index++] = short
return rawData
}
}
enum class ArgType {
REG, LABEL, INDEX, NUMBER
}
fun parseRegister(arg: String): Int? {
if(!arg.toUpperCase().startsWith("GR")) return null
try{
val reg = Integer.parseInt(arg.substring(2))
if(reg > 3 || reg < 0)
throw IllegalArgumentException("Register index out of range!")
return reg
}catch(e: NumberFormatException){
throw IllegalArgumentException("Invalid register value")
}
}
fun parseLabelReference(arg: String, unit: CompilationUnit): Pair<Label, Mode>? {
if(arg.length < 2 || Character.isDigit(arg[1])) return null
if(arg.startsWith("@")) return unit.getLabel(arg.substring(1)) to Mode.DIRECT
if(arg.startsWith("*")) return unit.getLabel(arg.substring(1)) to Mode.INDIRECT
return null
}
fun parseIndex(arg: String): Pair<Int, Mode>? {
if(arg.startsWith("[") && arg.endsWith("]")){
val literal = arg.substring(1, arg.length - 1)
return parseNumber(literal) to Mode.INDEXED
}
return null
}
fun parseNumber(literal: String): Int {
return if(literal.startsWith("0") && literal.length > 1){
when(literal[1]){
'x' -> Integer.parseInt(literal.substring(2), 16)
'b' -> Integer.parseInt(literal.substring(2), 2)
else -> Integer.parseInt(literal.substring(2), 8)
}
}else{
Integer.parseInt(literal)
}
}
fun checkProperSize(number: Int, max: Int): Int {
if(number > max || number < 0)
throw IllegalArgumentException("Parsed value out of range!")
return number
}
fun parseNumberLiteral(arg: String): Pair<Int, Mode>? {
if(arg.startsWith("$")) return checkProperSize(parseNumber(arg.substring(1)), 65535) to Mode.IMMEDIATE
if(arg.startsWith("*")) return checkProperSize(parseNumber(arg.substring(1)), 255) to Mode.INDIRECT
return try{
checkProperSize(parseNumber(arg), 255) to Mode.DIRECT
}catch(e: NumberFormatException){
null
}
}
fun parseInstruction(line: String, unit: CompilationUnit): Instruction? {
if(line.length == 0) return null
if(line.replace(" ", "").endsWith(":")){
val label = line.substring(0, line.length - 1)
if(label.toUpperCase().startsWith("GR") || label.startsWith("$") || label.startsWith("*") || label.startsWith("@"))
throw IllegalArgumentException("Label uses reserved prefix")
return unit.getLabel(label)
}
val firstSpace = line.indexOf(" ")
if(firstSpace == -1){
val opcode = OpCode.fromString(line.toUpperCase())
if(opcode == null)
throw IllegalArgumentException("Unknown instruction: $line")
if(opcode.useReg || opcode.useADR || opcode.useM)
throw IllegalArgumentException("Not enough arguments passed to $line")
return Operation(opcode)
}else{
val opcode = OpCode.fromString(line.substring(0, firstSpace).toUpperCase())
if(opcode == null)
throw IllegalArgumentException("Unknown instruction: $line")
val args = line.substring(firstSpace + 1).replace(" ", "").split(",").toTypedArray()
when(args.size){
1 -> {
var arg = parseArguments(args[0], null, unit)
return when(arg.first.first){
ArgType.REG -> Operation(opcode, arg.first.second as Int)
ArgType.LABEL -> Operation(opcode, (arg.first.second as Pair<Label, Mode>).second, AddressReference((arg.first.second as Pair<Label, Mode>).first))
ArgType.INDEX -> Operation(opcode, (arg.first.second as Pair<Int, Mode>).second, AddressReference((arg.first.second as Pair<Int, Mode>).first))
ArgType.NUMBER -> if((arg.first.second as Pair<Int, Mode>).second == Mode.IMMEDIATE)
Operation(opcode, (arg.first.second as Pair<Int, Mode>).second, AddressReference(0), (arg.first.second as Pair<Int, Mode>).first.toShort())
else Operation(opcode, (arg.first.second as Pair<Int, Mode>).second, AddressReference((arg.first.second as Pair<Int, Mode>).first))
}
}
2 -> {
val arg = parseArguments(args[0], args[1], unit)
val first = arg.first
val second = arg.second!!
if(first.first != ArgType.REG)
throw IllegalArgumentException("First argument must be a register")
return when(second.first){
ArgType.REG -> Operation(opcode, first.second as Int, Mode.values()[second.second as Int], AddressReference(0), 0)
ArgType.LABEL -> Operation(opcode, first.second as Int, (second.second as Pair<Label, Mode>).second, AddressReference((second.second as Pair<Label, Mode>).first))
ArgType.NUMBER -> if((second.second as Pair<Int, Mode>).second == Mode.IMMEDIATE)
Operation(opcode, first.second as Int, (second.second as Pair<Int, Mode>).second, AddressReference(0), (second.second as Pair<Int, Mode>).first.toShort())
else Operation(opcode, first.second as Int, (second.second as Pair<Int, Mode>).second, AddressReference((second.second as Pair<Int, Mode>).first))
ArgType.INDEX -> Operation(opcode, first.second as Int, (second.second as Pair<Int, Mode>).second, AddressReference((second.second as Pair<Int, Mode>).first))
}
}
else -> throw IllegalArgumentException("Too many arguments specified")
}
}
}
fun parseArgument(arg: String, unit: CompilationUnit): Pair<ArgType, Any>? {
var resolve: Any? = parseRegister(arg)
if(resolve != null) return ArgType.REG to resolve
resolve = parseIndex(arg)
if(resolve != null) return ArgType.INDEX to resolve
resolve = parseLabelReference(arg, unit)
if(resolve != null) return ArgType.LABEL to resolve
resolve = parseNumberLiteral(arg)
if(resolve != null) return ArgType.NUMBER to resolve
return null // Unresolved
}
fun parseArguments(arg0: String, arg1: String?, unit: CompilationUnit): Pair<Pair<ArgType, Any>, Pair<ArgType, Any>?> {
val resolve0 = parseArgument(arg0, unit)
if(resolve0 == null) throw IllegalArgumentException("Invalid argument: $arg0")
val resolve1 = if(arg1 != null) parseArgument(arg1, unit) else null
if(arg1 != null && resolve1 == null) throw IllegalArgumentException("Invalid argument: $arg0")
return resolve0 to resolve1
}
fun parseInstructions(fileData: String): ShortArray {
val unit = CompilationUnit()
val lines = fileData.replace("\r", "").replace("\t", "").split('\n').toTypedArray()
for(index in lines.indices){
val commentIndex = lines[index].indexOf("#")
if(commentIndex > 0)
lines[index] = lines[index].substring(0, commentIndex)
try{
val insn = parseInstruction(lines[index], unit)
if(insn != null) unit.registerInstruction(insn)
}catch(e: Exception){
print("An error occurred when compiling (line $index)")
val message = e.message
if(message != null) print(":\n\t$message")
println()
}
}
return unit.compile()
}
fun main(args: Array<String>){
// Ensure correct argument length 'n stuff
if(args.size != 1){
System.err.println("Invalid argument length!")
System.exit(-1)
}
val file = File(args[0])
// Make sure we have a valid file
if(!file.isFile){
System.err.println("Given file doesn't exist!")
System.exit(-2)
}
for(insn in parseInstructions(file.readText()))
println(insn.toUHex())
}
fun Short.toUHex() = toInt().and(0xFFFF.toInt()).or(1.shl(30)).toString(16).substring(4)

View File

@ -3,14 +3,16 @@
# Compiles ED-Assembly to Machinecode
#
# LOAD GRx, M, ADR
# STORE GRx, M, ADR
# ADD GRx, M, ADR
# SUB GRx, M, ADR
# AND GRx, M, ADR
# LSR GRx, M, Y # STEG
# LOAD GRx, ADR
# STORE GRx, ADR
# ADD GRx, ADR
# SUB GRx, ADR
# AND GRx, ADR
# LSR GRx, Y # STEG
# BRA ADR
# BNE ADR
# BEQ ADR
# CMP GRx, GRy
#
# One machine instruction
@ -125,6 +127,10 @@ OPCODE_TABLE = {
"LSR" : 0b0101,
"BRA" : 0b0110,
"BNE" : 0b0111,
"CMP" : 0b1000,
"BEQ" : 0b1001,
"BLT" : 0b1010,
"CMI" : 0b1011,
"HALT" : 0b1111,
}
@ -229,6 +235,34 @@ def parse_instruction(args):
"""
Parse an assembly instruction into an instruction object.
"""
if args[0] in ["HALT"]:
return Instruction(OPCODE_TABLE[args[0]], 0, 0, 0)
if args[0] in ["LOAD", "STORE", "ADD", "SUB", "AND", "LSR"]:
return parse_tripple_instuction(args)
if args[0] in ["BRA", "BNE", "BEQ", "BLT"]:
return parse_jump(args)
if args[0] in ["CMP"]:
return parse_dual_registers(args)
if args[0] in ["CMI"]:
return parse_compare_imediate(args)
else:
print("Foregotten instruction: ", args[0])
assert False
def parse_compare_imediate(args):
if len(args) < 3:
raise SyntaxError("Not enough arguments.")
if len(args) > 3:
raise SyntaxError("Trash at end of line \"{}\"".format(" ".join(args[3:])))
opcode = OPCODE_TABLE[args[0]]
if args[1].upper() not in REGISTERS:
raise SyntaxError("Invalid register name" + args[1])
register = REGISTERS[args[1].upper()]
address, mode = parse_operand(args[2])
return Instruction(opcode, register, ADDRESS_MODES["DIRECT"], address)
def parse_tripple_instuction(args):
if len(args) < 3:
raise SyntaxError("Not enough arguments.")
if len(args) > 3:
@ -240,6 +274,26 @@ def parse_instruction(args):
address, mode = parse_operand(args[2])
return Instruction(opcode, register, mode, address)
def parse_jump(args):
if len(args) < 2:
raise SyntaxError("Not enough arguments.")
if len(args) > 2:
raise SyntaxError("Trash at end of line \"{}\"".format(" ".join(args[3:])))
opcode = OPCODE_TABLE[args[0]]
address, mode = parse_operand(args[1])
return Instruction(opcode, 0, ADDRESS_MODES["DIRECT"], address)
def parse_dual_registers(args):
if len(args) < 3:
raise SyntaxError("Not enough arguments.")
if len(args) > 3:
raise SyntaxError("Trash at end of line \"{}\"".format(" ".join(args[3:])))
opcode = OPCODE_TABLE[args[0]]
if args[1].upper() not in REGISTERS:
raise SyntaxError("Invalid register name" + args[1])
register_a = REGISTERS[args[1].upper()]
register_b = REGISTERS[args[2].upper()]
return Instruction(opcode, register_a, register_b, 0)
def main():
success = True

110
labb3.mia
View File

@ -1,31 +1,31 @@
PM:
00: 4500
01: 0000
02: 00fe
03: 4100
04: 000f
05: 10ff
06: 24ff
07: 00fe
08: 5004
09: 4100
0a: 000f
0b: 10ff
0c: 24ff
0d: 00fe
0e: 5008
0f: 4100
10: 000f
11: 10ff
12: 24ff
13: 00fe
14: 500c
15: 4100
16: 000f
17: 10ff
18: 24ff
19: 14ff
1a: f000
00: 00df
01: 2100
02: 0001
03: b0ff
04: 9014
05: 1015
06: 0a15
07: 1415
08: 2500
09: 0001
0a: 1417
0b: 0e17
0c: 8b00
0d: a008
0e: 1a17
0f: 1e15
10: 0a15
11: b4ff
12: 7008
13: 6001
14: f000
15: 0020
16: 0000
17: 0000
18: 0000
19: 00ff
1a: 0000
1b: 0000
1c: 0000
1d: 0000
@ -272,7 +272,7 @@ MyM:
0c: 0380000
0d: 0880000
0e: 0130180
0f: 000020f
0f: 0000780
10: 0480000
11: 11c0001
12: 0980000
@ -286,17 +286,17 @@ MyM:
1a: 0000299
1b: 0000400
1c: 0058180
1d: 0000000
1e: 0000000
1f: 0000000
20: 0000000
21: 0000000
22: 0000000
23: 0000000
24: 0000000
25: 0000000
26: 0000000
27: 0000000
1d: 0380000
1e: 0b84180
1f: 000041c
20: 0000180
21: 000049c
22: 0000180
23: 03c00ff
24: 0c40000
25: 0128000
26: 0380000
27: 0b40180
28: 0000000
29: 0000000
2a: 0000000
@ -395,10 +395,10 @@ K1:
05: 17
06: 1c
07: 1b
08: 00
09: 00
0a: 00
0b: 00
08: 1d
09: 1f
0a: 21
0b: 23
0c: 00
0d: 00
0e: 00
@ -411,22 +411,22 @@ K2:
03: 07
PC:
1b
ASR:
00
AR:
001a
ASR:
01
HR:
AR:
0000
HR:
0001
GR0:
000a
0001
GR1:
002e
0000
GR2:
0000
@ -435,16 +435,16 @@ GR3:
0000
IR:
f000
b001
MyPC:
0f
00
SMyPC:
00
LC:
ff
00
O_flag:
@ -455,4 +455,4 @@ N_flag:
Z_flag:
L_flag:
End_of_dump_file
End_of_dump_file

620
microcompiler.kt Normal file
View File

@ -0,0 +1,620 @@
enum class ALU(val value: Int, val parameters: Int = 1) {
NOP(0b0000, 0), // No operation
MOV(0b0001), // Move from bus to AR
MVN(0b0010), // Move inverse of bus to AR
MVZ(0b0011, 0), // Set AR to zero
ADD(0b0100), // Add bus to AR
SUB(0b0101), // Subtract bus from AR
AND(0b0110), // Bitwise AND with bus and AR
ORR(0b0111), // Bitwise OR with bus and AR
ADN(0b1000), // Add bus to AR without setting flags
LSL(0b1001, 0), // Logical shift left AR
ISL(0b1010, 0), // Shift contents of (AR and HR) left (32-bit shift)
ASR(0b1011, 0), // Arithmetic shift right
ISR(0b1100, 0), // Signed big shift right
LSR(0b1101, 0), // Logical shift right AR
ROL(0b1110, 0), // Rotate AR left
IRL(0b1111, 0); // Rotate ARHR left (32-bit rotate)
companion object {
fun locate(value: Int) = values().first{ it.value == value }
fun matchName(name: String) = values().firstOrNull{ it.name.toLowerCase() == name.toLowerCase() }
}
}
enum class ToBus(val value: Int) {
NONE(0b000),
IR(0b001),
PM(0b010),
PC(0b011),
AR(0b100),
HR(0b101),
GR(0b110),
CONST(0b111);
companion object {
fun locate(value: Int) = values().first{ it.value == value }
fun matchName(name: String) = values().first{ it.name.toLowerCase() == name.toLowerCase() }
}
}
enum class FromBus(val value: Int) {
NONE(0b000),
IR(0b001),
PM(0b010),
PC(0b011),
// No AR
HR(0b101),
GR(0b110),
ASR(0b111);
companion object {
fun locate(value: Int) = values().first{ it.value == value }
}
}
enum class LoopCounter(val value: Int) {
NONE(0b00),
DEC(0b01),
BUS(0b10),
U(0b11);
companion object {
fun locate(value: Int) = values().first{ it.value == value }
}
}
enum class SEQ(val value: Int) {
INC(0b0000),
BOP(0b0001), // Branch on opcode
BAM(0b0010), // Branch on addressing mode
BST(0b0011), // Branch to start (set uPC = 0)
CAL(0b0110),
RET(0b0111),
BRA(0b0101),
BNZ(0b0100),
BRZ(0b1000),
BRN(0b1001),
BRC(0b1010),
BRO(0b1011),
BLS(0b1100),
BNC(0b1101),
BNO(0b1110),
HALT(0b1111);
companion object {
fun locate(value: Int) = values().first{ it.value == value }
fun matchName(name: String) = values().firstOrNull{ it.name.toLowerCase() == name.toLowerCase() }
}
}
open class MicroInstruction(private val _compiledValue: Int, val isConstInstr: Boolean = false) {
constructor(
aluOP: ALU,
toBus: ToBus,
fromBus: FromBus,
muxControl: Boolean,
increment: Boolean,
lc: LoopCounter,
seq: SEQ
): this((
(aluOP.value shl 21) or
(toBus.value shl 18) or
(fromBus.value shl 15) or
(muxControl.toBit() shl 14) or
(increment.toBit() shl 13) or
(lc.value shl 11) or
(seq.value shl 7)
) and (-1 ushr 7))
constructor(aluOP: ALU, toBus: ToBus, setM: Boolean = false): this(aluOP, toBus, FromBus.NONE, setM, false, LoopCounter.NONE, SEQ.INC)
constructor(aluOP: ALU, constant: Int): this((aluOP.value shl 21) or (ToBus.CONST.value shl 18) or (constant onlyBits 16), true)
constructor(aluOP: ALU): this(aluOP, 0)
constructor(toBus: ToBus, fromBus: FromBus, setM: Boolean = false): this(ALU.NOP, toBus, fromBus, setM, false, LoopCounter.NONE, SEQ.INC)
constructor(toBus: ToBus, setM: Boolean = false): this(ALU.NOP, toBus, FromBus.NONE, setM, false, LoopCounter.BUS, SEQ.INC)
var addressReference: AddressReference? = null
val aluOP: ALU
get() = ALU.locate(_compiledValue ushr 21 onlyBits 4)
val toBus: ToBus
get() = ToBus.locate(_compiledValue ushr 18 onlyBits 2)
val fromBus: FromBus
get() = FromBus.locate(_compiledValue ushr 15 onlyBits 2)
val muxControl = _compiledValue.getBitAt(14)
val increment = _compiledValue.getBitAt(13)
val lc: LoopCounter
get() = LoopCounter.locate(_compiledValue ushr 11 onlyBits 2)
val seq: SEQ
get() = SEQ.locate(_compiledValue ushr 7 onlyBits 4)
val address = _compiledValue onlyBits 7
val usesBus = isConstInstr || toBus != ToBus.NONE
val readsBus = isConstInstr || fromBus != FromBus.NONE
val needsUADR = isConstInstr || (lc == LoopCounter.U) || (seq.value in 0b0100..0b1110)
private val rawValue: Int = _compiledValue onlyBits 25 or (if(addressReference == null || addressReference!!.actualAddress == -1) 0 else addressReference!!.actualAddress)
val compiledValue: Int
get(){
if(addressReference != null && addressReference!!.actualAddress == -1)
throw RuntimeException("Unresolved label reference: ${addressReference!!.labelName}")
return _compiledValue onlyBits 25 or (if(addressReference == null) 0 else addressReference!!.actualAddress)
}
infix fun withReference(ref: AddressReference): MicroInstruction {
addressReference = ref
return this
}
infix fun withReference(name: String): MicroInstruction {
addressReference = AddressReference.makeReference(name)
return this
}
infix fun merge(uInstr: MicroInstruction?): MicroInstruction? {
if(uInstr == null) return this
if(isConstInstr || uInstr.isConstInstr) throw RuntimeException("CONST-instructions are fundamentally un-parallellizable")
if(usesBus && uInstr.usesBus && (toBus != uInstr.toBus)) throw RuntimeException("Instructions cannot share bus (output)") // Instructions can't share bus
if(readsBus && uInstr.readsBus && (fromBus != uInstr.fromBus)) throw RuntimeException("Instructions cannot share bus (input)") // Instructions cannot both read from bus to differing outputs
if(seq != SEQ.INC && uInstr.seq != SEQ.INC && seq != uInstr.seq) throw RuntimeException("SEQ mismatch") // We can merge an INC with something else, but we cannot merge more than this
if(needsUADR && uInstr.needsUADR && address != uInstr.address) throw RuntimeException("uADR mismatch") // Instructions cannot depend on differing uADR values
val newInstr = MicroInstruction(rawValue or uInstr.rawValue)
val adrRef = if(needsUADR) addressReference else if(uInstr.needsUADR) uInstr.addressReference else null
if(adrRef != null && newInstr.needsUADR) newInstr withReference adrRef
return newInstr
}
}
enum class Register(val busValue: Int, val canRead: Boolean = true) {
ASR(0b111, false),
IR(0b001),
PM(0b010),
PC(0b011),
AR(0b100),
HR(0b101),
GR(0b110);
companion object {
fun lookup(name: String) = if(name.toLowerCase() == "grm") (GR to true) else (values().firstOrNull{ it.name.toLowerCase() == name.toLowerCase() } to false)
}
}
class AddressReference{
private val address: Int
val labelName: String?
private constructor(address: Int?, labelName: String?){
this.address = address ?: -1
_actualAddress = this.address
this.labelName = labelName
if(address == null && labelName == null) throw RuntimeException("Reference must be accessible by name or address")
}
val actualAddress: Int
get() {
if(_actualAddress == -1)
throw RuntimeException("Unresolved label reference: $labelName")
return _actualAddress
}
private var _actualAddress: Int
private fun checkResolved(){
if(_actualAddress != -1) throw RuntimeException("Address already resolved")
}
fun resolveConstant(value: Int){
checkResolved()
_actualAddress = value
}
fun resolveAddress(target: Int){
if(target > 128 || target < 0) throw RuntimeException("Invalid target address")
checkResolved()
_actualAddress = target
}
companion object {
private val registry = ArrayList<AddressReference>()
fun makeReference(address: Int) = AddressReference(address, null)
fun makeReference(labelName: String): AddressReference {
val ref = registry.firstOrNull{ it.labelName == labelName }
if(ref != null) return ref
val reference = AddressReference(null, labelName)
registry.add(reference)
return reference
}
}
}
fun Boolean.toBit() = if(this) 1 else 0
fun Int.toInstruction() = or(0x10000000).toString(16).substring(1)
infix fun Int.onlyBits(bits: Int) = and(-1 ushr (32 - bits))
fun Int.getBitAt(index: Int) = ushr(index).and(1) == 1
fun parseMOV(instr: String): MicroInstruction {
val args = instr.split(" ")
if(args.size != 3) throw RuntimeException("MOV instruction requires two arguments: $instr")
if(args[1] == args[2]) throw RuntimeException("Cannot move from register being moved to: $instr")
val (a, setMA) = Register.lookup(args[1])
if(!a!!.canRead) throw RuntimeException("Cannot read from register: $instr")
if(args[2] == "lc") return MicroInstruction(ToBus.locate(a.busValue), setMA)
val (b, setMB) = Register.lookup(args[2])
if(a == b!! && setMA != setMB)
throw RuntimeException("Cannot directly move data between multiplexed registers!")
if(b == Register.AR) return MicroInstruction(ALU.MOV, ToBus.locate(a.busValue), setMA || setMB)
return MicroInstruction(ToBus.locate(a.busValue), FromBus.locate(b.busValue), setMA || setMB)
}
fun readNumber(literal: String, max: Int): Int {
val number = if(literal.startsWith("0") && literal.length > 1){ // Parse special number
if(literal[1] == 'x') Integer.parseInt(literal.substring(2), 16)
else if(literal[1] == 'b') Integer.parseInt(literal.substring(2), 2)
else Integer.parseInt(literal.substring(1), 8)
}else{
Integer.parseInt(literal, 10)
}
if(number > max || number < 0) throw RuntimeException("Value out of range: $literal")
return number
}
fun parseCONST(instr: String): MicroInstruction {
val args = instr.split(" ").toTypedArray()
if(args.size != 2) throw RuntimeException("Unexpected arguments: $instr")
return MicroInstruction(ALU.MOV) withReference try{ AddressReference.makeReference(readNumber(args[1], 65535)) } catch(e: NumberFormatException){ AddressReference.makeReference(args[1]) }
}
fun parseALU(instr: String): MicroInstruction {
val args = instr.split(" ")
val aluOperation = ALU.matchName(args[0])!!
if(args.size != (1 + aluOperation.parameters)) throw RuntimeException("Unexpected arguments: $instr")
if(aluOperation.parameters == 0) return MicroInstruction(aluOperation, ToBus.NONE, FromBus.NONE, false, false, LoopCounter.NONE, SEQ.INC)
try{
val num = readNumber(args[1], 65535)
return MicroInstruction(aluOperation, num)
}catch(e: NumberFormatException){
// NaN
}
val (source, setM) = Register.lookup(args[1])
if(source == null) return MicroInstruction(aluOperation) withReference AddressReference.makeReference(args[1])
if(!source.canRead) throw RuntimeException("Cannot read from source: $instr")
return MicroInstruction(aluOperation, ToBus.locate(source.busValue), setM)
}
fun parseLabelReference(ref: String): AddressReference? {
return if(ref.startsWith("@")) AddressReference.makeReference(ref.substring(1)) else null
}
fun parseCALL(instr: String): MicroInstruction {
val args = instr.split(" ")
if(args.size != 2) throw RuntimeException("Unexpected arguments: $instr")
return MicroInstruction(ALU.NOP, ToBus.NONE, FromBus.NONE, false, false, LoopCounter.NONE, SEQ.CAL) withReference parseAddressReference(args[1])
}
fun parseBranch(instr: String): MicroInstruction {
val args = instr.split(" ")
if(args.size != 2) throw RuntimeException("Unexpected arguments: $instr")
return MicroInstruction(ALU.NOP, ToBus.NONE, FromBus.NONE, false, false, LoopCounter.NONE, SEQ.matchName(args[0])!!) withReference parseAddressReference(args[1])
}
fun parseLCSet(instr: String): MicroInstruction {
val args = instr.split(" ")
if(args.size != 2) throw RuntimeException("Unexpected arguments: $instr")
val ref = try{
AddressReference.makeReference(readNumber(args[1], 0xFF))
}catch(e: NumberFormatException){
AddressReference.makeReference(args[1])
}
return MicroInstruction(ALU.NOP, ToBus.NONE, FromBus.NONE, false, false, LoopCounter.U, SEQ.INC) withReference ref
}
fun parseAddressReference(arg: String): AddressReference {
return parseLabelReference(arg) ?: AddressReference.makeReference(readNumber(arg, 0xFF))
}
fun parseInstruction(line: String): MicroInstruction? {
val subInstructions = line.split(";")
if(subInstructions.size == 1){
var shave = subInstructions[0].toLowerCase()
while(shave.startsWith(" ") || shave.startsWith("\t")) shave = shave.substring(1)
while(shave.endsWith(" ") || shave.endsWith("\t")) shave = shave.substring(0, shave.length - 1)
if(shave.length == 0) return null
if(shave.startsWith("mov ")) return parseMOV(shave)
else if(shave.startsWith("const ")) return parseCONST(shave)
else if(shave == "incpc") return MicroInstruction(ALU.NOP, ToBus.NONE, FromBus.NONE, false, true, LoopCounter.NONE, SEQ.INC)
else if(ALU.matchName(shave.substring(0, if(shave.indexOf(" ") == -1) shave.length else shave.indexOf(" "))) != null) return parseALU(shave)
else if(shave.startsWith("call ")) return parseCALL(shave)
else if(shave == "ret") return MicroInstruction(ALU.NOP, ToBus.NONE, FromBus.NONE, false, false, LoopCounter.NONE, SEQ.RET)
else if(shave == "bop") return MicroInstruction(ALU.NOP, ToBus.NONE, FromBus.NONE, false, false, LoopCounter.NONE, SEQ.BOP)
else if(shave == "bam") return MicroInstruction(ALU.NOP, ToBus.NONE, FromBus.NONE, false, false, LoopCounter.NONE, SEQ.BAM)
else if(shave == "bst") return MicroInstruction(ALU.NOP, ToBus.NONE, FromBus.NONE, false, false, LoopCounter.NONE, SEQ.BST)
else if(shave.startsWith("b") && shave.indexOf(" ") != -1 && SEQ.matchName(shave.substring(0, shave.indexOf(" "))) != null) return parseBranch(shave)
else if(shave == "halt") return MicroInstruction(ALU.NOP, ToBus.NONE, FromBus.NONE, false, false, LoopCounter.NONE, SEQ.HALT)
else if(shave.startsWith("lcset")) return parseLCSet(shave)
else if(shave == "declc") return MicroInstruction(ALU.NOP, ToBus.NONE, FromBus.NONE, false, false, LoopCounter.DEC, SEQ.INC)
else if(shave.startsWith("reset")){
val (resetReg, setM) = Register.lookup(shave.substring(6))
return MicroInstruction(ALU.NOP, ToBus.NONE, FromBus.locate(resetReg!!.busValue), setM, false, LoopCounter.NONE, SEQ.INC)
}
else throw RuntimeException("Unknown instruction: $shave")
}else{
var result: MicroInstruction? = null
for(rawInstruction in subInstructions){
val parsed = parseInstruction(rawInstruction)
if(parsed == null) continue
result = parsed merge result
if(result == null) throw RuntimeException("Instructions ($line) could not be merged!")
}
return result!!
}
}
fun error(message: String, line: Int){
System.err.println("Error on line $line:\n\t$message")
System.exit(1)
}
fun main(args: Array<String>){
if(args.size != 1) throw RuntimeException("Bad arguments :(")
val file = java.io.File(args[0])
if(!file.isFile) throw RuntimeException("File not found :(")
val builder = StringBuilder("@u\n")
var currentLine = 0
var lineCount = 0
val insns = ArrayList<MicroInstruction>()
val k1 = HashMap<Int, AddressReference>()
val k2 = HashMap<Int, AddressReference>()
var emitting = false
val emitter = ArrayList<String>()
val rawData = file.readText().replace("\r", "").split("\n")
val pregen = StringBuilder()
fun emitCode(){
val emission = StringBuilder()
for(line in emitter)
emission.append(line).append("\n")
val (output, error) = emission.toString().runPython()
if(error.length > 0)
throw RuntimeException("Error when attemping to emit code:\n ${error.replace("\n", "\n ")}")
pregen.append(output)
emitter.clear()
}
// Process code emission first
for(line in rawData){
if(line == "#emit") emitting = true
else{
if(emitting){
if(line.startsWith(">")){
emitter.add(line.substring(1))
continue
}
else{
emitCode()
emitting = false
}
}
pregen.append(line).append("\n")
}
}
if(emitter.size > 0) emitCode()
println("@p")
for(line in pregen.toString().replace("\r", "").split("\n")){
++lineCount
try{
val actualCode = (if(line.indexOf("//") != -1) line.substring(0, line.indexOf("//")) else line).toLowerCase()
if(actualCode.length == 0) continue
if(actualCode.startsWith("$")){
AddressReference.makeReference(actualCode.substring(1)).resolveAddress(currentLine)
continue
}
// Define compile-time constant
if(actualCode.startsWith("#define ")){
val substr = actualCode.substring(8)
if(substr.indexOf(" ") == -1) throw RuntimeException("Bad compile-time constant definition: $actualCode")
val name = substr.substring(0, substr.indexOf(" "))
val value = substr.substring(substr.indexOf(" ") + 1).replace(" ", "").replace("\t", "")
try{
val constant = readNumber(value, 65535)
AddressReference.makeReference(name).resolveConstant(constant)
continue
}catch(e: NumberFormatException){
throw RuntimeException("Cannot parse constant: $actualCode")
}
}
// Define Program-Memory constants
if(actualCode.startsWith("#data ")){
fun err(): Nothing = throw RuntimeException("Bad program-memory state definition: $actualCode")
val substr = actualCode.substring(6)
if(substr.indexOf(" ") == -1) err()
val address = substr.substring(0, substr.indexOf(" "))
val constant = substr.substring(substr.indexOf(" ") + 1).replace(" ", "").replace("\t", "")
try{
println("@0x${readNumber(address, 255).toString(16)}\n${readNumber(constant, 65535).or(1 shl 30).toString(16).substring(4)}")
}catch(e: NumberFormatException){
err()
}
continue
}
// Define optable (k1) entry
if(actualCode.startsWith("#optable ")){
// Parse K1 declaration
val substr = actualCode.substring(9)
if(substr.indexOf(" ") == -1) throw RuntimeException("Bad optable declaration: $actualCode")
val address = substr.substring(0, substr.indexOf(" "))
val constant = substr.substring(substr.indexOf(" ") + 1).replace(" ", "").replace("\t", "")
try{
k1[readNumber(address, 0xF)] =
if(constant.startsWith("@")) AddressReference.makeReference(constant.substring(1))
else AddressReference.makeReference(readNumber(constant, 127))
}catch(e: NumberFormatException){
throw RuntimeException("Unknown number format for optable declaration: $actualCode")
}
continue
}
// Define addressing mode (k2) entry
if(actualCode.startsWith("#amode ")){
// Parse K2 declarations
val substr = actualCode.substring(7)
if(substr.indexOf(" ") == -1) throw RuntimeException("Bad amode declaration: $actualCode")
val address = substr.substring(0, substr.indexOf(" "))
val constant = substr.substring(substr.indexOf(" ") + 1).replace(" ", "").replace("\t", "")
try{
k2[readNumber(address, 0x3)] =
if(constant.startsWith("@")) AddressReference.makeReference(constant.substring(1))
else AddressReference.makeReference(readNumber(constant, 127))
}catch(e: NumberFormatException){
throw RuntimeException("Unknown number format for amode declaration: $actualCode")
}
continue
}
if(actualCode.startsWith("#pmgen ")){
val code = actualCode.substring("#pmgen ".length)
val (output, error) = "for address in range(0, 256):\n\t$code".runPython()
if(error.length > 0)
throw RuntimeException("An error ocurred when running PM-generation script:\n ${error.replace("\n", "\n ")}")
val outStrings = output.split("\n")
for(directive in outStrings){
if(directive.length == 0) continue
val split = directive.split(" ")
if(split.size != 2)
throw RuntimeException("Unknown PM mapping: $directive")
val address: Int
val value: Int
try{
address = split[0].toInt()
if(address < 0 || address > 255)
throw RuntimeException("Could not parse address: $directive")
}catch(e: NumberFormatException){
throw RuntimeException("Could not parse address: $directive")
}
try{
value = split[1].toInt()
if(value < -32768 || address > 65535)
throw RuntimeException("Could not parse value: $directive")
}catch(e: NumberFormatException){
throw RuntimeException("Could not parse value: $directive")
}
// TODO: Fix this. This works, but it's absolutely terrible
println("@0x${address.toString(16)}\n${value.and(0xFFFF).or(1 shl 30).toString(16).substring(4)}")
}
continue
}
val instr = parseInstruction(actualCode)
if(instr == null) continue // Blank line
insns.add(instr)
++currentLine
}catch(e: Throwable){
error(e.message ?: "Compilation failed with an unknown error", lineCount)
}
}
if(currentLine > 127){
System.err.println("Instruction count overflow: $currentLine instructions")
System.exit(1)
}
// Convert instructions to strings
// Also resolve address references here
for(instr in insns)
builder.append(instr.compiledValue.toInstruction()).append("\n")
// Write K-table data ahead of instructions
for(k1Pair in k1)
println("@k1${k1Pair.key.toString(16)}${k1Pair.value.actualAddress.toPaddedHexString(2)}")
for(k2Pair in k2)
println("@k2${k2Pair.key.toString(16)}${k2Pair.value.actualAddress.toPaddedHexString(2)}")
// Write instructions
print(builder.toString())
// Informational message
System.err.println("INFO: Microcompilation succeeded! Instruction count: $currentLine")
}
fun Int.toPaddedHexString(len: Int): String {
val str = toString(16)
return if(str.length >= len) str else ("0".repeat(len - str.length) + str)
}
fun String.runPython(): Pair<String, String> {
val proc = ProcessBuilder("python", "-c", "$this")
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
// Await termination of pm generator
proc.waitFor()
return proc.inputStream.readString() to proc.errorStream.readString()
}
fun java.io.InputStream.readString(): String {
val bytes = ByteArray(available())
read(bytes, 0, bytes.size)
return String(bytes)
}

34
qs.c Normal file
View File

@ -0,0 +1,34 @@
#include <stdio.h>
void quicksort(int *data, int low, int high){
while(low < high){
int pivot = data[high];
int i = low;
for(int j = low; j < high; ++j){
if(data[j] < pivot){
int tmp = data[j];
data[j] = data[i];
data[i] = tmp;
i += 1;
}
}
int tmp = data[high];
data[high] = data[i];
data[i] = tmp;
quicksort(data, low, i - 1);
low = i + 1;
}
}
int main(int argc, char ** argv){
int data[26] = {1, 2, 0, 3, 5, 9, 8, 7, 6, 11, 10, 4, 5, 6, 7, 9, 4, 6, 8, 7, 2, 4, 1, 12, 40, 25};
for(int i = 0; i < 26; ++i)
printf("#: %d\n", data[i]);
quicksort(data, 0, 25);
for(int i = 0; i < 26; ++i)
printf("%: %d\n", data[i]);
}

9
rand_gen.py Executable file
View File

@ -0,0 +1,9 @@
import random
def getRandomHex():
return hex(random.randint(0, 0x10000) | (1 << 30))[-4:].upper()
if __name__ == "__main__":
print("@p\n@0xE0")
for i in range(0, 32):
print(getRandomHex())

58
sorter.eda Normal file
View File

@ -0,0 +1,58 @@
# Beatuifull
LOAD GR0 0x00DF # I
OUTER:
ADD GR0 1
CMI GR0 0xFF
BEQ @END
STORE GR0 *@I
LOAD GR2 **@I
LOAD GR1 *@I
INNER:
ADD GR1 1
STORE GR1 *@J
LOAD GR3 **@J
CMP GR2 GR3
BLT @SKIPP
STORE GR2 **@J
STORE GR3 **@I
LOAD GR2 **@I
SKIPP:
CMI GR1 0xFF
BNE @INNER
BRA @OUTER
END:
HALT
# for (int i = 0; i < length; i++)
# {
# for (int j = i; j < length;)
# {
# j++
# if (data[i] < data[j])
# {
# int a = data[i];
# data[i] = data[j];
# data[j] = a;
# }
# }
# }
I:
. 0020
J:
. 0000
A: # Pointer to value at I
. 0000
B: # Pointer to value at J
. 0000
LAST:
. 00FF

3
test.gs Normal file
View File

@ -0,0 +1,3 @@
LOAD GR0, 0xFF # Put FF into GR0
CMP GR0, GR2
HALT

253
ucode/bsrt.uc Normal file
View File

@ -0,0 +1,253 @@
#define LIST_START 0xE0
#define HASH_MASK 0b1111
#define BUCKET_SIZE 13
#define FIRST_BUCKET_ADDRESS 0x10
#define LAST_BUCKET_ADDRESS 0xE0
#define LAST_BUCKET_START_ADDR 0xD3
// PM initial state
#data 0x00 0x78
#data 0x01 0x85
#data 0x02 0x92
#data 0x03 0x9f
#data 0x04 0xac
#data 0x05 0xb9
#data 0x06 0xc6
#data 0x07 0xd3
#data 0x08 0x10
#data 0x09 0x1d
#data 0x0a 0x2a
#data 0x0b 0x37
#data 0x0c 0x44
#data 0x0d 0x51
#data 0x0e 0x5e
#data 0x0f 0x6b
#data 0x10 0x10
#data 0x1d 0x1d
#data 0x2a 0x2a
#data 0x37 0x37
#data 0x44 0x44
#data 0x51 0x51
#data 0x5e 0x5e
#data 0x6b 0x6b
#data 0x78 0x78
#data 0x85 0x85
#data 0x92 0x92
#data 0x9f 0x9f
#data 0xac 0xac
#data 0xb9 0xb9
#data 0xc6 0xc6
#data 0xd3 0xd3
// TODO: Include register initial-state compiler directive (saves, like, 2 cycles max)
// Set PC to LIST_START
const LIST_START
mov ar pc
//// ---- START OF DANK SORT ---- ////
$DANK_SORT
mov pc asr
mov pc ir; incpc
mov pm ar
mov pc asr
mov pm hr
irl; mov hr gr
irl
irl
irl
and HASH_MASK
mov ar asr
mov pm ar; mov pm asr
mov ar pc; mvn ar
add 1
add pm
mov ar lc
mov pc ar
// Move
mov pm pc
incpc
mov pc pm
mov ar pc
// First insertion
$DANK_INSERT_FIRST_START
incpc; bls @DANK_INSERT_FIRST_END_BIGGEST
mov pc asr
mov pm ar
sub gr
adn gr; brn @DANK_INSERT_FIRST_BOTTOM
mov gr pm; incpc
$DANK_INSERT_FIRST_SHIFT
mov pc asr; bls @DANK_INSERT_SECOND_START
mov pm gr
mov ar pm
mov gr ar; declc; incpc; bra @DANK_INSERT_FIRST_SHIFT
$DANK_INSERT_FIRST_BOTTOM
declc; bra @DANK_INSERT_FIRST_START
$DANK_INSERT_FIRST_END_BIGGEST
mov pc asr
mov gr pm
$DANK_INSERT_SECOND_START
// Finalize hash in HR
mov hr ar
and HASH_MASK
mov ar asr
mov pm hr
mov pm asr
mov pm pc; mov pm ar
incpc; sub hr
mov pc pm
mov ar lc
mov hr pc
// Get the value we hashed
mov ir asr
mov pm gr
// Second insertion
$DANK_INSERT_SECOND_LOOP
incpc; bls @DANK_INSERT_SECOND_END_BIGGEST
mov pc asr
mov pm ar
sub gr
adn gr; brn @DANK_INSERT_SECOND_BOTTOM
mov gr pm; incpc
$DANK_INSERT_SECOND_SHIFT
mov pc asr; bls @DANK_INSERT_SECOND_END_NUMLET
mov pm gr
mov ar pm
mov gr ar; declc; incpc; bra @DANK_INSERT_SECOND_SHIFT
$DANK_INSERT_SECOND_BOTTOM
declc; bra @DANK_INSERT_SECOND_LOOP
$DANK_INSERT_SECOND_END_BIGGEST
mov pc asr
mov gr pm
$DANK_INSERT_SECOND_END_NUMLET
mov ir ar; mov ir pc
sub 0xFE
brz @BUCKET_SORT_END; incpc
bra @DANK_SORT; incpc
$BUCKET_SORT_END
// New merge-algo. Saves about 100 cycles over previous version
const LAST_BUCKET_START_ADDR
mov ar hr
const FIRST_BUCKET_ADDRESS
mov ar pc
const LIST_START
$MERGE
mov pc ir
mov ar gr
mov pc asr; incpc
mov pm ar
sub ir
mov ar lc
mov gr ar
$MERGE_MOVE
mov pc asr; bls @MERGE_BOTTOM
mov pm gr
mov ar asr
add 1
mov gr pm; declc; incpc; bra @MERGE_MOVE
$MERGE_BOTTOM
mov ar gr
mov ir ar
sub hr
adn hr; brz @MERGE_END
add BUCKET_SIZE
mov ar pc
mov gr ar; bra @MERGE
$MERGE_END
$BREAK
halt
// Old merge-algo
// Set PC to write index
//const 0xE0
//mov ar pc
//
//
//// ######## INSERTION ##########
//
//// COPY NEGATIVE
//
//// Initialize GR as bucket pointer and IR as element pointer
//const FIRST_BUCKET_ADDRESS
//mov ar gr
//
//
//$LOOP
//// Dereference GR into IR and compute length
//mov gr asr
//mov pm ar
//
//// Subtract start from end and save (the resultant length) into LC
//sub gr
//mov ar lc
//
//// Calcuate the first element of the array and store in IR
//mov gr ar
//add 1
//mov ar ir
//
//$COPY_BUCKET
//bls @NEXT_BUCKET
//
//mov ir asr; mov ir ar
//mov pm hr
//
//add 1
//mov ar ir
//
//// Write value to be copied into write index (data[pc] = data[ir])
//mov pc asr
//mov hr pm; incpc; declc
//bra @COPY_BUCKET
//$NEXT_BUCKET
//
//
//mov gr ar
//add BUCKET_SIZE
//mov ar gr
//sub LAST_BUCKET_ADDRESS
//bnz @LOOP
//
//
//$BREAK
//halt

267
ucode/bsrt2.uc Normal file
View File

@ -0,0 +1,267 @@
#define LIST_START 0xE0
#define HASH_MASK 0b1111
#define BUCKET_SIZE 14
#define BUCKET_ADDRESS 0x00
#define LAST_BUCKET_ADDRESS 0xE0
#define LAST_BUCKET_START_ADDR 0xD2
#optable 0x0 @OT_0
#optable 0x1 @OT_1
#optable 0x2 @OT_2
#optable 0x3 @OT_3
#optable 0x4 @OT_4
#optable 0x5 @OT_5
#optable 0x6 @OT_6
#optable 0x7 @OT_7
#optable 0x8 @OT_8
#optable 0x9 @OT_9
#optable 0xa @OT_A
#optable 0xb @OT_B
#optable 0xc @OT_C
#optable 0xd @OT_D
#optable 0xe @OT_E
#optable 0xf @OT_F
// PM initial state
#data 0x00 0x00
#data 0x0E 0x00
#data 0x1C 0x00
#data 0x2A 0x00
#data 0x38 0x00
#data 0x46 0x00
#data 0x54 0x00
#data 0x62 0x00
#data 0x70 0x00
#data 0x7e 0x00
#data 0x8c 0x00
#data 0x9a 0x00
#data 0xa8 0x00
#data 0xb6 0x00
#data 0xc4 0x00
#data 0xd2 0x00
// TODO: Include register initial-state compiler directive
// (saves, like, 2 cycles max)
// Set PC to LIST_START
const LIST_START
mov ar pc
//// ---- START OF DANK SORT ---- ////
$SORT
mov pc asr; incpc
mov pm ir; call @JTABLE // Call jumptable. This also copies PC to HR
// We return here after visiting jump table
// AR contains a magic value and ASR contains the same value
mov pm lc; mov pm pc
// Move
incpc
mov pc pm
mov ar pc
// First insertion
$INSERT_START
// Load a value and compare with value to be inserted
// If L is set, value is greater than all other values in bucket
incpc
mov pc asr; bls @INSERT_END_BIGGEST
mov pm ar
sub ir
adn ir; brn @INSERT_BOTTOM
// We found where to insert value. Insert it
mov ir pm; incpc
// Shift values after the value we inserted
$INSERT_SHIFT
mov pc asr; bls @INSERT_END_NUMLET
mov pm gr
mov ar pm
mov gr ar; declc; incpc; bra @INSERT_SHIFT
// Continue to next value in bucket for comparison
$INSERT_BOTTOM
declc; bra @INSERT_START
// Value we are inserting was the biggest value in the bucket
$INSERT_END_BIGGEST
mov ir pm
$INSERT_END_NUMLET
// Check if we've sorted all values. If we have, merge then,
// otherwise return to top of bucketsort
mov hr ar; mov hr pc
sub 0
bnz @SORT
$BUCKET_SORT_END
const LAST_BUCKET_START_ADDR
mov ar hr
const BUCKET_ADDRESS
mov ar pc
const LIST_START
$MERGE
mov pc ir
mov pc asr; incpc
mov pm lc
$MERGE_MOVE
mov pc asr; bls @MERGE_BOTTOM
mov pm gr
mov ar asr
add 1
mov gr pm; declc; incpc; bra @MERGE_MOVE
$MERGE_BOTTOM
mov ar gr
mov ir ar
sub hr
adn hr; brz @PROGRAM_END
adn BUCKET_SIZE
mov ar pc
mov gr ar; bra @MERGE
$PROGRAM_END
$BREAK
halt
// Jump-table subroutine
$JTABLE
mov pc hr; bop
// IR OP-field jump table
$OT_0
const 0x70
mov ar asr; ret
$OT_1
const 0x7e
mov ar asr; ret
$OT_2
const 0x8c
mov ar asr; ret
$OT_3
const 0x9a
mov ar asr; ret
$OT_4
const 0xa8
mov ar asr; ret
$OT_5
const 0xb6
mov ar asr; ret
$OT_6
const 0xc4
mov ar asr; ret
$OT_7
const 0xd2
mov ar asr; ret
$OT_8
const 0x00
mov ar asr; ret
$OT_9
const 0x0e
mov ar asr; ret
$OT_A
const 0x1c
mov ar asr; ret
$OT_B
const 0x2a
mov ar asr; ret
$OT_C
const 0x38
mov ar asr; ret
$OT_D
const 0x46
mov ar asr; ret
$OT_E
const 0x54
mov ar asr; ret
$OT_F
const 0x62
mov ar asr; ret
// Old merge-algo
// Set PC to write index
//const 0xE0
//mov ar pc
//
//
//// ######## INSERTION ##########
//
//// COPY NEGATIVE
//
//// Initialize GR as bucket pointer and IR as element pointer
//const BUCKET_ADDRESS
//mov ar gr
//
//
//$LOOP
//// Dereference GR into IR and compute length
//mov gr asr
//mov pm ar
//
//// Subtract start from end and save (the resultant length) into LC
//sub gr
//mov ar lc
//
//// Calcuate the first element of the array and store in IR
//mov gr ar
//add 1
//mov ar ir
//
//$COPY_BUCKET
//bls @NEXT_BUCKET
//
//mov ir asr; mov ir ar
//mov pm hr
//
//add 1
//mov ar ir
//
//// Write value to be copied into write index (data[pc] = data[ir])
//mov pc asr
//mov hr pm; incpc; declc
//bra @COPY_BUCKET
//$NEXT_BUCKET
//
//
//mov gr ar
//add BUCKET_SIZE
//mov ar gr
//sub LAST_BUCKET_ADDRESS
//bnz @LOOP
//
//
//$BREAK
//halt

154
ucode/bucksort.uc Normal file
View File

@ -0,0 +1,154 @@
#define BUCKET_SIZE 0b1000
#define BUCKET_COUNT 15
#define LIST_START 0xE0
#define HASH_MASK 0b1111000
#data 0x00 0x00
#data 0x08 0x08
#data 0x10 0x10
#data 0x18 0x18
#data 0x20 0x20
#data 0x28 0x28
#data 0x30 0x30
#data 0x38 0x38
#data 0x40 0x40
#data 0x48 0x48
#data 0x50 0x50
#data 0x58 0x58
#data 0x60 0x60
#data 0x68 0x68
#data 0x70 0x70
//// FOR TESTING PURPOSES ONLY ////
// TODO: Buckets store last index instead of length.
//// ---- Bucket sort ----
// Set PC to E0
const LIST_START
mov ar pc
$MOVE_TO_BUCKETS
mov pc asr; mov pc ar;
add pc; incpc;
brz @MOVE_TO_BUCKETS_END
mov pm ar; mov pm gr
// The hash
rol
rol
rol
rol
rol
rol
rol
and HASH_MASK
// Push PC
mov pc ir
// Set current address to the hash.
mov ar asr
mov ar pc
// Increase length
// Compare loop
// if a < b : keep looping
// else: swap a and b and shift the rest one step
// if at end, insert value and return.
// Length of loop, and set up PC for first list index
const 1
add pm; incpc
mov ar pm
sub pc
// Set loop counter
mov ar lc
$COMPARE_LOOP
bls @INSERT_LAST_ELEMENT
mov pc asr; incpc; declc
// TODO: Make sure this is optimal
mov pm ar
sub gr
brn @COMPARE_LOOP
add gr; mov gr pm
mov pc asr; incpc; declc
// call @BREAK
$INSERT_LAST_ELEMENT
mov gr pm
// Pop PC
mov ir pc
bra @MOVE_TO_BUCKETS
// mov ar asr
// mov pc ir // Push PC
// mov pm pc; add pm
// incpc
// mov pc pm
// mov ar asr
// mov gr pm
// mov ir pc // Pop PC
// bra @MOVE_TO_BUCKETS
$MOVE_TO_BUCKETS_END
halt
// //// ---- Bubble sort ----
//
//
// $BUBBLE_YEET
//
// mov pm ir; incpc
//
// mov pc asr
//
//
// $SORT_LOOP
// mov pc hr // Current lowest index
// mov pm gr // Current lowest value
//
// reset asr
// mov pc pm
//
// $LOOP
// mov pc asr
// mov gr ar
// sub pm
//
// // If PM < GR
// brn @CHECK
// mov pc hr // Current lowest index
// mov pm gr // Current lowest value
//
// $CHECK
// incpc
// mov pc ar
// sub ir
// bnz @LOOP
//
// reset asr
// mov pm pc
// mov pm asr; incpc
// mov pm ar
// mov gr pm
// mov hr asr
// mov ar pm
// reset asr
// mov pc pm
// declc
// bls @END_SORT
// bra @SORT_LOOP
//
// $END_SORT
// halt
$BREAK
halt

219
ucode/sort2.uc Normal file
View File

@ -0,0 +1,219 @@
// Hi! If you're reading this, you probably want to know what this program does.
// To that, I say: Good luck and I hope you have patience and a strong will to
// live, 'cause both negatively impacted by trying to read the code below.
// This isn't a joke; I feel like someone telling a person to get off the edge
// of a tall building here: reading the code below WILL negatively impact your
// life. You have been warned.
#define LIST_START 0xE0
#define LIST_END 0xFE
#define LIST_START_MODIFIED 0xDF
#define LIST_END_MODIFIED 0xFD
#define HASH_MASK 0b1111000
#define NEGATIVE_START 0x40
#define NEGATIVE_END 0x78
#define POSITIVE_START 0x00
#define POSITIVE_END 0x38
#define BUCKET_SIZE 8
#define BUCKET_INDEX_TRACKER 0b11111000
// Set up buckets (with absolute sizes)
// Bucket count: 16
// Bucket size: 8
// Motivation: direct mapping from hash to bucket at the cost of bucket size (-6 per bucket)
#data 0x00 0x00 // 0 (POSITIVE)
#data 0x08 0x00 // 1 (POSITIVE)
#data 0x10 0x00 // 2 (POSITIVE)
#data 0x18 0x00 // 3 (POSITIVE)
#data 0x20 0x00 // 4 (POSITIVE)
#data 0x28 0x00 // 5 (POSITIVE)
#data 0x30 0x00 // 6 (POSITIVE)
#data 0x38 0x00 // 7 (POSITIVE)
#data 0x40 0x00 // 8 (NEGATIVE)
#data 0x48 0x00 // 9 (NEGATIVE)
#data 0x50 0x00 // A (NEGATIVE)
#data 0x58 0x00 // B (NEGATIVE)
#data 0x60 0x00 // C (NEGATIVE)
#data 0x68 0x00 // D (NEGATIVE)
#data 0x70 0x00 // E (NEGATIVE)
#data 0x78 0x00 // F (NEGATIVE)
// Shift list down by one address, freeing up 0xFF for random-access use
reset asr
mov pm hr
const LIST_START_MODIFIED
mov ar asr
mov hr pm
// Initialize PC to point at list
//const LIST_START
mov ar pc
$BUCKET_SORT_START
// AR = PM[PC]
// HR = PM[PC + 1]
mov pc asr
mov pc ir; incpc
mov pm ar
mov pc asr
mov pm hr
// Hash HR, partially hash AR.
// Result of HR-hash is in AR
// Result of AR-hash is in HR
irl; mov ar gr // PM[0xFF] = AR
irl; reset asr
irl; mov gr pm
irl; mov pc asr // Set GR to pre-hash value of HR
irl; mov pm gr
irl
irl
and HASH_MASK
mov ar asr
mov pm lc; mov pm pc
incpc
mov pc pm
mov ar pc
$FIRST_INSERTION
incpc; bls @FIRST_INSERTION_END_BIGGEST
mov pc asr
mov pm ar
sub gr
adn gr; brn @FIRST_INSERTION_BOTTOM
mov gr pm; incpc
$FIRST_INSERTION_SHIFT
mov pc asr; bls @FIRST_INSERTION_END_NOTBIGGEST
mov pm gr
mov ar pm
mov gr ar; declc; incpc; bra @FIRST_INSERTION_SHIFT
$FIRST_INSERTION_BOTTOM
declc; bra @FIRST_INSERTION
$FIRST_INSERTION_END_BIGGEST
mov pc asr
mov gr pm
$FIRST_INSERTION_END_NOTBIGGEST
// Prepare second insertion (minimal bookkeeping)
mov hr ar
and HASH_MASK
mov ar asr
mov pm pc; mov pm lc
incpc; reset asr
mov pm gr
mov ar asr
mov pc pm
mov ar pc
$SECOND_INSERTION
incpc; bls @SECOND_INSERTION_END_BIGGEST
mov pc asr
mov pm ar
sub gr
adn gr; brn @SECOND_INSERTION_BOTTOM
mov gr pm; incpc
$SECOND_INSERTION_SHIFT
mov pc asr; bls @SECOND_INSERTION_END_NOTBIGGEST
mov pm gr
mov ar pm
mov gr ar; declc; incpc; bra @SECOND_INSERTION_SHIFT
$SECOND_INSERTION_BOTTOM
declc; bra @SECOND_INSERTION
$SECOND_INSERTION_END_BIGGEST
mov pc asr
mov gr pm
$SECOND_INSERTION_END_NOTBIGGEST
mov ir ar; mov ir pc
sub LIST_END_MODIFIED
brz @BUCKET_SORT_END; incpc
bra @BUCKET_SORT_START; incpc
$BUCKET_SORT_END
//call @BREAK
// Merge negative values
const NEGATIVE_END
mov ar hr
const NEGATIVE_START
mov ar pc
const LIST_START
$NEGATIVE_MERGE
mov pc ir
mov pc asr; incpc
mov pm lc
$NEGATIVE_MERGE_MOVE
mov pc asr; bls @NEGATIVE_MERGE_BOTTOM
mov pm gr
mov ar asr
add 1
mov gr pm; declc; incpc; bra @NEGATIVE_MERGE_MOVE
$NEGATIVE_MERGE_BOTTOM
mov ar gr
mov ir ar
sub hr
adn hr; brz @POSITIVE_MERGE_INIT
add BUCKET_SIZE
mov ar pc
mov gr ar; bra @NEGATIVE_MERGE
// Merge positive values
$POSITIVE_MERGE_INIT
const POSITIVE_END
mov ar hr
const POSITIVE_START
mov ar pc
mov gr ar // Pop AR
$POSITIVE_MERGE
mov pc asr; incpc
mov pm lc
$POSITIVE_MERGE_MOVE
mov pc asr; bls @POSITIVE_MERGE_BOTTOM
mov pm gr
mov ar asr
add 1
mov gr pm; declc; incpc; bra @POSITIVE_MERGE_MOVE
$POSITIVE_MERGE_BOTTOM
mov ar gr
mov pc ar
and BUCKET_INDEX_TRACKER
sub hr
adn hr; brz @PROGRAM_END
adn BUCKET_SIZE
mov ar pc
mov gr ar; bra @POSITIVE_MERGE
$PROGRAM_END
$BREAK
halt

119
ucode/sort3.uc Normal file
View File

@ -0,0 +1,119 @@
#optable 0x0 @BREAK
#optable 0x1 @BREAK
#optable 0x2 @BREAK
#optable 0x3 @BREAK
#optable 0x4 @BREAK
#optable 0x5 @BREAK
#optable 0x6 @BREAK
#optable 0x7 @BREAK
#optable 0x9 @BREAK
#optable 0xa @BREAK
#optable 0xb @BREAK
#optable 0xc @BREAK
#optable 0xd @BREAK
#optable 0xe @BREAK
#optable 0xf @BREAK
#define LIST_START 0xE0
#define HASH_MASK 0b1111
#define FIRST_BUCKET 0x10 // Index of start of first bucket
#define LAST_BUCKET 0xD3 // Index of start of last bucket
#define BUCKET_SIZE 13 // Each bucket an hold 13 elements before spilling
// LUT entries contain pointers to last element in bucket
// This allows the LUT to serve as both a jump-table and as bucket-headers
// Look at me being all resourceful and stuff, huh? Inb4 slowdowns ;))
#data 0x00 0x77
#data 0x01 0x84
#data 0x02 0x91
#data 0x03 0x9e
#data 0x04 0xab
#data 0x05 0xb8
#data 0x06 0xc5
#data 0x07 0xd2
#data 0x08 0x0f
#data 0x09 0x1c
#data 0x0a 0x29
#data 0x0b 0x36
#data 0x0c 0x43
#data 0x0d 0x50
#data 0x0e 0x5d
#data 0x0f 0x6a
const LIST_START
mov ar pc
// Two values sorted per iteration, so half the iterations obv ;)
// For consistency, we just decrement LC twice per iteration, though
// Had this course also stressed power use or other efficiency-related questions,
// we probably wouldn't decrement twice per iteration for the sole purpose of "clarity"
// But alas, Kent doesn't care, so why should we?
lcset 32
$BUCKET_SORT
bls @MERGE_INIT
mov pc asr; incpc
mov pm ar; mov pm ir // Use whatever value we're sorting to index GR at complete random
mov pc asr; incpc
mov pm hr
// Shift AR and HR
irl; mov pm gr
irl
irl
irl
and HASH_MASK // Completely hash value from HR
mov ar asr
mov pm ar
add 1
mov ar pm
mov ar asr
mov gr pm; declc // Mark element as sorted by decrementing LC
mov hr ar
and HASH_MASK // Completely hash value from AR
mov ar asr
mov pm ar
add 1
mov ar pm
mov ar asr
mov ir pm; declc // Mark element as sorted by decrementing LC
bra @BUCKET_SORT
//// ---- MERGE ---- ////
$MERGE_INIT
// HR points to LUT
// PC points to start of first bucket
// AR points to list
mov pc hr // PC is always 0 after bucketsort because FF+1 mod 100 = 0. Imagine that!
const FIRST_BUCKET
mov ar pc
const LIST_START
$MERGE
mov ar ir
mov hr asr
sub pm
mov pc gr
// If this was not the last bucket, branch back to start of merge
sub LAST_BUCKET
adn LAST_BUCKET
bnz @MERGE
$END
$BREAK
halt

148
ucode/sort4.uc Normal file
View File

@ -0,0 +1,148 @@
// Hi! If you're reading this, you probably want to know what this program does
// To that, I say: Good luck and I hope you have patience and a strong will to
// live, 'cause both will be negatively impacted by trying to read the code
// below.
// This isn't a joke; I feel like someone telling a person to get off the edge
// of a tall building here: reading the code below WILL negatively impact your
// life. You have been warned.
// For clarity's sake, I thought I'd add a small preface here:
// We use the optable as a replacement for a hashing algorithm. Check the
// #optable directives if you're wondering what bucket the 4 highest bits of a
// value end up pointing to. Each entry in the optable simply loads the
// address of the corresponding bucket and initiates an insertion sort for said
// bucket.
// Labels ending in "_SPEC" are edge-case optimization labels. They are
// branched to in the case of special edge cases with the idea being that they
// are much faster than following the reguar code-path (even if said path would
// ultimately perform the same action).
// For example, "call @JTABLE_SPEC" invokes a special jumptable subroutine
// which parallelizes some of the boilerplate needed for the upcoming merge.
#define LIST_START 0xE0
#define HIGHEST_BUCKET 0xD2
// Generate jump-table ;)
#emit
>for i in range(16): print("#optable "+hex(i)+" @OT_"+hex(i)[2::])
// Generate program memory:
// Addresses 0xE0-0xFF ignored
// Addresses that are a multiple of 0xE are set to 0
// All other addresses contain a relative pointer to the next bucket start
#pmgen if address < 0xE0: print(str(address)+" "+str(0 if (address % 14) == 0 else ((address - (address % 0xE)) - 0xE) if address > 0xD else -1))
// Set all GR-registers to -1, so that we always can call "sub gr" to increment
// AR, no matter what value we have in IR ;)
const 0xB00 // GRx=10, M=11
lsr; mov ar ir
lsr; reset gr
lsr; reset grm
mov ar ir // GRx=00, M=01
reset gr
reset grm
// Initialize PC to point at list
const LIST_START
mov ar pc
mov pc asr
// Perform bucketsort for 32 elements
#emit
>for i in range(31):
> print("mov pm ir; incpc; call @JTABLE")
mov pm ir; call @JTABLE_SPEC
// Initialize state for merge
const HIGHEST_BUCKET
mov ar asr
mov pm lc
sub gr
// Copy elements to list
$MERGE
mov ar asr; declc; bls @MB_SPEC
mov pm ir
mov pc asr // This branch improves stability
mov ir pm; incpc; bls @MERGE_BOTTOM // Transfer value and copy more elements
sub gr; bra @MERGE
$MERGE_BOTTOM
sub gr
mov ar asr
$MB_SPEC
mov pm ar; mov pm asr
sub gr
mov pm lc; bnz @MERGE
// Breakpoint hook and program termination point
$BREAK
$END
halt
// Jump-table subroutine
$JTABLE
mov pc hr; bop
$JTABLE_SPEC
const LIST_START
mov ar hr; bop
// Generate jump table
#emit
>bucket_size=14
>for i in range(16):
> print("$OT_"+hex(i)[2::])
> if i < 8:
> print("const "+str(bucket_size*(7-i)))
> else:
> print("const "+str(bucket_size*(23-i)))
> print("mov ar asr; bra @PREPARE_SORT")
// Actual bucketsort
$PREPARE_SORT
mov pm pc; mov pm lc // Load bucket length
sub gr; incpc // Increment bucket length
mov pc pm; bls @IE_SPEC // Store new length
mov ar pc; sub ar
sub ir // Save -IR into AR (you'll see why)
$INSERTION
mov pc asr; incpc; declc; bls @INSERTION_END_BIGGEST
add pm // Effectively, AR=PM-IR, except more like AR=-IR+PM
sub pm; brn @INSERTION
mov pm ar
mov ir pm; bls @IEN_SPEC
$INSERTION_SHIFT
mov pc asr
mov pm ir
mov ar pm; bls @INSERTION_END_NOTBIGGEST
mov ir ar; declc; incpc; bra @INSERTION_SHIFT
$IEN_SPEC
mov pc asr
mov ar pm; bra @INSERTION_END_NOTBIGGEST
$IE_SPEC
mov ar asr
$INSERTION_END_BIGGEST
mov ir pm
$INSERTION_END_NOTBIGGEST
mov hr pc
mov pc asr; ret

31
ucode/test.uc Normal file
View File

@ -0,0 +1,31 @@
#optable 0x0 @TABLE_0
#optable 0x1 @TABLE_1
#optable 0x2 @TABLE_2
#optable 0x3 @TABLE_3
mov pm ir
reset asr
mov pm grm
const 0
mov ar asr
mov pm gr
bop
$TABLE_0
const 1
bra @END
$TABLE_1
const 2
bra @END
$TABLE_2
const 3
bra @END
$TABLE_3
const 4
$END
halt

211
weaver.kt Normal file
View File

@ -0,0 +1,211 @@
import java.io.*
class MachineState {
val programMemory = ShortArray(256)// PM
val microMemory = IntArray(128) // MyM
val k1 = ByteArray(16) // K1
val k2 = ByteArray(4) // K2
var pc: Byte = 0.toByte() // PC
var asr: Byte = 0.toByte() // ASR
var ar: Short = 0.toShort() // AR
var hr: Short = 0.toShort() // HR
var gr0: Short = 0.toShort() // GR0
var gr1: Short = 0.toShort() // GR0
var gr2: Short = 0.toShort() // GR0
var gr3: Short = 0.toShort() // GR0
var ir: Byte = 0.toByte() // IR
var uPC: Byte = 0.toByte() // MyPC
var uSP: Byte = 0.toByte() // SMyPC
var lc: Byte = 0.toByte() // LC
override fun toString(): String {
val builder = StringBuilder("PM:\n")
for(index in 0 until programMemory.size)
builder.append(index.toUHex()).append(": ").append(programMemory[index].toUHex()).append("\n")
builder.append("\nMyM:\n")
for(index in 0 until microMemory.size)
builder.append(index.toUHex()).append(": ").append(microMemory[index].toShortUHex()).append("\n")
builder.append("\nK1:\n")
for(index in 0 until k1.size)
builder.append(index.toUHex()).append(": ").append(k1[index].toUHex()).append("\n")
builder.append("\nK2:\n")
for(index in 0 until k2.size)
builder.append(index.toUHex()).append(": ").append(k2[index].toUHex()).append("\n")
return builder
.append("\nPC:\n")
.append(pc.toUHex())
.append("\n\nASR:\n")
.append(asr.toUHex())
.append("\n\nAR:\n")
.append(ar.toUHex())
.append("\n\nHR:\n")
.append(hr.toUHex())
.append("\n\nGR0:\n")
.append(gr0.toUHex())
.append("\n\nGR1:\n")
.append(gr1.toUHex())
.append("\n\nGR2:\n")
.append(gr2.toUHex())
.append("\n\nGR3:\n")
.append(gr3.toUHex())
.append("\n\nIR:\nb")
.append(ir.toInt().or(1 shl 30).toString(2).substring(28))
.append("\n\nMyPC:\n")
.append(uPC.toUHex())
.append("\n\nSMyPC:\n")
.append(uSP.toUHex())
.append("\n\nLC:\n")
.append(lc.toUHex())
.append("\n\nO_flag:\n\nC_flag:\n\nN_flag:\n\nZ_flag:\n\nL_flag:\nEnd_of_dump_file")
.toString()
}
companion object {
fun parseState(rawState: String): MachineState {
val state = MachineState()
val lines = rawState.replace("\r", "").split("\n").toTypedArray()
// Read PM
for(index in 0 until 256)
state.programMemory[index] = Integer.parseInt(lines[index + 1].substring(4), 16).toShort()
// Read MyM
for(index in 0 until 128)
state.microMemory[index] = Integer.parseInt(lines[index + 3 + 256].substring(4), 16)
// Read K1
for(index in 0 until 16)
state.k1[index] = Integer.parseInt(lines[index + 5 + 256 + 128].substring(4), 16).toByte()
// Read K2
for(index in 0 until 4)
state.k2[index] = Integer.parseInt(lines[index + 7 + 256 + 128 + 16].substring(4), 16).toByte()
state.pc = Integer.parseInt(lines[413], 16).toByte()
state.asr = Integer.parseInt(lines[416], 16).toByte()
state.ar = Integer.parseInt(lines[419], 16).toShort()
state.hr = Integer.parseInt(lines[422], 16).toShort()
state.gr0 = Integer.parseInt(lines[425], 16).toShort()
state.gr1 = Integer.parseInt(lines[428], 16).toShort()
state.gr2 = Integer.parseInt(lines[431], 16).toShort()
state.gr3 = Integer.parseInt(lines[434], 16).toShort()
state.ir = Integer.parseInt(lines[437].substring(1), 2).toByte()
state.uPC = Integer.parseInt(lines[440], 16).toByte()
state.uSP = Integer.parseInt(lines[443], 16).toByte()
state.lc = Integer.parseInt(lines[446], 16).toByte()
return state
}
}
}
fun error(message: String){
System.err.println(message)
println("Usage:\n\tkotlin WeaverKt [machineCodeFile] [outputFile]\nor\n\tkotlin WeaverKt [machineCodeFile] [inputState] [outputFile]")
System.exit(1)
}
fun main(args: Array<String>){
if(args.size > 3) error("Too many arguments!")
if(args.size < 2) error("Too few arguments!")
val weaveFile = File(args[0])
if(!weaveFile.isFile) error("Given machine code file doesn't exist!")
val state: MachineState
if(args.size == 2) state = MachineState()
else{
val file = File(args[1])
if(file.isFile) state = MachineState.parseState(file.readText())
else{
error("Machine state file (${args[1]}) doesn't exist!")
state = MachineState()
}
}
val weaveData = weaveFile.readText().replace("\r", "").split("\n").toTypedArray()
var weaveUCode = false
var pIndex = 0
var uIndex = 0
fun index()= if(weaveUCode) uIndex else pIndex
fun iIdx() {
if(weaveUCode) ++uIndex
else ++pIndex
}
fun sIdx(value: Int){
if(weaveUCode) uIndex = value
else pIndex = value
}
for(rawValue in weaveData){
val value = {
var v = rawValue.replace(" ", "").replace("\t", "")
// Return
if(v.indexOf("#") > 0) v.substring(0, v.indexOf("#")) else v
}()
if(value.length == 0) continue
else if(value.startsWith("@")){
if(value == "@u") weaveUCode = true
else if(value == "@p") weaveUCode = false
else if(value.startsWith("@k1")){
// Parse K1 table entry
if(value.length != 6)
error("Badly formatted K1 declaration: $rawValue")
val index = value.substring(3, 4).toInt(16)
val k1Value = value.substring(4, 6).toInt(16).toByte()
if(k1Value < 0)
error("Invalid K1 address pointer (must be in range 00-7F): $rawValue")
state.k1[index] = k1Value
}
else if(value.startsWith("@k2")){
// Parse K2 table entry
if(value.length != 6)
error("Badly formatted K2 declaration: $rawValue")
val index = value.substring(3, 4).toInt(16)
if(index > 4)
error("Invalid K2 index value (must be in range 0-3): $rawValue")
val k2Value = value.substring(4, 6).toInt(16).toByte()
if(k2Value < 0)
error("Invalid K2 address pointer (must be in range 00-7F): $rawValue")
state.k2[index] = k2Value
}
else if(value.startsWith("@0x")) sIdx(Integer.parseInt(value.substring(3), 16))
else sIdx(Integer.parseInt(value.substring(1), 10))
continue
}
else if((weaveUCode && index() >= state.microMemory.size) || (!weaveUCode && index() >= state.programMemory.size)) error("Memory out of bounds: ${index()}! Did you pass too much data?")
else if((weaveUCode && value.length != 7) || (!weaveUCode && value.length != 4)) error("Cannot weave data of bad length: $value")
else try{
if(weaveUCode) state.microMemory[index()] = Integer.parseInt(value, 16)
else state.programMemory[index()] = Integer.parseInt(value, 16).toShort()
}catch(e: NumberFormatException){
error("Cannot weave non-hex data: $value")
}
iIdx()
}
val outputFile = if(args.size == 3) File(args[2]) else File(args[1])
if(outputFile.isFile) outputFile.delete()
outputFile.createNewFile()
val machineData = state.toString()
outputFile.bufferedWriter().use{ it.write(machineData, 0, machineData.length) }
}
fun Short.toUHex() = toInt().and(0xFFFF.toInt()).or(1.shl(30)).toString(16).substring(4)
fun Int.toUHex() = and(0xFF).or(1.shl(30)).toString(16).substring(6)
fun Int.toShortUHex() = and((-1 ushr 7)).or(1.shl(30)).toString(16).substring(1)
fun Byte.toUHex() = toInt().and(0xFF.toInt()).or(1.shl(30)).toString(16).substring(6)