Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions UE1/Software/Go-UE1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# UE1 Assembler and Emulator in Go

For those who would rather not fire up DOSBox and QuickBASIC to write and run UE1 software, this is for you!

## Building

`go build` in this directory should be sufficient on any operating system where Go is installable.

## Usage

### ue1 build

`ue1 build <filename>` is the most basic use. Extension can be anything, but the default output is `<filename-without-extension>.bin`. Only the final extension is changed, so `foo.bar.asm` would become `foo.bar.bin`.

If desired, you can pass the `-d` or `--dump` flags to dump the binary to stdout rather than a bin file. This will print the bytes in a human-readable form (ala hexdump), as below:

```
000  40 A8 B8 58 80 81 82 83  84 85 86 87 88 89 8A 8B  |@..X............|
010  8C 8D 8E 8F 90 98 40 58  10 24 80 11 25 81 12 26  |......@X.$..%..&|
020  82 13 27 83 10 88 11 89  12 8A 13 8B 40 58 10 24  |..'.........@X.$|
030  84 11 25 85 12 26 86 13  27 87 14 88 15 89 16 8A  |..%..&..'.......|
040  17 8B 40 58 10 24 80 11  25 81 12 26 82 13 27 83  |..@X.$..%..&..'.|
050  10 88 11 89 12 8A 13 8B  40 58 10 24 84 11 25 85  |........@X.$..%.|
060  12 26 86 13 27 87 14 88  15 89 16 8A 17 8B 40 58  |.&..'.........@X|
070  10 24 80 11 25 81 12 26  82 13 27 83 10 88 11 89  |.$..%..&..'.....|
080  12 8A 13 8B 40 58 10 24  84 11 25 85 12 26 86 13  |....@X.$..%..&..|
090  27 87 14 88 15 89 16 8A  17 8B 40 58 10 24 80 11  |'.........@X.$..|
0A0  25 81 12 26 82 13 27 83  10 88 11 89 12 8A 13 8B  |%..&..'.........|
0B0  40 58 10 24 84 11 25 85  12 26 86 13 27 87 28 8C  |@X.$..%..&..'.(.|
0C0  14 88 15 89 16 8A 17 8B  C0 F0 |..........|
0CA
```

### ue1 emu

Options are:

```
-a
--asm
Use provided unassembled assembly file for input.

-n
--non-interactive
Do not display execution, only show output register (and maybe ring terminal bell).

-s
--speed
Speed in Hertz. Must be an integer. Default is 60.
```

## Demo

![Fibonacci at 10 Hz](demo/demo.gif)
112 changes: 112 additions & 0 deletions UE1/Software/Go-UE1/asm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package main

import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"time"
)

func getBitPosition(addr string) (int, error) {
pos, err := strconv.Atoi(string(addr[2]))
if err != nil {
return -1, fmt.Errorf("error converting address bit position: %v", err)
}
return pos, nil
}

func (c *CPU) parseAddressString(addr string) (err error) {
var bitPos int
dcAddress := strings.ToLower(addr)

switch {
case strings.HasPrefix(dcAddress, "sr"):
if bitPos, err = getBitPosition(dcAddress); err == nil {
c.whichBit = 1 << bitPos
c.whichOutput = Scratch
c.tmpbit = (c.register.scratch & c.whichBit) >> bitPos
}
case strings.HasPrefix(dcAddress, "or"):
if bitPos, err = getBitPosition(dcAddress); err == nil {
c.whichBit = 1 << bitPos
c.whichOutput = Output
c.tmpbit = (c.register.output & c.whichBit) >> bitPos
}
case strings.HasPrefix(dcAddress, "ir"):
if bitPos, err = getBitPosition(dcAddress); err == nil {
c.tmpbit = (c.register.input & c.whichBit) >> bitPos
}
case dcAddress == "rr":
c.tmpbit = c.register.rr
default:
return fmt.Errorf("unable to parse address pnemonic `%s`: %v",
addr, err)
}
return
}

func (c *CPU) parseOpcodeString(opcode string) (err error) {
// I ain't writin' no long switch statement twice!
dcOpcode := strings.ToLower(opcode)
opcodeBin := opcodes[dcOpcode]

return c.parseOpcodeBin(opcodeBin)
}

func (c *CPU) processAsm(args []string, speed int, nonint bool) {
ticker := time.NewTicker(time.Second / time.Duration(speed))
defer ticker.Stop()

source, err := os.Open(args[len(args)-1])
if err != nil {
fatalln(err)
}
defer source.Close()

scanner := bufio.NewScanner(source)
for scanner.Scan() {
line := scanner.Text()
tokens := strings.Fields(line)

if tokens[0] == ";" {
continue
}

if err = c.parseAddressString(tokens[1]); err != nil {
fatalln(err)
}

if err = c.parseOpcodeString(tokens[0]); err != nil {
fatalln(err)
}

if c.flag.wrt == 1 {
if c.whichOutput == Output {
err = c.writeMemRegister(Output)
} else {
err = c.writeMemRegister(Scratch)
}
}
if err != nil {
fatalln(err)
}

if nonint {
c.flag.wrt = 0
continue
}

<-ticker.C
fmt.Println("INSTRUCTION : ", tokens[0])
fmt.Println("MEMORY ADDRESS: ", tokens[1])

c.printCpuStat()
c.flag.wrt = 0
}
if nonint {
fmt.Printf("OUTPUT = %b (%d)\n",
c.register.output, c.register.output)
}
}
185 changes: 185 additions & 0 deletions UE1/Software/Go-UE1/bin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package main

import (
"bufio"
"fmt"
"os"
"time"
)

var opcodeStrings = [16]string{
"NOP0", "LD", "ADD", "SUB",
"ONE", "NAND", "OR", "XOR",
"STO", "STOC", "IEN", "OEN",
"IOC", "RTN", "SKZ", "NOPF",
}

func (c *CPU) parseOpcodeBin(b byte) (err error) {
switch b {
case nopz:
c.flag.zero = 1
case ld:
if c.register.ien == 1 {
c.register.rr = c.tmpbit
}
case add:
if c.register.ien == 1 {
c.tmprr = c.register.rr + c.register.carry + c.tmpbit

c.register.rr = c.tmprr & 1
c.register.carry = (c.tmprr & 0b10) >> 1
}
case sub:
if c.register.ien == 1 {
c.tmpdb = ^c.tmpbit & 1
c.tmprr = c.register.rr + c.register.carry + c.tmpdb

c.register.rr = c.tmprr & 1
c.register.carry = (c.tmprr & 0b10) >> 1
}
case one:
c.register.rr = 1
c.register.carry = 0
case nand:
if c.register.ien == 1 {
c.tmprr = c.register.rr & c.tmpbit

if c.tmprr == 1 {
c.register.rr = 0
} else if c.register.rr == 0 {
c.register.rr = 1
}
}
case or:
if c.register.ien == 1 {
c.register.rr = c.register.rr | c.tmpbit
}
case xor:
if c.register.ien == 1 {
c.register.rr = c.register.rr ^ c.tmpbit
}
case sto:
if c.register.oen == 1 {
c.flag.wrt = 1
}
case stoc:
if c.register.oen == 1 {
c.flag.wrt = 1
c.tmprr = ^c.register.rr & 1
}
case ien:
c.register.ien = c.tmpbit
case oen:
c.register.oen = c.tmpbit
case ioc:
c.flag.ioc = 1
fmt.Print("\a") // BEEP, depending on terminal settings
case rtn:
c.flag.rtn = 1
case skz:
c.flag.skz = 1
case nopf:
c.flag.f = 1
default:
return fmt.Errorf("unrecognized opcode. byte value: %X", b)
}

if c.flag.wrt == 1 {
if b != stoc {
c.tmprr = c.register.rr
}
}
return
}

func (c *CPU) parseAddressBin(b byte) (err error) {
var bitPos int

op := b & 0xF0
addr := b & 0x0F
switch {
case addr < 0x08:
// SR0 - SR7
c.whichBit = 1 << int(addr)
c.whichOutput = Scratch
c.tmpbit = (c.register.scratch & c.whichBit) >> int(addr)
case addr == 0x08 && !(op == sto || op == stoc):
// RR
c.tmpbit = c.register.rr
case addr < 0x10:
bitPos = int(addr - 0x08)
c.whichBit = 1 << bitPos

// OR0 - OR7
if op == sto || op == stoc {
c.whichBit = 1 << bitPos
c.whichOutput = Output
c.tmpbit = (c.register.output & c.whichBit) >> bitPos
} else {
// IR1 - IR7
c.tmpbit = (c.register.input & c.whichBit) >> bitPos
}
default:
return fmt.Errorf("unable to parse address in byte: %v", err)
}
return
}

func (c *CPU) processBin(args []string, speed int, nonint bool) {
ticker := time.NewTicker(time.Second / time.Duration(speed))
defer ticker.Stop()

source, err := os.Open(args[len(args)-1])
if err != nil {
fatalln(err)
}
defer source.Close()

reader := bufio.NewReader(source)
for {
b, err := reader.ReadByte()
if err != nil {
if err.Error() == "EOF" {
break
}
fatalln(err)
}

if err = c.parseAddressBin(b); err != nil {
fatalln(err)
}

if err = c.parseOpcodeBin(b & 0xF0); err != nil {
fatalln(err)
}

if c.flag.wrt == 1 {
if c.whichOutput == Output {
err = c.writeMemRegister(Output)
} else {
err = c.writeMemRegister(Scratch)
}
}
if err != nil {
fatalln(err)
}

if nonint {
c.flag.wrt = 0
continue
}

<-ticker.C
opString := opcodeStrings[b&0xF0>>4]
addrString := fmt.Sprintf("%02X", int(b&0x0F))
fmt.Println("INSTRUCTION : ", opString)
fmt.Println("MEMORY ADDRESS: ", addrString)

c.printCpuStat()
c.flag.wrt = 0
}
if nonint {
fmt.Printf("OUTPUT = %b (%d)\n",
c.register.output, c.register.output)
}
}
Loading