├── .gitignore ├── tools └── icmp │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── icmp-rcs │ └── icmp-rcs.go │ └── z80emu │ └── icmp-z80emu.c ├── rcs ├── z80 │ ├── doc.go │ ├── README.md │ ├── cpu_test.go │ ├── dasm_test.go │ ├── instructions_test.go │ ├── zex_test.go │ ├── reader.go │ └── addressing.go ├── namco │ ├── doc.go │ ├── n51xx.go │ ├── n54xx.go │ └── n06xx.go ├── cbm │ ├── doc.go │ ├── petscii │ │ ├── petscii.go │ │ └── petscii_table.go │ ├── screen.go │ └── vic.go ├── m6502 │ ├── doc.go │ ├── dormann_test.go │ ├── reader.go │ ├── cpu_test.go │ ├── addressing.go │ ├── dasm.go │ └── cpu.go ├── audio_test.go ├── rcs_test.go ├── audio.go ├── cpu.go ├── video.go └── rcs.go ├── doc ├── img │ ├── digiloi.png │ ├── monopole.png │ ├── c128-ready.png │ ├── c64-ready.png │ ├── c64-chargen.png │ ├── digiloi.thumb.png │ ├── c64-ready.thumb.png │ ├── monopole.thumb.png │ ├── pacman-attract.png │ ├── c128-ready.thumb.png │ ├── mspacman-attract.png │ ├── pacman-attract.thumb.png │ └── mspacman-attract.thumb.png ├── z80.md ├── galaga.md ├── c128.md ├── m6502.md ├── monitor.md └── memory.md ├── .vscode └── settings.json ├── go.mod ├── mock ├── memory_test.go ├── mock_test.go ├── mach.go ├── mock.go ├── memory.go └── cpu.go ├── gen ├── z80 │ ├── opcodes │ │ └── README.md │ ├── fuse │ │ ├── README.md │ │ └── fuse.go │ └── dasm │ │ ├── README.md │ │ └── dasm.go └── cbm │ └── petscii │ ├── README.md │ └── petscii.go ├── .travis.yml ├── system ├── c64 │ ├── roms.go │ ├── c64.go │ ├── memory.go │ ├── inputs.go │ └── memory_test.go ├── c128 │ ├── roms.go │ ├── mmu.go │ ├── vdc.go │ └── c128.go ├── galaga │ ├── encoding.go │ ├── roms.go │ ├── video.go │ └── galaga.go └── pacman │ ├── encoding.go │ ├── roms.go │ ├── audio.go │ ├── video.go │ └── inputs.go ├── Dockerfile ├── app ├── systems.go └── monitor │ ├── galaga.go │ └── namco.go ├── cmd ├── mb2h │ └── main.go ├── rcs-viewer │ ├── views.go │ └── main.go └── retro-cs │ └── main.go ├── LICENSE ├── config └── config.go ├── go.sum └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /tools/icmp/.gitignore: -------------------------------------------------------------------------------- 1 | icmp-z80emu 2 | *.txt 3 | -------------------------------------------------------------------------------- /rcs/z80/doc.go: -------------------------------------------------------------------------------- 1 | // Package z80 is the Zilog Z80 processor. 2 | package z80 3 | -------------------------------------------------------------------------------- /doc/img/digiloi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/digiloi.png -------------------------------------------------------------------------------- /doc/img/monopole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/monopole.png -------------------------------------------------------------------------------- /rcs/namco/doc.go: -------------------------------------------------------------------------------- 1 | // Package namco contains components for Namco systems. 2 | package namco 3 | -------------------------------------------------------------------------------- /doc/img/c128-ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/c128-ready.png -------------------------------------------------------------------------------- /doc/img/c64-ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/c64-ready.png -------------------------------------------------------------------------------- /rcs/cbm/doc.go: -------------------------------------------------------------------------------- 1 | // Package cbm contains components for Commodore Business Machines. 2 | package cbm 3 | -------------------------------------------------------------------------------- /rcs/m6502/doc.go: -------------------------------------------------------------------------------- 1 | // Package m6502 is the MOS Technology 6502 series processor. 2 | package m6502 3 | -------------------------------------------------------------------------------- /doc/img/c64-chargen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/c64-chargen.png -------------------------------------------------------------------------------- /doc/img/digiloi.thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/digiloi.thumb.png -------------------------------------------------------------------------------- /doc/img/c64-ready.thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/c64-ready.thumb.png -------------------------------------------------------------------------------- /doc/img/monopole.thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/monopole.thumb.png -------------------------------------------------------------------------------- /doc/img/pacman-attract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/pacman-attract.png -------------------------------------------------------------------------------- /doc/img/c128-ready.thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/c128-ready.thumb.png -------------------------------------------------------------------------------- /doc/img/mspacman-attract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/mspacman-attract.png -------------------------------------------------------------------------------- /doc/img/pacman-attract.thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/pacman-attract.thumb.png -------------------------------------------------------------------------------- /doc/img/mspacman-attract.thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackchip-org/retro-cs/HEAD/doc/img/mspacman-attract.thumb.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "galaga", 4 | "kernal", 5 | "petscii" 6 | ] 7 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/blackchip-org/retro-cs 2 | 3 | require ( 4 | github.com/chzyer/readline v1.5.0 5 | github.com/veandco/go-sdl2 v0.4.24 6 | golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect 7 | ) 8 | 9 | go 1.13 10 | -------------------------------------------------------------------------------- /mock/memory_test.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleMockRead() { 8 | data := []int{1, 2, 3} 9 | read := MockRead(data) 10 | fmt.Println(read()) 11 | fmt.Println(read()) 12 | fmt.Println(read()) 13 | // Output: 14 | // 1 15 | // 2 16 | // 3 17 | } 18 | -------------------------------------------------------------------------------- /gen/z80/opcodes/README.md: -------------------------------------------------------------------------------- 1 | # Z80 opcodes 2 | 3 | Generates the operations table using the algorithmic approach described 4 | by Cristian Dinu in "Decdoding Z80 Opcodes" found here: 5 | 6 | - http://www.z80.info/decoding.htm 7 | 8 | Generate the code with: 9 | 10 | ```bash 11 | go generate 12 | ``` 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: generic 4 | 5 | services: 6 | - docker 7 | 8 | before_install: 9 | - docker pull blackchip/retro-cs 10 | 11 | script: 12 | - docker run --mount type=bind,source="$(pwd)",target=/root/go/src/github.com/blackchip-org/retro-cs blackchip/retro-cs /bin/bash -c "cd /root/go/src/github.com/blackchip-org/retro-cs ; go test ./..." 13 | -------------------------------------------------------------------------------- /system/c64/roms.go: -------------------------------------------------------------------------------- 1 | package c64 2 | 3 | import "github.com/blackchip-org/retro-cs/rcs" 4 | 5 | var SystemROM = []rcs.ROM{ 6 | rcs.NewROM("basic ", "basic ", "79015323128650c742a3694c9429aa91f355905e"), 7 | rcs.NewROM("chargen", "chargen", "adc7c31e18c7c7413d54802ef2f4193da14711aa"), 8 | rcs.NewROM("kernal ", "kernal ", "1d503e56df85a62fee696e7618dc5b4e781df1bb"), 9 | } 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y \ 5 | golang \ 6 | git \ 7 | libsdl2-dev \ 8 | libsdl2-image-dev \ 9 | libsdl2-mixer-dev \ 10 | libsdl2-ttf-dev \ 11 | libsdl2-gfx-dev 12 | 13 | RUN mkdir -p /root/go/src/github.com/blackchip-org/retro-cs 14 | RUN go get github.com/veandco/go-sdl2/sdl 15 | RUN go get github.com/chzyer/readline 16 | -------------------------------------------------------------------------------- /tools/icmp/Makefile: -------------------------------------------------------------------------------- 1 | Z80EMU_HOME=$(HOME)/z80emu 2 | 3 | all: z80emu/icmp-z80emu 4 | z80emu/icmp-z80emu > z80emu.txt 5 | go run icmp-rcs/icmp-rcs.go > rcs.txt 6 | diff z80emu.txt rcs.txt 7 | 8 | z80emu/icmp-z80emu: z80emu/icmp-z80emu.c 9 | gcc -o z80emu/icmp-z80emu \ 10 | -I$(Z80EMU_HOME) \ 11 | $(Z80EMU_HOME)/z80emu.c \ 12 | z80emu/icmp-z80emu.c 13 | 14 | clean: 15 | rm *.txt 16 | rm z80emu/icmp-z80emu 17 | 18 | -------------------------------------------------------------------------------- /rcs/namco/n51xx.go: -------------------------------------------------------------------------------- 1 | package namco 2 | 3 | import "log" 4 | 5 | type N51XX struct { 6 | WatchR bool 7 | WatchW bool 8 | } 9 | 10 | func NewN51XX() *N51XX { 11 | return &N51XX{} 12 | } 13 | 14 | func (n *N51XX) Write(v uint8) { 15 | if n.WatchW { 16 | log.Printf("write input controller: %02v", v) 17 | } 18 | } 19 | 20 | func (n *N51XX) Read() uint8 { 21 | if n.WatchR { 22 | log.Printf("read input controller") 23 | } 24 | return 0 25 | } 26 | -------------------------------------------------------------------------------- /rcs/namco/n54xx.go: -------------------------------------------------------------------------------- 1 | package namco 2 | 3 | import "log" 4 | 5 | type N54XX struct { 6 | WatchR bool 7 | WatchW bool 8 | } 9 | 10 | func NewN54XX() *N54XX { 11 | return &N54XX{} 12 | } 13 | 14 | func (n *N54XX) Write(v uint8) { 15 | if n.WatchW { 16 | log.Printf("write noise generator: %02v\n", v) 17 | } 18 | } 19 | 20 | func (n *N54XX) Read() uint8 { 21 | if n.WatchR { 22 | log.Printf("read noise generator\n") 23 | } 24 | return 0 25 | } 26 | -------------------------------------------------------------------------------- /system/c128/roms.go: -------------------------------------------------------------------------------- 1 | package c128 2 | 3 | import "github.com/blackchip-org/retro-cs/rcs" 4 | 5 | var SystemROM = []rcs.ROM{ 6 | rcs.NewROM("basichi", "basichi", "c4fb4a714e48a7bf6c28659de0302183a0e0d6c0"), 7 | rcs.NewROM("basiclo", "basiclo", "d53a7884404f7d18ebd60dd3080c8f8d71067441"), 8 | rcs.NewROM("chargen", "chargen", "29ed066d513f2d5c09ff26d9166ba23c2afb2b3f"), 9 | rcs.NewROM("kernal ", "kernal ", "ceb6e1a1bf7e08eb9cbc651afa29e26adccf38ab"), 10 | } 11 | -------------------------------------------------------------------------------- /mock/mock_test.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestPanicWriter(t *testing.T) { 11 | log.SetOutput(&PanicWriter{}) 12 | defer func() { 13 | log.SetOutput(os.Stderr) 14 | if r := recover(); r != nil { 15 | msg := r.(string) 16 | if !strings.HasSuffix(msg, "This is a panic\n") { 17 | t.Errorf("panic message is: %v", msg) 18 | } 19 | } 20 | }() 21 | log.Printf("This is a panic") 22 | t.Fail() 23 | } 24 | -------------------------------------------------------------------------------- /gen/cbm/petscii/README.md: -------------------------------------------------------------------------------- 1 | # petscii 2 | 3 | Generates the PETSCII tables using the Unicode to PETSCII mapping table compiled by Rebecca Turner. The source used to generate this table are not found in this repository. Download and place in the following location: 4 | 5 | ``` 6 | ~/rcs/ext/petscii/table.txt 7 | ``` 8 | 9 | Document downloaded from here: 10 | 11 | https://github.com/9999years/Unicode-PETSCII/blob/master/table.txt 12 | 13 | Generate `petscii.go` with: 14 | 15 | ```bash 16 | go generate 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /app/systems.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/blackchip-org/retro-cs/rcs" 5 | "github.com/blackchip-org/retro-cs/system/c128" 6 | "github.com/blackchip-org/retro-cs/system/c64" 7 | "github.com/blackchip-org/retro-cs/system/galaga" 8 | "github.com/blackchip-org/retro-cs/system/pacman" 9 | ) 10 | 11 | var Systems = map[string]func(rcs.SDLContext) (*rcs.Mach, error){ 12 | "c64": c64.New, 13 | "c128": c128.New, 14 | "galaga": galaga.New, 15 | "pacman": pacman.New, 16 | "mspacman": pacman.NewMs, 17 | } 18 | -------------------------------------------------------------------------------- /gen/z80/fuse/README.md: -------------------------------------------------------------------------------- 1 | # fuse 2 | 3 | Z80 tests designed for the Free Unix Spectrum Emulator (FUSE). 4 | 5 | Source files used to generate the tests are not found in this repository. Download and place in the following locations: 6 | 7 | ``` 8 | ~/rcs/ext/fuse/tests.expected 9 | ~/rcs/ext/fuse/tests.in 10 | ``` 11 | 12 | The original location of FUSE is here: 13 | 14 | - http://fuse-emulator.sourceforge.net/ 15 | 16 | The files found in the resource pack were downloaded from: 17 | 18 | - https://github.com/descarte1/fuse-emulator-fuse/tree/fuse-1-3-6/z80/tests 19 | 20 | Generate `in.go` and `expected.go` with: 21 | 22 | ```bash 23 | go generate 24 | ``` -------------------------------------------------------------------------------- /mock/mach.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import "github.com/blackchip-org/retro-cs/rcs" 4 | 5 | func NewMach() *rcs.Mach { 6 | ResetMemory() 7 | return &rcs.Mach{ 8 | Comps: []rcs.Component{ 9 | rcs.NewComponent("mem", "mem", "", TestMemory), 10 | rcs.NewComponent("cpu", "cpu", "mem", NewCPU(TestMemory)), 11 | }, 12 | CharDecoders: map[string]rcs.CharDecoder{ 13 | "ascii": rcs.ASCIIDecoder, 14 | "az26": AZ26Decoder, 15 | }, 16 | DefaultEncoding: "ascii", 17 | } 18 | } 19 | 20 | var AZ26Decoder = func(code uint8) (rune, bool) { 21 | if code < 1 || code > 26 { 22 | return 0, false 23 | } 24 | return rune(64 + code), true 25 | } 26 | -------------------------------------------------------------------------------- /mock/mock.go: -------------------------------------------------------------------------------- 1 | // Package mock contains testing mocks and other various utilities 2 | // for testing code. 3 | package mock 4 | 5 | import "bytes" 6 | 7 | // PanicWriter is a write that panics after the first newline has been 8 | // written. Useful for tracking down where warnings are being emitted 9 | // in the logger. 10 | type PanicWriter struct { 11 | buf bytes.Buffer 12 | } 13 | 14 | // Write appends the output to an internal buffer and then panics with 15 | // the written text once a newline is found. 16 | func (p *PanicWriter) Write(out []byte) (int, error) { 17 | p.buf.Write(out) 18 | for _, b := range out { 19 | if b == '\n' { 20 | panic(p.buf.String()) 21 | } 22 | } 23 | return len(out), nil 24 | } 25 | -------------------------------------------------------------------------------- /tools/icmp/README.md: -------------------------------------------------------------------------------- 1 | # icmp 2 | 3 | Instruction comparision tool. 4 | 5 | This tool was useful to find the source of errors in the z80 instruction code. It uses the z80emu code written by Lin Ke-Fong: 6 | 7 | https://github.com/anotherlin/z80emu 8 | 9 | There are two code wrappers, one for z80emu and one for RCS, to print a text file of all inputs and outputs of an instruction. Both programs are run and the text files are compared for differences. 10 | 11 | The code is hard-wired to a specific instruction and is modified as needed. To run the test: 12 | 13 | ``` 14 | make 15 | ``` 16 | 17 | The z80emu code must be available to compile. By default the Makefile uses `$(HOME)/z80emu` but can be overriden by setting the Z80EMU_HOME variable. 18 | -------------------------------------------------------------------------------- /gen/z80/dasm/README.md: -------------------------------------------------------------------------------- 1 | # dasm 2 | 3 | Generates the disassembly table using the "Full Z80 Opcode List Including 4 | Undocumented Opcodes" written by J.G. Harston. This source used to generate the table and tests are not found in this repository. Download and place in the following location: 5 | 6 | ``` 7 | ~/rcs/ext/harston/z80oplist.txt 8 | ``` 9 | 10 | Document downloaded from here: 11 | 12 | - http://www.z80.info/z80oplist.txt 13 | 14 | Expected values from the disassembler are found in `expected.txt` and 15 | are in the following two line format: 16 | 17 | - List of bytes in hexadecimal separated by a space 18 | - Expected output of the disassembler 19 | 20 | Each test is delimited by a blank line. 21 | 22 | Generate `dasm.go` and `dasm_harston_test.go` with: 23 | 24 | ```bash 25 | go generate 26 | ``` 27 | -------------------------------------------------------------------------------- /cmd/mb2h/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func mb2h(in string) (string, error) { 12 | in = strings.Replace(in, "-", "0", -1) 13 | strlo := strings.Replace(in, "x", "0", -1) 14 | strhi := strings.Replace(in, "x", "1", -1) 15 | 16 | lo, err := strconv.ParseUint(strlo, 2, 16) 17 | if err != nil { 18 | return "", err 19 | } 20 | hi, err := strconv.ParseUint(strhi, 2, 16) 21 | if err != nil { 22 | return "", err 23 | } 24 | if lo == hi { 25 | return fmt.Sprintf("%04x", lo), nil 26 | } 27 | return fmt.Sprintf("%04x - %04x", lo, hi), nil 28 | } 29 | 30 | func main() { 31 | flag.Parse() 32 | val, err := mb2h(flag.Arg(0)) 33 | if err != nil { 34 | fmt.Printf("error: %v\n", err) 35 | os.Exit(1) 36 | } 37 | fmt.Println(val) 38 | } 39 | -------------------------------------------------------------------------------- /rcs/cbm/petscii/petscii.go: -------------------------------------------------------------------------------- 1 | package petscii 2 | 3 | const ( 4 | White = 0x05 5 | Red = 0x1c 6 | Green = 0x1e 7 | Blue = 0x1f 8 | Orange = 0x81 9 | Black = 0x90 10 | Brown = 0x95 11 | LightRed = 0x96 12 | DarkGray = 0x97 13 | MediumGray = 0x98 14 | LightGreen = 0x99 15 | LightBlue = 0x9a 16 | LightGray = 0x9b 17 | Purple = 0x9c 18 | Yellow = 0x9e 19 | Cyan = 0x9f 20 | ) 21 | 22 | // Decoder converts byte values to PETSCII equivilents in Unicode. 23 | var Decoder = func(code uint8) (rune, bool) { 24 | ch, printable := tableUnshifted[code] 25 | return ch, printable 26 | } 27 | 28 | // ShiftedDecoder converts byte values to PETSCII equivilents in Unicode. 29 | var ShiftedDecoder = func(code uint8) (rune, bool) { 30 | ch, printable := tableShifted[code] 31 | return ch, printable 32 | } 33 | -------------------------------------------------------------------------------- /system/galaga/encoding.go: -------------------------------------------------------------------------------- 1 | package galaga 2 | 3 | var chars = map[uint8]rune{ 4 | 0x00: '0', 5 | 0x01: '1', 6 | 0x02: '2', 7 | 0x03: '3', 8 | 0x04: '4', 9 | 0x05: '5', 10 | 0x06: '6', 11 | 0x07: '7', 12 | 0x08: '8', 13 | 0x09: '9', 14 | 0x0a: 'A', 15 | 0x0b: 'B', 16 | 0x0c: 'C', 17 | 0x0d: 'D', 18 | 0x0e: 'E', 19 | 0x0f: 'F', 20 | 0x10: 'G', 21 | 0x11: 'H', 22 | 0x12: 'I', 23 | 0x13: 'J', 24 | 0x14: 'K', 25 | 0x15: 'L', 26 | 0x16: 'M', 27 | 0x17: 'N', 28 | 0x18: 'O', 29 | 0x19: 'P', 30 | 0x1a: 'Q', 31 | 0x1b: 'R', 32 | 0x1c: 'S', 33 | 0x1d: 'T', 34 | 0x1e: 'U', 35 | 0x1f: 'V', 36 | 0x20: 'W', 37 | 0x21: 'X', 38 | 0x22: 'Y', 39 | 0x23: 'Z', 40 | 0x24: ' ', 41 | 0x29: '%', 42 | 0x2a: '.', 43 | 0x2c: '!', 44 | 0x2e: '©', 45 | } 46 | 47 | var GalagaDecoder = func(code uint8) (rune, bool) { 48 | ch, printable := chars[code] 49 | return ch, printable 50 | } 51 | -------------------------------------------------------------------------------- /rcs/z80/README.md: -------------------------------------------------------------------------------- 1 | # z80 2 | 3 | ## zex_test.go 4 | 5 | The Z80 Instruction Exerciser written by Frank D. Cringle. 6 | 7 | Source code used to run the tests are not found in this repository. Download 8 | and place in the following location: 9 | 10 | ``` 11 | ~/rcs/ext/zex/zexdoc.com 12 | ``` 13 | 14 | The original location of the exerciser seems to be here: 15 | 16 | - http://mdfs.net/Software/Z80/Exerciser/ 17 | 18 | The sources found in the resource pack were downloaded from: 19 | 20 | - https://github.com/anotherlin/z80emu/blob/master/testfiles 21 | 22 | Helpful references: 23 | 24 | - https://floooh.github.io/2016/07/12/z80-rust-ms1.html 25 | - http://jeffavery.ca/computers/macintosh_z80exerciser.html 26 | 27 | Run the functional test with: 28 | 29 | ```bash 30 | go test -v -tags=ext -timeout 60m 31 | ``` 32 | 33 | Run the benchmarks with: 34 | 35 | ```bash 36 | go test -run=X -tags=ext -bench=. 37 | ``` -------------------------------------------------------------------------------- /mock/memory.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "github.com/blackchip-org/retro-cs/rcs" 5 | ) 6 | 7 | // The FUSE tests want the full address space available. Creating memory 8 | // is expensive and with the amount of tests to run it takes up to 12 seconds 9 | // to complete. Instead, create memory once and zero out the backing 10 | // slice each time. 11 | var ( 12 | TestMemory *rcs.Memory 13 | testRAM []uint8 14 | testZero []uint8 15 | ) 16 | 17 | func init() { 18 | testRAM = make([]uint8, 0x10000, 0x10000) 19 | testZero = make([]uint8, 0x10000, 0x10000) 20 | TestMemory = rcs.NewMemory(1, 0x10000) 21 | TestMemory.MapRAM(0, testRAM) 22 | } 23 | 24 | // ResetMemory zeros out all memory values in TestMemory. 25 | func ResetMemory() { 26 | copy(testRAM, testZero) 27 | } 28 | 29 | func MockRead(data []int) func() uint8 { 30 | pos := 0 31 | return func() uint8 { 32 | pos++ 33 | return uint8(data[pos-1]) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rcs/z80/cpu_test.go: -------------------------------------------------------------------------------- 1 | package z80 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestString(t *testing.T) { 8 | cpu := New(nil) 9 | cpu.A = 0x0a 10 | cpu.F = 0xff 11 | cpu.B = 0x0b 12 | cpu.C = 0x0c 13 | cpu.D = 0x0d 14 | cpu.E = 0x0e 15 | cpu.H = 0xf0 16 | cpu.L = 0x0f 17 | cpu.IXH = 0x12 18 | cpu.IXL = 0x34 19 | cpu.IYH = 0x56 20 | cpu.IYL = 0x78 21 | cpu.SP = 0xabcd 22 | cpu.I = 0xee 23 | cpu.R = 0xff 24 | 25 | cpu.A1 = 0xa0 26 | cpu.F1 = 0x88 27 | cpu.B1 = 0xb0 28 | cpu.C1 = 0xc0 29 | cpu.D1 = 0xd0 30 | cpu.E1 = 0xe0 31 | cpu.H1 = 0x0f 32 | cpu.L1 = 0xf0 33 | 34 | cpu.IFF1 = true 35 | cpu.IFF2 = true 36 | 37 | have := cpu.String() 38 | want := "" + 39 | " pc af bc de hl ix iy sp i r\n" + 40 | "0000 0aff 0b0c 0d0e f00f 1234 5678 abcd ee ff iff1\n" + 41 | "im 0 a088 b0c0 d0e0 0ff0 S Z 5 H 3 V N C iff2\n" 42 | if have != want { 43 | t.Errorf("\n have: \n%v \n want: \n%v", have, want) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /rcs/cbm/screen.go: -------------------------------------------------------------------------------- 1 | package cbm 2 | 3 | import ( 4 | "github.com/blackchip-org/retro-cs/rcs" 5 | "github.com/blackchip-org/retro-cs/rcs/cbm/petscii" 6 | ) 7 | 8 | // http://sta.c64.org/cbm64pettoscr.html 9 | 10 | var ScreenDecoder = func(code uint8) (rune, bool) { 11 | return decoder(code, petscii.Decoder) 12 | } 13 | 14 | var ScreenShiftedDecoder = func(code uint8) (rune, bool) { 15 | return decoder(code, petscii.ShiftedDecoder) 16 | } 17 | 18 | func decoder(code uint8, decode rcs.CharDecoder) (rune, bool) { 19 | switch { 20 | case code == 0x5e: 21 | return decode(0xff) 22 | case code >= 0x00 && code <= 0x1f: 23 | return decode(code + 64) 24 | case code >= 0x20 && code <= 0x3f: 25 | return decode(code) 26 | case code >= 0x40 && code <= 0x5f: 27 | return decode(code + 32) 28 | case code >= 0x60 && code <= 0x7f: 29 | return decode(code + 64) 30 | case code >= 0x80 && code <= 0x9f: 31 | return decode(code - 128) 32 | case code >= 0xc0 && code <= 0xdf: 33 | return decode(code - 64) 34 | } 35 | return decode(code) 36 | } 37 | -------------------------------------------------------------------------------- /system/pacman/encoding.go: -------------------------------------------------------------------------------- 1 | package pacman 2 | 3 | var chars = map[uint8]rune{ 4 | 0x00: '0', 5 | 0x01: '1', 6 | 0x02: '2', 7 | 0x03: '3', 8 | 0x04: '4', 9 | 0x05: '5', 10 | 0x06: '6', 11 | 0x07: '7', 12 | 0x08: '8', 13 | 0x09: '9', 14 | 0x0a: 'a', 15 | 0x0b: 'b', 16 | 0x0c: 'c', 17 | 0x0d: 'd', 18 | 0x0e: 'e', 19 | 0x0f: 'f', 20 | 0x30: '0', 21 | 0x31: '1', 22 | 0x32: '2', 23 | 0x33: '3', 24 | 0x34: '4', 25 | 0x35: '5', 26 | 0x36: '6', 27 | 0x37: '7', 28 | 0x38: '8', 29 | 0x39: '9', 30 | 0x40: ' ', 31 | 0x41: 'A', 32 | 0x42: 'B', 33 | 0x43: 'C', 34 | 0x44: 'D', 35 | 0x45: 'E', 36 | 0x46: 'F', 37 | 0x47: 'G', 38 | 0x48: 'H', 39 | 0x49: 'I', 40 | 0x4a: 'J', 41 | 0x4b: 'K', 42 | 0x4c: 'L', 43 | 0x4d: 'M', 44 | 0x4e: 'N', 45 | 0x4f: 'O', 46 | 0x50: 'P', 47 | 0x51: 'Q', 48 | 0x52: 'R', 49 | 0x53: 'S', 50 | 0x54: 'T', 51 | 0x55: 'U', 52 | 0x56: 'V', 53 | 0x57: 'W', 54 | 0x58: 'X', 55 | 0x59: 'Y', 56 | 0x5a: 'Z', 57 | 0x5b: '!', 58 | } 59 | 60 | var PacmanDecoder = func(code uint8) (rune, bool) { 61 | ch, printable := chars[code] 62 | return ch, printable 63 | } 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2019 Mike McGann 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /system/galaga/roms.go: -------------------------------------------------------------------------------- 1 | package galaga 2 | 3 | import "github.com/blackchip-org/retro-cs/rcs" 4 | 5 | var ROM = map[string][]rcs.ROM{ 6 | "galaga": []rcs.ROM{ 7 | rcs.NewROM("code1 ", "04m_g01.bin", "6907773db7c002ecde5e41853603d53387c5c7cd"), 8 | rcs.NewROM("code1 ", "04k_g02.bin", "666975aed5ce84f09794c54b550d64d95ab311f0"), 9 | rcs.NewROM("code1 ", "04j_g03.bin", "481f443aea3ed3504ec2f3a6bfcf3cd47e2f8f81"), 10 | rcs.NewROM("code1 ", "04h_g04.bin", "366cb0dbd31b787e64f88d182108b670d03b393e"), 11 | rcs.NewROM("code2 ", "04e_g05.bin", "d29b68d6aab3217fa2106b3507b9273ff3f927bf"), 12 | rcs.NewROM("code3 ", "04d_g06.bin", "d6cb439de0718826d1a0363c9d77de8740b18ecf"), 13 | rcs.NewROM("tiles ", "07m_g08.bin", "62f1279a784ab2f8218c4137c7accda00e6a3490"), 14 | rcs.NewROM("sprites ", "07e_g10.bin", "e697c180178cabd1d32483c5d8889a40633f7857"), 15 | rcs.NewROM("sprites ", "07h_g09.bin", "c340ed8c25e0979629a9a1730edc762bd72d0cff"), 16 | rcs.NewROM("palettes", "5n.bin ", "1a6dea13b4af155d9cb5b999a75d4f1eb9c71346"), 17 | rcs.NewROM("colors ", "2n.bin ", "7323084320bb61ae1530d916f5edd8835d4d2461"), 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /doc/z80.md: -------------------------------------------------------------------------------- 1 | # z80 2 | 3 | ## References 4 | 5 | - Avery, Jeff, "Using Z80 Instruction Exerciser (Zexall/ /Zexdoc)", http://jeffavery.ca/computers/macintosh_z80exerciser.html 6 | - Cringle, Frank D., "Z80 Instruction Exerciser", http://mdfs.net/Software/Z80/Exerciser/ 7 | - Dinu, Cristian, "Decdoding Z80 Opcodes", http://www.z80.info/decoding.htm 8 | - "Free Unix Spectrum Emulator", https://github.com/FuseEmulator/fuse-emulator-svn/tree/master/fuse/z80/tests 9 | - Frunze, Alexey, "Overflow and Carry flags on Z80", https://stackoverflow.com/questions/8034566/overflow-and-carry-flags-on-z80/8037485#8037485 10 | - Harston, J.G., "Full Z80 Opcode List Including Undocumented Opcodes", http://www.z80.info/z80oplist.txt 11 | - Ke-Fong, Lin, "z80emu", https://github.com/anotherlin/z80emu 12 | - Weissflog, Andre, "Z80 emulation in Rust, Milestone 1", https://floooh.github.io/2016/07/12/z80-rust-ms1.html 13 | - Young, Sean, "The Undocumented Z80 Documented", http://datasheets.chipdb.org/Zilog/Z80/z80-documented-0.90.pdf 14 | - Young, Sean, et al. "Z80 Flag Affection", http://www.z80.info/z80sflag.htm 15 | - "Z80 Family CPU User Manual", http://www.z80.info/zip/z80cpu_um.pdf 16 | -------------------------------------------------------------------------------- /rcs/m6502/dormann_test.go: -------------------------------------------------------------------------------- 1 | // +build ext 2 | 3 | package m6502 4 | 5 | import ( 6 | "io/ioutil" 7 | "log" 8 | "path/filepath" 9 | "testing" 10 | 11 | "github.com/blackchip-org/retro-cs/config" 12 | "github.com/blackchip-org/retro-cs/rcs" 13 | ) 14 | 15 | var ( 16 | sourceDir = filepath.Join(config.ResourceDir(), "ext", "m6502") 17 | ) 18 | 19 | func TestDormann(t *testing.T) { 20 | mem := rcs.NewMemory(1, 0x10000) 21 | ram := make([]uint8, 0x10000, 0x10000) 22 | code, err := ioutil.ReadFile(filepath.Join(sourceDir, "6502_functional_test.bin")) 23 | if err != nil { 24 | t.Fatalf("unable to load test runner: %v", err) 25 | } 26 | 27 | mem.MapRAM(0x0, ram) 28 | mem.MapRAM(0x0, code) 29 | 30 | cpu := New(mem) 31 | cpu.SetPC(0x03ff) 32 | log.SetFlags(0) 33 | cpu.WatchBRK = true 34 | dasm := cpu.NewDisassembler() 35 | for { 36 | here := cpu.PC() + cpu.Offset() 37 | // Success points 38 | if here == 0x346c || here == 0x3469 { 39 | break 40 | } 41 | dasm.SetPC(here) 42 | ppc := cpu.PC() 43 | cpu.Next() 44 | // If the PC hasn't moved, its a trap 45 | if ppc == cpu.PC() { 46 | t.Fatalf("\n[trap]\n%v", cpu) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tools/icmp/icmp-rcs/icmp-rcs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/blackchip-org/retro-cs/mock" 7 | "github.com/blackchip-org/retro-cs/rcs/z80" 8 | ) 9 | 10 | func main() { 11 | af() 12 | } 13 | 14 | func cpd() { 15 | for a := 0; a <= 0xff; a++ { 16 | for _hl := 0; _hl <= 0xff; _hl++ { 17 | for f := 0; f <= 1; f++ { 18 | for c := 2; c <= 2; c++ { 19 | mock.ResetMemory() 20 | mem := mock.TestMemory 21 | cpu := z80.New(mem) 22 | mem.Write(0x1234, uint8(_hl)) 23 | cpu.H, cpu.L = 0x12, 0x34 24 | cpu.C = uint8(c) 25 | cpu.A = uint8(a) 26 | cpu.F = uint8(f) 27 | mem.WriteN(0x00, 0xed, 0xb9) 28 | cpu.Next() 29 | fmt.Printf("a:%02x _hl:%02x c:%02x f:%02x => b:%02x c:%02x f:%02x\n", a, _hl, c, f, cpu.B, cpu.C, cpu.F) 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | func af() { 37 | for f := 0; f <= 0xff; f++ { 38 | for a := 0; a <= 0xff; a++ { 39 | mock.ResetMemory() 40 | mem := mock.TestMemory 41 | cpu := z80.New(mem) 42 | mem.Write(0x00, 0x17) 43 | cpu.A = uint8(a) 44 | cpu.F = uint8(f) 45 | cpu.Next() 46 | fmt.Printf("a:%02x f:%02x => a:%02x f:%02x\n", a, f, cpu.A, cpu.F) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/user" 7 | "path/filepath" 8 | ) 9 | 10 | var ( 11 | UserHome string // the user's home directory 12 | RCSDir string // use this as the home directory 13 | UserDir string 14 | DataDir string // data directory 15 | VarDir string // Directory where runtime variable data is stored 16 | System string 17 | ) 18 | 19 | func init() { 20 | usr, err := user.Current() 21 | if err != nil { 22 | log.Printf("unable to get home directory: %v", err) 23 | } 24 | UserHome = usr.HomeDir 25 | } 26 | 27 | // ResourceDir returns the root directory where RCS data can be found. Locations 28 | // for the root directory are checked in this order: 1) The value of the 29 | // Home variable in the configuration, 2) The value of the RCS_HOME 30 | // environmental variable, 3) The "retro-cs" directory in the user's home 31 | // directory. 32 | func ResourceDir() string { 33 | userHome := "." 34 | u, err := user.Current() 35 | if err != nil { 36 | log.Printf("unable to find home directory: %v", err) 37 | } else { 38 | userHome = u.HomeDir 39 | } 40 | 41 | root := RCSDir 42 | if root == "" { 43 | root = os.Getenv("RCS_HOME") 44 | } 45 | if root == "" { 46 | root = filepath.Join(userHome, "rcs") 47 | } 48 | return root 49 | } 50 | -------------------------------------------------------------------------------- /app/monitor/galaga.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/chzyer/readline" 8 | 9 | "github.com/blackchip-org/retro-cs/rcs" 10 | "github.com/blackchip-org/retro-cs/system/galaga" 11 | ) 12 | 13 | type modGalaga struct { 14 | mon *Monitor 15 | out *log.Logger 16 | galaga *galaga.System 17 | } 18 | 19 | func newModGalaga(mon *Monitor, comp rcs.Component) module { 20 | return &modGalaga{ 21 | mon: mon, 22 | out: mon.out, 23 | galaga: comp.C.(*galaga.System), 24 | } 25 | } 26 | 27 | func (m *modGalaga) Command(args []string) error { 28 | if err := checkLen(args, 1, maxArgs); err != nil { 29 | return err 30 | } 31 | switch args[0] { 32 | case "interrupt-enable1": 33 | return valueBit(m.out, &m.galaga.InterruptEnable0, (1 << 0), args[1:]) 34 | case "interrupt-enable2": 35 | return valueBit(m.out, &m.galaga.InterruptEnable1, (1 << 0), args[1:]) 36 | case "interrupt-enable3": 37 | return valueBit(m.out, &m.galaga.InterruptEnable2, (1 << 0), args[1:]) 38 | } 39 | return fmt.Errorf("no such command: %v", args[0]) 40 | } 41 | 42 | func (m *modGalaga) AutoComplete() []readline.PrefixCompleterInterface { 43 | return []readline.PrefixCompleterInterface{ 44 | readline.PcItem("interrupt-enable1"), 45 | readline.PcItem("interrupt-enable2"), 46 | readline.PcItem("interrupt-enable3"), 47 | } 48 | } 49 | 50 | func (m *modGalaga) Silence() error { 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /doc/galaga.md: -------------------------------------------------------------------------------- 1 | # galaga 2 | 3 | ## Status 4 | 5 | - Does not work 6 | - Tiles and sprites available in rcs-viewer 7 | - Boots to test screen 8 | 9 | ## ROMs 10 | The ROMs used for this emulator were obtained from the MAME 0.37b5 ROM Set. The Internet Archive is a great resource. The correct SHA1 checksums are listed below: 11 | 12 | Place these files in `~/rcs/data/galaga` 13 | ``` 14 | d6cb439de0718826d1a0363c9d77de8740b18ecf 04d_g06.bin 15 | d29b68d6aab3217fa2106b3507b9273ff3f927bf 04e_g05.bin 16 | 366cb0dbd31b787e64f88d182108b670d03b393e 04h_g04.bin 17 | 481f443aea3ed3504ec2f3a6bfcf3cd47e2f8f81 04j_g03.bin 18 | 666975aed5ce84f09794c54b550d64d95ab311f0 04k_g02.bin 19 | 6907773db7c002ecde5e41853603d53387c5c7cd 04m_g01.bin 20 | e697c180178cabd1d32483c5d8889a40633f7857 07e_g10.bin 21 | c340ed8c25e0979629a9a1730edc762bd72d0cff 07h_g09.bin 22 | 62f1279a784ab2f8218c4137c7accda00e6a3490 07m_g08.bin 23 | dd10147c4f05fede7ae6e7a760681700a660e87e 1c.bin 24 | 6bef9102b97c83025a2cf84e89d95f2d44c3d2ed 1d.bin 25 | 7323084320bb61ae1530d916f5edd8835d4d2461 2n.bin 26 | bedba65816abfc2ebeacac6ee335ca6f136e3e3d 5c.bin 27 | 1a6dea13b4af155d9cb5b999a75d4f1eb9c71346 5n.bin 28 | ``` 29 | 30 | ## Viewers 31 | ``` 32 | rcs-viewer galaga:sprites 33 | rcs-viewer galaga:tiles 34 | ``` 35 | 36 | ## References 37 | 38 | - Cantrell, Christopher, "Galaga", http://www.computerarcheology.com/Arcade/Galaga/ 39 | - Salmoria, Nicola, et al, "Galaga", https://github.com/mamedev/mame/blob/master/src/mame/drivers/galaga.cpp 40 | - Severini, Palao, "Galaga: an Arcade machine emulator for Windows and HTML5", https://paoloseverini.wordpress.com/2016/02/13/galaga-an-arcade-machine-emulator-for-windows-and-html5/ 41 | -------------------------------------------------------------------------------- /system/c128/mmu.go: -------------------------------------------------------------------------------- 1 | package c128 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/blackchip-org/retro-cs/rcs" 7 | ) 8 | 9 | var mmuRegs = []string{"A", "B", "C", "D"} 10 | 11 | // MMU is the memory management unit 12 | type MMU struct { 13 | Mem *rcs.Memory // configuration register is the bank number 14 | LCR [4]uint8 // load configuration register 15 | PCR [4]uint8 // pre-configuration register 16 | // FIXME: What is this for? 17 | // Mode uint8 18 | 19 | WatchCR rcs.FlagRW 20 | WatchLCR rcs.FlagRW 21 | WatchPCR rcs.FlagRW 22 | } 23 | 24 | func NewMMU(mem *rcs.Memory) *MMU { 25 | return &MMU{ 26 | Mem: mem, 27 | } 28 | } 29 | 30 | func (m *MMU) ReadCR() uint8 { 31 | v := uint8(m.Mem.Bank()) 32 | if m.WatchCR.R { 33 | log.Printf("0x%02x <= mmu:cr", v) 34 | } 35 | return v 36 | } 37 | 38 | func (m *MMU) WriteCR(v uint8) { 39 | if m.WatchCR.W { 40 | log.Printf("mmu:cr <= 0x%02x", v) 41 | } 42 | m.Mem.SetBank(int(v)) 43 | } 44 | 45 | func (m *MMU) ReadLCR(i int) uint8 { 46 | v := m.LCR[i] 47 | if m.WatchLCR.R { 48 | log.Printf("0x%02x <= mmu:lcr-%v", v, mmuRegs[i]) 49 | } 50 | return v 51 | } 52 | 53 | func (m *MMU) WriteLCR(i int, v uint8) { 54 | if m.WatchLCR.W { 55 | log.Printf("mmu:lcr-%v <= 0x%02x", mmuRegs[i], v) 56 | } 57 | m.LCR[i] = v 58 | // FIXME: something is wrong here 59 | //m.StoreCR(m.LCR[i]) 60 | } 61 | 62 | func (m *MMU) ReadPCR(i int) uint8 { 63 | v := m.PCR[i] 64 | if m.WatchPCR.R { 65 | log.Printf("0x%02x <= mmu:pcr-%v", v, mmuRegs[i]) 66 | } 67 | return v 68 | } 69 | 70 | func (m *MMU) WritePCR(i int, v uint8) { 71 | if m.WatchPCR.W { 72 | log.Printf("mmu:pcr-%v <= 0x%02x", mmuRegs[i], v) 73 | } 74 | m.PCR[i] = v 75 | } 76 | -------------------------------------------------------------------------------- /rcs/audio_test.go: -------------------------------------------------------------------------------- 1 | package rcs 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestFill(t *testing.T) { 11 | v := NewVoice(8) 12 | v.Freq = 2 13 | v.Vol = 1.0 14 | v.Waveform = []float64{-1, 0, 1, 0} 15 | 16 | have := make([]float64, 8, 8) 17 | want := []float64{-1, 0, 1, 0, -1, 0, 1, 0} 18 | v.Fill(have, len(have)) 19 | 20 | if !reflect.DeepEqual(have, want) { 21 | t.Errorf("\n have: %v \n want: %v", have, want) 22 | } 23 | } 24 | 25 | func TestFillHalfVol(t *testing.T) { 26 | v := NewVoice(8) 27 | v.Freq = 2 28 | v.Vol = 0.5 29 | v.Waveform = []float64{-1, 0, 1, 0} 30 | 31 | have := make([]float64, 8, 8) 32 | want := []float64{-0.5, 0, 0.5, 0, -0.5, 0, 0.5, 0} 33 | v.Fill(have, len(have)) 34 | 35 | if !reflect.DeepEqual(have, want) { 36 | t.Errorf("\n have: %v \n want: %v", have, want) 37 | } 38 | } 39 | 40 | func TestFillStretch(t *testing.T) { 41 | v := NewVoice(8) 42 | v.Freq = 1 43 | v.Vol = 1.0 44 | v.Waveform = []float64{-1, 0, 1, 0} 45 | 46 | have := make([]float64, 8, 8) 47 | want := []float64{-1, -1, 0, 0, 1, 1, 0, 0} 48 | v.Fill(have, len(have)) 49 | 50 | if !reflect.DeepEqual(have, want) { 51 | t.Errorf("\n have: %v \n want: %v", have, want) 52 | } 53 | } 54 | 55 | func TestConvertSample(t *testing.T) { 56 | tests := []struct { 57 | from float64 58 | to int16 59 | }{ 60 | {-1, -math.MaxInt16}, 61 | {0, 0}, 62 | {1, math.MaxInt16}, 63 | } 64 | for _, test := range tests { 65 | t.Run(fmt.Sprintf("%v to %v", test.from, test.to), func(t *testing.T) { 66 | have := convert(test.from) 67 | want := test.to 68 | if have != want { 69 | t.Errorf("\n have: %v \n want: %v", have, want) 70 | } 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rcs/m6502/reader.go: -------------------------------------------------------------------------------- 1 | package m6502 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/blackchip-org/retro-cs/rcs" 8 | ) 9 | 10 | func Reader(e rcs.StmtEval) { 11 | e.Stmt.Addr = e.Ptr.Addr() 12 | opcode := e.Ptr.Fetch() 13 | e.Stmt.Bytes = append(e.Stmt.Bytes, opcode) 14 | op, ok := dasmTable[opcode] 15 | if !ok { 16 | e.Stmt.Op = fmt.Sprintf("?%02x", opcode) 17 | return 18 | } 19 | 20 | len := operandLengths[op.mode] 21 | operand := 0 22 | switch len { 23 | case 1: 24 | operand = int(e.Ptr.Fetch()) 25 | e.Stmt.Bytes = append(e.Stmt.Bytes, uint8(operand)) 26 | case 2: 27 | operand = e.Ptr.FetchLE() 28 | e.Stmt.Bytes = append(e.Stmt.Bytes, uint8(operand), uint8(operand>>8)) 29 | } 30 | e.Stmt.Op = op.inst + formatOp(op, operand, e.Stmt.Addr) 31 | return 32 | } 33 | 34 | func Formatter() rcs.CodeFormatter { 35 | options := rcs.FormatOptions{ 36 | BytesFormat: "%-8s", 37 | } 38 | return func(s rcs.Stmt) string { 39 | return rcs.FormatStmt(s, options) 40 | } 41 | } 42 | 43 | func formatOp(op op, operand int, addr int) string { 44 | format, ok := operandFormats[op.mode] 45 | result := "" 46 | if ok { 47 | // If this is a branch instruction, the value of the operand needs to be 48 | // added to the current addresss. Add two as it is relative after consuming 49 | // the instruction 50 | value := operand 51 | if op.mode == relative { 52 | value8 := int8(value) 53 | if value8 >= 0 { 54 | value = addr + int(value8) + 2 55 | } else { 56 | value = addr - int(value8*-1) + 2 57 | } 58 | } 59 | // If the format does not contain a formatting directive, just use as is. 60 | // For example: "asl a" 61 | if strings.Contains(format, "%") { 62 | result = " " + fmt.Sprintf(format, value) 63 | } else { 64 | result = " " + format 65 | } 66 | } 67 | return result 68 | } 69 | -------------------------------------------------------------------------------- /system/pacman/roms.go: -------------------------------------------------------------------------------- 1 | package pacman 2 | 3 | import "github.com/blackchip-org/retro-cs/rcs" 4 | 5 | var ROM = map[string][]rcs.ROM{ 6 | "pacman": []rcs.ROM{ 7 | rcs.NewROM("code ", "pacman.6e", "e87e059c5be45753f7e9f33dff851f16d6751181"), 8 | rcs.NewROM("code ", "pacman.6f", "674d3a7f00d8be5e38b1fdc208ebef5a92d38329"), 9 | rcs.NewROM("code ", "pacman.6h", "8e47e8c2c4d6117d174cdac150392042d3e0a881"), 10 | rcs.NewROM("code ", "pacman.6j", "d4a70d56bb01d27d094d73db8667ffb00ca69cb9"), 11 | rcs.NewROM("tiles ", "pacman.5e", "06ef227747a440831c9a3a613b76693d52a2f0a9"), 12 | rcs.NewROM("sprites ", "pacman.5f", "4a937ac02216ea8c96477d4a15522070507fb599"), 13 | rcs.NewROM("colors ", "82s123.7f", "8d0268dee78e47c712202b0ec4f1f51109b1f2a5"), 14 | rcs.NewROM("palettes ", "82s126.4a", "19097b5f60d1030f8b82d9f1d3a241f93e5c75d6"), 15 | rcs.NewROM("waveforms", "82s126.1m", "bbcec0570aeceb582ff8238a4bc8546a23430081"), 16 | rcs.NewROM("waveforms", "82s126.3m", "0c4d0bee858b97632411c440bea6948a74759746"), 17 | }, 18 | "mspacman": []rcs.ROM{ 19 | rcs.NewROM("code ", "boot1 ", "bc2247ec946b639dd1f00bfc603fa157d0baaa97"), 20 | rcs.NewROM("code ", "boot2 ", "13ea0c343de072508908be885e6a2a217bbb3047"), 21 | rcs.NewROM("code ", "boot3 ", "5ea4d907dbb2690698db72c4e0b5be4d3e9a7786"), 22 | rcs.NewROM("code ", "boot4 ", "3022a408118fa7420060e32a760aeef15b8a96cf"), 23 | rcs.NewROM("code2 ", "boot5 ", "fed6e9a2b210b07e7189a18574f6b8c4ec5bb49b"), 24 | rcs.NewROM("code2 ", "boot6 ", "387010a0c76319a1eab61b54c9bcb5c66c4b67a1"), 25 | rcs.NewROM("tiles ", "5e ", "5e8b472b615f12efca3fe792410c23619f067845"), 26 | rcs.NewROM("sprites ", "5f ", "fd6a1dde780b39aea76bf1c4befa5882573c2ef4"), 27 | rcs.NewROM("colors ", "82s123.7f", "8d0268dee78e47c712202b0ec4f1f51109b1f2a5"), 28 | rcs.NewROM("palettes ", "82s126.4a", "19097b5f60d1030f8b82d9f1d3a241f93e5c75d6"), 29 | rcs.NewROM("waveforms", "82s126.1m", "bbcec0570aeceb582ff8238a4bc8546a23430081"), 30 | rcs.NewROM("waveforms", "82s126.3m", "0c4d0bee858b97632411c440bea6948a74759746"), 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /system/pacman/audio.go: -------------------------------------------------------------------------------- 1 | package pacman 2 | 3 | import ( 4 | "github.com/blackchip-org/retro-cs/rcs" 5 | "github.com/veandco/go-sdl2/sdl" 6 | ) 7 | 8 | type audioData struct { 9 | waveforms []uint8 10 | } 11 | 12 | type voice struct { 13 | acc []uint8 14 | waveform uint8 15 | freq []uint8 16 | vol uint8 17 | } 18 | 19 | type audio struct { 20 | voices []voice 21 | waveforms [16][]float64 22 | synth *rcs.Synth 23 | } 24 | 25 | func newAudio(spec sdl.AudioSpec, data audioData) (*audio, error) { 26 | synth, err := rcs.NewSynth(spec, 3) 27 | if err != nil { 28 | return nil, err 29 | } 30 | a := &audio{ 31 | voices: make([]voice, 3, 3), 32 | synth: synth, 33 | } 34 | a.voices[0].acc = make([]uint8, 5, 5) 35 | a.voices[0].freq = make([]uint8, 5, 5) 36 | a.voices[1].acc = make([]uint8, 4, 4) 37 | a.voices[1].freq = make([]uint8, 4, 4) 38 | a.voices[2].acc = make([]uint8, 4, 4) 39 | a.voices[2].freq = make([]uint8, 4, 4) 40 | 41 | for i := 0; i < 16; i++ { 42 | addr := uint16(i * 32) 43 | a.waveforms[i] = rescale(data.waveforms, addr) 44 | } 45 | 46 | return a, nil 47 | } 48 | 49 | func (a *audio) queue() error { 50 | for i := 0; i < 3; i++ { 51 | v := a.voices[i] 52 | wf := rcs.SliceBits(v.waveform, 0, 2) 53 | 54 | // Voice 0 has 5 bytes but Voice 1 and 2 only have 4 bytes with 55 | // the missing lower byte being zero. 56 | nFreq := 4 57 | if i == 0 { 58 | nFreq = 5 59 | } 60 | a.synth.V[i].Freq = freq(v.freq, nFreq) 61 | a.synth.V[i].Vol = float64(v.vol&0xf) / 15 62 | a.synth.V[i].Waveform = a.waveforms[wf] 63 | } 64 | return a.synth.Queue() 65 | } 66 | 67 | func rescale(d []uint8, addr uint16) []float64 { 68 | out := make([]float64, 32, 32) 69 | for i := uint16(0); i < 32; i++ { 70 | v := d[addr+i] 71 | out[i] = (float64(v) - 7.5) / 8 72 | } 73 | return out 74 | } 75 | 76 | func freq(f []uint8, n int) int { 77 | val := uint32(0) 78 | shift := uint(0) 79 | if n == 4 { 80 | shift = 4 81 | } 82 | for i := 0; i < n; i++ { 83 | val += uint32(f[i]&0x0f) << shift 84 | shift += 4 85 | } 86 | freq := (375.0 / 4096.0) * float32(val) 87 | return int(freq) 88 | } 89 | -------------------------------------------------------------------------------- /tools/icmp/z80emu/icmp-z80emu.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "z80emu.h" 4 | #include "z80user.h" 5 | 6 | typedef struct ix { 7 | Z80_STATE state; 8 | unsigned char memory[1 << 16]; 9 | } ix; 10 | 11 | void cpd() { 12 | struct ix context; 13 | for (int a = 0; a <= 0xff; a++) { 14 | for (int _hl = 0; _hl <= 0xff; _hl++) { 15 | for (int f = 0; f <= 1; f++) { 16 | for (int c = 2; c <= 2; c++) { 17 | Z80Reset(&context.state); 18 | context.memory[0x1234] = _hl; 19 | context.state.registers.byte[Z80_B] = 0; 20 | context.state.registers.byte[Z80_C] = c; 21 | context.state.registers.byte[Z80_H] = 0x12; 22 | context.state.registers.byte[Z80_L] = 0x34; 23 | context.state.registers.byte[Z80_A] = a; 24 | context.state.registers.byte[Z80_F] = f; 25 | context.memory[0] = 0xed; 26 | context.memory[1] = 0xb9; 27 | Z80Emulate(&context.state, 0, &context); 28 | printf("a:%02x _hl:%02x c:%02x f:%02x => b:%02x c:%02x f:%02x\n", a, _hl, c, f, 29 | context.state.registers.byte[Z80_B], 30 | context.state.registers.byte[Z80_C], 31 | context.state.registers.byte[Z80_F] 32 | ); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | void af() { 40 | struct ix context; 41 | for (int f = 0; f <= 0xff; f++) { 42 | for (int a = 0; a <= 0xff; a++) { 43 | Z80Reset(&context.state); 44 | context.memory[0] = 0x17; 45 | context.state.registers.byte[Z80_A] = a; 46 | context.state.registers.byte[Z80_F] = f; 47 | Z80Emulate(&context.state, 0, &context); 48 | printf("a:%02x f:%02x => a:%02x f:%02x\n", a, f, 49 | context.state.registers.byte[Z80_A], 50 | context.state.registers.byte[Z80_F] 51 | ); 52 | } 53 | } 54 | } 55 | 56 | void SystemCall (ZEXTEST *zextest) {} 57 | 58 | int main() { 59 | af(); 60 | } 61 | -------------------------------------------------------------------------------- /system/c128/vdc.go: -------------------------------------------------------------------------------- 1 | package c128 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/blackchip-org/retro-cs/rcs" 7 | ) 8 | 9 | const ( 10 | VBlankFlag = (1 << 5) 11 | StatusFlag = (1 << 7) 12 | ) 13 | 14 | type VDC struct { 15 | Name string 16 | Addr uint8 17 | Status uint8 18 | MemPos uint16 19 | VSS uint8 // vertical smooth scrolling and control register 20 | 21 | WatchAddr rcs.FlagRW 22 | WatchStatus rcs.FlagRW 23 | WatchData rcs.FlagRW 24 | } 25 | 26 | func NewVDC() *VDC { 27 | return &VDC{ 28 | Name: "vdc", 29 | Status: VBlankFlag | StatusFlag, 30 | } 31 | } 32 | 33 | func (v *VDC) WriteAddr(val uint8) { 34 | if v.WatchAddr.W { 35 | log.Printf("%v:addr <= %v", v.Name, rcs.X8(val)) 36 | } 37 | v.Addr = val 38 | } 39 | 40 | func (v *VDC) ReadStatus() uint8 { 41 | if v.WatchAddr.R { 42 | log.Printf("%v <= %v:addr", rcs.X8(v.Status), v.Name) 43 | } 44 | return v.Status 45 | } 46 | 47 | func (v *VDC) ReadData() uint8 { 48 | val := uint8(0) 49 | switch v.Addr { 50 | case 0x12: // current memory address (high byte) 51 | val = uint8(v.MemPos >> 8) 52 | case 0x13: // current memory address (low byte) 53 | val = uint8(v.MemPos) 54 | case 0x18: // vertical smooth scrolling and control register 55 | val = v.VSS 56 | case 0x1f: // memory read/write register 57 | v.MemPos++ 58 | default: 59 | log.Printf("(!) %v: read to unhandled addr %v", v.Name, rcs.X8(v.Addr)) 60 | } 61 | if v.WatchData.R { 62 | log.Printf("%v <= %v[%v]", rcs.X8(val), v.Name, rcs.X8(v.Addr)) 63 | } 64 | return val 65 | } 66 | 67 | func (v *VDC) WriteData(val uint8) { 68 | switch v.Addr { 69 | case 0x12: // current memory address (high byte) 70 | v.MemPos = uint16(val)<<8 | v.MemPos&0xff 71 | case 0x13: // current memory address (low byte) 72 | v.MemPos = v.MemPos&0xff00 | uint16(val) 73 | case 0x18: // vertical smooth scrolling and control register 74 | v.VSS = val 75 | case 0x1e: // number of bytes to copy or fill 76 | v.blockOp(val) 77 | case 0x1f: // memory read/write register 78 | v.MemPos++ 79 | default: 80 | log.Printf("(!) %v: write to unhandled addr %v, value %v", v.Name, rcs.X8(v.Addr), rcs.X8(val)) 81 | } 82 | if v.WatchData.W { 83 | log.Printf("%v[%v] <= %v", v.Name, rcs.X8(v.Addr), rcs.X8(val)) 84 | } 85 | } 86 | 87 | func (v *VDC) blockOp(val uint8) { 88 | v.MemPos += uint16(val) 89 | } 90 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= 2 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 3 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 4 | github.com/chzyer/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI= 5 | github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= 6 | github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 7 | github.com/veandco/go-sdl2 v0.3.0 h1:IWYkHMp8V3v37NsKjszln8FFnX2+ab0538J371t+rss= 8 | github.com/veandco/go-sdl2 v0.3.0/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= 9 | github.com/veandco/go-sdl2 v0.3.3 h1:4/TirgB2MQ7oww3pM3Yfgf1YbChMlAQAmiCPe5koK0I= 10 | github.com/veandco/go-sdl2 v0.3.3/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= 11 | github.com/veandco/go-sdl2 v0.4.0-rc.0.0.20190924140640-68b56f2c64c3 h1:vByjQfx4idpwOq/nQaIyBAM67g4ezp0SkVn3oV9TEYI= 12 | github.com/veandco/go-sdl2 v0.4.0-rc.0.0.20190924140640-68b56f2c64c3/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= 13 | github.com/veandco/go-sdl2 v0.4.0-rc.1.0.20191208053522-3d9b5d6d1911 h1:hFAktcDWMt0NOB6hmAXztwREAlADZ4CjOyrBznupl20= 14 | github.com/veandco/go-sdl2 v0.4.0-rc.1.0.20191208053522-3d9b5d6d1911/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= 15 | github.com/veandco/go-sdl2 v0.4.24 h1:J+OCnPp0yfas4DAG13e3kIgC84mNxWGa3gpWYxrQQfI= 16 | github.com/veandco/go-sdl2 v0.4.24/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= 17 | golang.org/x/sys v0.0.0-20191118133127-cf1e2d577169 h1:LPLFLulk2vyM7yI3CwNW64O6e8AxBmr9opfv14yI7HI= 18 | golang.org/x/sys v0.0.0-20191118133127-cf1e2d577169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 19 | golang.org/x/sys v0.0.0-20191218084908-4a24b4065292 h1:Y8q0zsdcgAd+JU8VUA8p8Qv2YhuY9zevDG2ORt5qBUI= 20 | golang.org/x/sys v0.0.0-20191218084908-4a24b4065292/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 22 | golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= 23 | golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 24 | -------------------------------------------------------------------------------- /mock/cpu.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/blackchip-org/retro-cs/rcs" 7 | ) 8 | 9 | type CPU struct { 10 | mem *rcs.Memory 11 | pc uint16 12 | A uint8 // sample register 13 | B uint8 // sample register 14 | Q bool // sample flag 15 | Z bool // sample flag 16 | 17 | // Offset to be added to the program counter to get the address of the 18 | // next instruction. 19 | OffsetPC int 20 | } 21 | 22 | func NewCPU(mem *rcs.Memory) *CPU { 23 | return &CPU{mem: mem} 24 | } 25 | 26 | func (c *CPU) PC() int { 27 | return int(c.pc) 28 | } 29 | 30 | func (c *CPU) SetPC(addr int) { 31 | c.pc = uint16(addr) 32 | } 33 | 34 | func (c *CPU) Offset() int { 35 | return c.OffsetPC 36 | } 37 | 38 | func (c *CPU) Memory() *rcs.Memory { 39 | return c.mem 40 | } 41 | 42 | // Next reads the next byte at the program counter as the "opcode". The high 43 | // nibble is the number of "arguments" it will fetch (max two). 44 | func (c *CPU) Next() { 45 | if c.OffsetPC == 1 { 46 | c.pc++ 47 | } 48 | opcode := c.mem.Read(int(c.pc)) 49 | if c.OffsetPC == 0 { 50 | c.pc++ 51 | } 52 | narg := int(opcode) >> 4 53 | if narg > 2 { 54 | narg = 2 55 | } 56 | c.pc += uint16(narg) 57 | return 58 | } 59 | 60 | func (c *CPU) String() string { 61 | return fmt.Sprintf("pc:%04x a:%02x b:%02x q:%v z:%v", c.pc, c.A, c.B, c.Q, c.Z) 62 | } 63 | 64 | func reader(e rcs.StmtEval) { 65 | e.Stmt.Addr = e.Ptr.Addr() 66 | opcode := e.Ptr.Fetch() 67 | e.Stmt.Bytes = append(e.Stmt.Bytes, opcode) 68 | argN := opcode >> 4 69 | switch argN { 70 | case 0: 71 | e.Stmt.Op = fmt.Sprintf("i%02x", opcode) 72 | case 1: 73 | value := e.Ptr.Fetch() 74 | e.Stmt.Bytes = append(e.Stmt.Bytes, value) 75 | e.Stmt.Op = fmt.Sprintf("i%02x $%02x", opcode, value) 76 | case 2: 77 | value := e.Ptr.FetchLE() 78 | e.Stmt.Bytes = append(e.Stmt.Bytes, uint8(value&0xff)) 79 | e.Stmt.Bytes = append(e.Stmt.Bytes, uint8(value>>8)) 80 | e.Stmt.Op = fmt.Sprintf("i%02x $%04x", opcode, value) 81 | default: 82 | e.Stmt.Op = fmt.Sprintf("?%02x", opcode) 83 | } 84 | } 85 | 86 | func formatter() rcs.CodeFormatter { 87 | options := rcs.FormatOptions{ 88 | BytesFormat: "%-8s", 89 | } 90 | return func(s rcs.Stmt) string { 91 | return rcs.FormatStmt(s, options) 92 | } 93 | } 94 | 95 | func (c *CPU) NewDisassembler() *rcs.Disassembler { 96 | return rcs.NewDisassembler(c.mem, reader, formatter()) 97 | } 98 | -------------------------------------------------------------------------------- /rcs/namco/n06xx.go: -------------------------------------------------------------------------------- 1 | package namco 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/blackchip-org/retro-cs/rcs" 7 | ) 8 | 9 | type N06XX struct { 10 | DeviceR [4]rcs.Load8 11 | DeviceW [4]rcs.Store8 12 | 13 | ctrl uint8 14 | elapsed int 15 | timing bool 16 | NMI func() 17 | 18 | WatchDataW bool 19 | WatchDataR bool 20 | WatchCtrlW bool 21 | WatchCtrlR bool 22 | WatchNMI bool 23 | } 24 | 25 | func NewN06XX() *N06XX { 26 | n := &N06XX{} 27 | for i := 0; i < 4; i++ { 28 | j := i 29 | n.DeviceR[i] = func() uint8 { 30 | log.Printf("n06xx device %v not mapped for read", j) 31 | return 0 32 | } 33 | n.DeviceW[i] = func(uint8) { 34 | log.Printf("n06xx device %v not mapped for write", j) 35 | } 36 | } 37 | return n 38 | } 39 | 40 | func (n *N06XX) WriteData(addr int) rcs.Store8 { 41 | return func(v uint8) { 42 | if n.ctrl&0x10 != 0 { 43 | return 44 | } 45 | if n.WatchDataW { 46 | log.Printf("n06xx data write($%04x) => $%02x\n", addr, v) 47 | } 48 | dev := n.ctrl & 0x03 49 | switch dev { 50 | case 1 << 0: 51 | n.DeviceW[0](v) 52 | case 1 << 1: 53 | n.DeviceW[1](v) 54 | case 1 << 2: 55 | n.DeviceW[2](v) 56 | case 1 << 3: 57 | n.DeviceW[3](v) 58 | } 59 | } 60 | } 61 | 62 | func (n *N06XX) ReadData(addr int) rcs.Load8 { 63 | return func() uint8 { 64 | if n.ctrl&0x10 != 0 { 65 | return 0 66 | } 67 | dev := n.ctrl & 0x03 68 | v := uint8(0xff) 69 | switch dev { 70 | case 1 << 0: 71 | v = n.DeviceR[0]() 72 | case 1 << 1: 73 | v = n.DeviceR[1]() 74 | case 1 << 2: 75 | v = n.DeviceR[2]() 76 | case 1 << 3: 77 | v = n.DeviceR[3]() 78 | } 79 | if n.WatchDataR { 80 | log.Printf("n06xx data $%02x <= read($%04x)\n", v, addr) 81 | } 82 | return v 83 | } 84 | } 85 | 86 | func (n *N06XX) WriteCtrl(addr int) rcs.Store8 { 87 | return func(v uint8) { 88 | if n.WatchCtrlW { 89 | log.Printf("n06xx ctrl write($%04x) => $%02x\n", addr, v) 90 | } 91 | n.ctrl = v 92 | if v&0x0f == 0 { 93 | //n.timing = false 94 | } else { 95 | n.elapsed = 0 96 | n.timing = true 97 | } 98 | } 99 | } 100 | 101 | func (n *N06XX) ReadCtrl(addr int) rcs.Load8 { 102 | return func() uint8 { 103 | if n.WatchCtrlR { 104 | log.Printf("n06xx ctrl $%02x <= read(addr $%04x)\n", n.ctrl, addr) 105 | } 106 | return n.ctrl 107 | } 108 | } 109 | 110 | func (n *N06XX) Next() { 111 | if n.timing { 112 | n.elapsed++ 113 | if n.elapsed > 2000 { 114 | if n.WatchNMI { 115 | log.Println("n06xx NMI") 116 | } 117 | n.NMI() 118 | n.elapsed = 0 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /doc/c128.md: -------------------------------------------------------------------------------- 1 | # c128 2 | The Commodore 128. 3 | 4 | [![Commodore 128](img/c128-ready.thumb.png)](img/c128-ready.png) 5 | 6 | # Status 7 | 8 | - Boots up to READY. 9 | - No inputs yet so there is nothing to do. 10 | 11 | ## Run 12 | ``` 13 | retro-cs -s c128 14 | ``` 15 | 16 | ## Development Notes 17 | 18 | I thought that getting to a READY prompt would take about an hour. Implement the 128 style of banking, load the ROMs, and good to go, correct? The initialization routine is more sophisticated than the one found on the 64. 19 | 20 | There is supposed to be some magic with the MMU pre-configuration registers. Store a value in the PCR and when an arbitrary store happens to the corresponding load configuration register, the PCR value gets copied into the configuration register. Ignoring this for now gets to the READY prompt and I will have to visit this again. Trying to get this to work was causing problems. 21 | 22 | On the C64, setting the raster scan line to zero at $d012 was enough to get it to boot. On the C128, it actually looks for the 9th bit of the scan line to be set which is stored as the 7th bit in $d011. It then looks for $d012 to be greater than $8. Why that number? I have no clue. Stuffing these values before the machine starts seems to work for now. 23 | 24 | The emulator then hung trying to interact with the non-existent VDC chip. Since it was talking to the VDC, I assumed that it thought that the 40/80 column key was down in the 80 column position. I looked all over to see where that was being set but it turned out to not be the problem. During initialization, the 40 column *and* 80 column displays are cleared regardless of the button state. 25 | 26 | After implementing the minimum necessary in the VDC, the emulator booted to a monitor prompt and then filled the screen with "DSAVE". That command happens to be on one of the function keys so I suspected that it thought keyboard keys were being pressed. Filling in $dc01 with $ff gets past this for now and boots to READY. The CIA will need to be implemented this time instead of populating the keyboard buffer. 27 | 28 | ### Memory Map Points of Interest 29 | 30 | | Address | Description 31 | |-|-| 32 | | $00d7(7) | 0 = 40 column mode, 1 = 80 column mode 33 | | $d500 | MMU configuration register 34 | | $d505(7) | 40/80 key, 0 = 80 (down), 1 = 40 (up) 35 | | $d600 | VDC address/status register 36 | | $d601 | VDC data register 37 | | $dc01 | CIA data port B 38 | 39 | ## References 40 | - Cowper, Ottis R., "Mapping the Commodore 128", https://archive.org/details/Compute_s_Mapping_the_Commodore_128 41 | - Greenley, Larry, et al, "Commodore 128 Programmer's Reference Guide", https://archive.org/details/C128_Programmers_Reference_Guide_1986_Bamtam_Books 42 | 43 | 44 | -------------------------------------------------------------------------------- /rcs/m6502/cpu_test.go: -------------------------------------------------------------------------------- 1 | package m6502 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/blackchip-org/retro-cs/mock" 7 | ) 8 | 9 | func newTestCPU() *CPU { 10 | mock.ResetMemory() 11 | cpu := New(mock.TestMemory) 12 | cpu.SP = 0xff 13 | cpu.SetPC(0x1ff) 14 | return cpu 15 | } 16 | 17 | func testRunCPU(t *testing.T, cpu *CPU) error { 18 | cycles := 0 19 | run := true 20 | cpu.BreakFunc = func() { 21 | run = false 22 | } 23 | for run { 24 | cycles++ 25 | if cycles > 100 { 26 | t.Fatalf("max cycles exceeded") 27 | } 28 | cpu.Next() 29 | } 30 | return nil 31 | } 32 | 33 | func TestCPUString(t *testing.T) { 34 | 35 | var tests = []struct { 36 | setup func(*CPU) 37 | want string 38 | }{ 39 | {func(cpu *CPU) { cpu.SetPC(0x1234) }, 40 | "" + 41 | " pc sr ac xr yr sp n v - - d i z c\n" + 42 | "1234 20 00 00 00 ff . . * . . . . ."}, 43 | {func(cpu *CPU) { cpu.A = 0x56 }, 44 | "" + 45 | " pc sr ac xr yr sp n v - - d i z c\n" + 46 | "01ff 20 56 00 00 ff . . * . . . . ."}, 47 | {func(cpu *CPU) { cpu.X = 0x78 }, 48 | "" + 49 | " pc sr ac xr yr sp n v - - d i z c\n" + 50 | "01ff 20 00 78 00 ff . . * . . . . ."}, 51 | {func(cpu *CPU) { cpu.Y = 0x9a }, 52 | "" + 53 | " pc sr ac xr yr sp n v - - d i z c\n" + 54 | "01ff 20 00 00 9a ff . . * . . . . ."}, 55 | {func(cpu *CPU) { cpu.SP = 0xbc }, 56 | "" + 57 | " pc sr ac xr yr sp n v - - d i z c\n" + 58 | "01ff 20 00 00 00 bc . . * . . . . ."}, 59 | {func(cpu *CPU) { cpu.SR = FlagC }, 60 | "" + 61 | " pc sr ac xr yr sp n v - - d i z c\n" + 62 | "01ff 21 00 00 00 ff . . * . . . . *"}, 63 | {func(cpu *CPU) { cpu.SR = FlagZ }, 64 | "" + 65 | " pc sr ac xr yr sp n v - - d i z c\n" + 66 | "01ff 22 00 00 00 ff . . * . . . * ."}, 67 | {func(cpu *CPU) { cpu.SR = FlagI }, 68 | "" + 69 | " pc sr ac xr yr sp n v - - d i z c\n" + 70 | "01ff 24 00 00 00 ff . . * . . * . ."}, 71 | {func(cpu *CPU) { cpu.SR = FlagD }, 72 | "" + 73 | " pc sr ac xr yr sp n v - - d i z c\n" + 74 | "01ff 28 00 00 00 ff . . * . * . . ."}, 75 | {func(cpu *CPU) { cpu.SR = FlagB }, 76 | "" + 77 | " pc sr ac xr yr sp n v - - d i z c\n" + 78 | "01ff 30 00 00 00 ff . . * . . . . ."}, 79 | {func(cpu *CPU) { cpu.SR = FlagV }, 80 | "" + 81 | " pc sr ac xr yr sp n v - - d i z c\n" + 82 | "01ff 60 00 00 00 ff . * * . . . . ."}, 83 | {func(cpu *CPU) { cpu.SR = FlagN }, 84 | "" + 85 | " pc sr ac xr yr sp n v - - d i z c\n" + 86 | "01ff a0 00 00 00 ff * . * . . . . ."}, 87 | } 88 | 89 | for _, test := range tests { 90 | cpu := newTestCPU() 91 | test.setup(cpu) 92 | have := cpu.String() 93 | if test.want != have { 94 | t.Errorf("\n want: \n%v \n have: \n%v\n", test.want, have) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /rcs/rcs_test.go: -------------------------------------------------------------------------------- 1 | package rcs 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestFormats(t *testing.T) { 9 | tests := []struct { 10 | in int 11 | out string 12 | fn func(int) string 13 | }{ 14 | {15, "$f", func(v int) string { return X(v) }}, 15 | {15, "$0f", func(v int) string { return X8(uint8(v)) }}, 16 | {15, "$000f", func(v int) string { return X16(uint16(v)) }}, 17 | {15, "%1111", func(v int) string { return B(v) }}, 18 | {15, "%0000.1111", func(v int) string { return B8(uint8(v)) }}, 19 | {15, "%0000.0000:0000.1111", func(v int) string { return B16(uint16(v)) }}, 20 | } 21 | for i, test := range tests { 22 | name := fmt.Sprintf("%v", i) 23 | t.Run(name, func(t *testing.T) { 24 | have := test.fn(test.in) 25 | want := test.out 26 | if have != want { 27 | t.Errorf("\n have: %v \n want: %v\n", have, want) 28 | } 29 | }) 30 | } 31 | } 32 | 33 | func ExampleFromBCD() { 34 | v := FromBCD(0x42) 35 | fmt.Println(v) 36 | // Output: 42 37 | } 38 | 39 | func ExampleToBCD() { 40 | v := ToBCD(42) 41 | fmt.Printf("%02x", v) 42 | // Output: 42 43 | } 44 | 45 | func TestToBCDOverflow(t *testing.T) { 46 | want := uint8(0x12) 47 | have := ToBCD(112) 48 | if want != have { 49 | t.Errorf("\n want: %02x \n have: %02x\n", want, have) 50 | } 51 | } 52 | 53 | func TestSliceBits(t *testing.T) { 54 | b := ParseBits 55 | tests := []struct { 56 | lo int 57 | hi int 58 | in uint8 59 | out uint8 60 | name string 61 | }{ 62 | {6, 7, b("11000000"), b("011"), "high one"}, 63 | {6, 7, b("00111111"), b("000"), "high zero"}, 64 | {3, 5, b("00111000"), b("111"), "middle one"}, 65 | {3, 5, b("11000111"), b("000"), "middle zero"}, 66 | {0, 2, b("00000111"), b("111"), "low one"}, 67 | {0, 2, b("11111000"), b("000"), "low zero"}, 68 | } 69 | 70 | for _, test := range tests { 71 | t.Run(test.name, func(t *testing.T) { 72 | slice := SliceBits(test.in, test.lo, test.hi) 73 | if slice != test.out { 74 | t.Errorf("\n have: %08b \n want: %08b", slice, test.out) 75 | } 76 | }) 77 | } 78 | } 79 | 80 | func ExampleSliceBits() { 81 | value := ParseBits("00111000") 82 | fmt.Printf("%03b", SliceBits(value, 3, 5)) 83 | // Output: 111 84 | } 85 | 86 | func TestBitPlane(t *testing.T) { 87 | b := ParseBits 88 | tests := []struct { 89 | offset int 90 | in uint8 91 | out uint8 92 | }{ 93 | {0, b("00010001"), b("11")}, 94 | {1, b("00100010"), b("11")}, 95 | {2, b("01000100"), b("11")}, 96 | {3, b("10001000"), b("11")}, 97 | } 98 | for _, test := range tests { 99 | out := BitPlane4(test.in, test.offset) 100 | if out != test.out { 101 | t.Errorf("\n have: %08b \n want: %08b", out, test.out) 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /system/pacman/video.go: -------------------------------------------------------------------------------- 1 | package pacman 2 | 3 | import ( 4 | "github.com/blackchip-org/retro-cs/rcs" 5 | "github.com/blackchip-org/retro-cs/rcs/namco" 6 | "github.com/veandco/go-sdl2/sdl" 7 | ) 8 | 9 | func newVideo(r *sdl.Renderer, data namco.Data) (*namco.Video, error) { 10 | return namco.NewVideo(r, VideoConfig, data) 11 | } 12 | 13 | var VideoConfig = namco.Config{ 14 | TileLayout: namco.SheetLayout{ 15 | TileW: 8, 16 | TileH: 8, 17 | TextureW: 8 * 16, 18 | TextureH: 8 * 16, 19 | PixelLayout: tilePixels, 20 | PixelReader: pixelReader, 21 | BytesPerCell: 16, 22 | }, 23 | SpriteLayout: namco.SheetLayout{ 24 | TileW: 16, 25 | TileH: 16, 26 | TextureW: 16 * 8, 27 | TextureH: 16 * 8, 28 | PixelLayout: spritePixels, 29 | PixelReader: pixelReader, 30 | BytesPerCell: 64, 31 | }, 32 | PaletteEntries: 64, 33 | PaletteColors: 4, 34 | } 35 | 36 | var spritePixels = [][]int{ 37 | []int{191, 187, 183, 179, 175, 171, 167, 163, 63, 59, 55, 51, 47, 43, 39, 35}, 38 | []int{190, 186, 182, 178, 174, 170, 166, 162, 62, 58, 54, 50, 46, 42, 38, 34}, 39 | []int{189, 185, 181, 177, 173, 169, 165, 161, 61, 57, 53, 49, 45, 41, 37, 33}, 40 | []int{188, 184, 180, 176, 172, 168, 164, 160, 60, 56, 52, 48, 44, 40, 36, 32}, 41 | 42 | []int{223, 219, 215, 211, 207, 203, 199, 195, 95, 91, 87, 83, 79, 75, 71, 67}, 43 | []int{222, 218, 214, 210, 206, 202, 198, 194, 94, 90, 86, 82, 78, 74, 70, 66}, 44 | []int{221, 217, 213, 209, 205, 201, 197, 193, 93, 89, 85, 81, 77, 73, 69, 65}, 45 | []int{220, 216, 212, 208, 204, 200, 196, 192, 92, 88, 84, 80, 76, 72, 68, 64}, 46 | 47 | []int{255, 251, 247, 243, 239, 235, 231, 227, 127, 123, 119, 115, 111, 107, 103, 99}, 48 | []int{254, 250, 246, 242, 238, 234, 230, 226, 126, 122, 118, 114, 110, 106, 102, 98}, 49 | []int{253, 249, 245, 241, 237, 233, 229, 225, 125, 121, 117, 113, 109, 105, 101, 97}, 50 | []int{252, 248, 244, 240, 236, 232, 228, 224, 124, 120, 116, 112, 108, 104, 100, 96}, 51 | 52 | []int{159, 155, 151, 147, 143, 139, 135, 131, 31, 27, 23, 19, 15, 11, 7, 3}, 53 | []int{158, 154, 150, 146, 142, 138, 134, 130, 30, 26, 22, 18, 14, 10, 6, 2}, 54 | []int{157, 153, 149, 145, 141, 137, 133, 129, 29, 25, 21, 17, 13, 9, 5, 1}, 55 | []int{156, 152, 148, 144, 140, 136, 132, 128, 28, 24, 20, 16, 12, 8, 4, 0}, 56 | } 57 | 58 | var tilePixels = [][]int{ 59 | []int{63, 59, 55, 51, 47, 43, 39, 35}, 60 | []int{62, 58, 54, 50, 46, 42, 38, 34}, 61 | []int{61, 57, 53, 49, 45, 41, 37, 33}, 62 | []int{60, 56, 52, 48, 44, 40, 36, 32}, 63 | 64 | []int{31, 27, 23, 19, 15, 11, 7, 3}, 65 | []int{30, 26, 22, 18, 14, 10, 6, 2}, 66 | []int{29, 25, 21, 17, 13, 9, 5, 1}, 67 | []int{28, 24, 20, 16, 12, 8, 4, 0}, 68 | } 69 | 70 | func pixelReader(d []byte, base int, pixel int) uint8 { 71 | addr := base + int(pixel/4) 72 | offset := pixel % 4 73 | return rcs.BitPlane4(d[addr], offset) 74 | } 75 | -------------------------------------------------------------------------------- /system/galaga/video.go: -------------------------------------------------------------------------------- 1 | package galaga 2 | 3 | import ( 4 | "github.com/blackchip-org/retro-cs/rcs" 5 | "github.com/blackchip-org/retro-cs/rcs/namco" 6 | "github.com/veandco/go-sdl2/sdl" 7 | ) 8 | 9 | func newVideo(r *sdl.Renderer, data namco.Data) (*namco.Video, error) { 10 | return namco.NewVideo(r, VideoConfig, data) 11 | } 12 | 13 | var VideoConfig = namco.Config{ 14 | TileLayout: namco.SheetLayout{ 15 | TileW: 8, 16 | TileH: 8, 17 | TextureW: 8 * 16, 18 | TextureH: 8 * 8, 19 | PixelLayout: tilePixels, 20 | PixelReader: pixelReader, 21 | BytesPerCell: 16, 22 | }, 23 | SpriteLayout: namco.SheetLayout{ 24 | TileW: 16, 25 | TileH: 16, 26 | TextureW: 16 * 16, 27 | TextureH: 16 * 8, 28 | PixelLayout: spritePixels, 29 | PixelReader: pixelReader, 30 | BytesPerCell: 64, 31 | }, 32 | PaletteEntries: 32, 33 | PaletteColors: 8, 34 | Hack: true, 35 | } 36 | 37 | var tilePixels = [][]int{ 38 | []int{63, 59, 55, 51, 47, 43, 39, 35}, 39 | []int{62, 58, 54, 50, 46, 42, 38, 34}, 40 | []int{61, 57, 53, 49, 45, 41, 37, 33}, 41 | []int{60, 56, 52, 48, 44, 40, 36, 32}, 42 | 43 | []int{31, 27, 23, 19, 15, 11, 7, 3}, 44 | []int{30, 26, 22, 18, 14, 10, 6, 2}, 45 | []int{29, 25, 21, 17, 13, 9, 5, 1}, 46 | []int{28, 24, 20, 16, 12, 8, 4, 0}, 47 | } 48 | 49 | var spritePixels = [][]int{ 50 | []int{159, 155, 151, 147, 143, 139, 135, 131, 31, 27, 23, 19, 15, 11, 7, 3}, 51 | []int{158, 154, 150, 146, 142, 138, 134, 130, 30, 26, 22, 18, 14, 10, 6, 2}, 52 | []int{157, 153, 149, 145, 141, 137, 133, 129, 29, 25, 21, 17, 13, 9, 5, 1}, 53 | []int{156, 152, 148, 144, 140, 136, 132, 128, 28, 24, 20, 16, 12, 8, 4, 0}, 54 | 55 | []int{191, 187, 183, 179, 175, 171, 167, 163, 63, 59, 55, 51, 47, 43, 39, 35}, 56 | []int{190, 186, 182, 178, 174, 170, 166, 162, 62, 58, 54, 50, 46, 42, 38, 34}, 57 | []int{189, 185, 181, 177, 173, 169, 165, 161, 61, 57, 53, 49, 45, 41, 37, 33}, 58 | []int{188, 184, 180, 176, 172, 168, 164, 160, 60, 56, 52, 48, 44, 40, 36, 32}, 59 | 60 | []int{223, 219, 215, 211, 207, 203, 199, 195, 95, 91, 87, 83, 79, 75, 71, 67}, 61 | []int{222, 218, 214, 210, 206, 202, 198, 194, 94, 90, 86, 82, 78, 74, 70, 66}, 62 | []int{221, 217, 213, 209, 205, 201, 197, 193, 93, 89, 85, 81, 77, 73, 69, 65}, 63 | []int{220, 216, 212, 208, 204, 200, 196, 192, 92, 88, 84, 80, 76, 72, 68, 64}, 64 | 65 | []int{255, 251, 247, 243, 239, 235, 231, 227, 127, 123, 119, 115, 111, 107, 103, 99}, 66 | []int{254, 250, 246, 242, 238, 234, 230, 226, 126, 122, 118, 114, 110, 106, 102, 98}, 67 | []int{253, 249, 245, 241, 237, 233, 229, 225, 125, 121, 117, 113, 109, 105, 101, 97}, 68 | []int{252, 248, 244, 240, 236, 232, 228, 224, 124, 120, 116, 112, 108, 104, 100, 96}, 69 | } 70 | 71 | func pixelReader(d []byte, base int, pixel int) uint8 { 72 | addr := base + (pixel / 4) 73 | offset := pixel % 4 74 | return rcs.BitPlane4(d[addr], offset) 75 | } 76 | -------------------------------------------------------------------------------- /gen/cbm/petscii/petscii.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate go run . 4 | //go:generate go fmt ../../../rcs/cbm/petscii_table.go 5 | 6 | import ( 7 | "bufio" 8 | "bytes" 9 | "fmt" 10 | "io/ioutil" 11 | "log" 12 | "os" 13 | "path/filepath" 14 | "regexp" 15 | "strconv" 16 | "strings" 17 | 18 | "github.com/blackchip-org/retro-cs/config" 19 | ) 20 | 21 | var ( 22 | root = filepath.Join("..", "..", "..") 23 | targetDir = filepath.Join(root, "rcs", "cbm") 24 | sourceDir = filepath.Join(config.ResourceDir(), "ext", "petscii") 25 | ) 26 | 27 | func main() { 28 | log.SetFlags(0) 29 | infile := filepath.Join(sourceDir, "table.txt") 30 | in, err := os.Open(infile) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | defer in.Close() 35 | 36 | var table [2][0x100]rune 37 | 38 | s := bufio.NewScanner(in) 39 | s.Split(bufio.ScanLines) 40 | for i := 0; i < 6; i++ { 41 | s.Scan() 42 | } 43 | for s.Scan() { 44 | line := strings.TrimSpace(s.Text()) 45 | if line == "" { 46 | continue 47 | } 48 | if strings.Contains(line, "CHARACTER") { 49 | break 50 | } 51 | fields := strings.Split(line, "|") 52 | cp := strings.TrimSpace(fields[1]) 53 | charCode, err := strconv.ParseUint(cp[1:], 16, 8) 54 | if err != nil { 55 | log.Fatalln(err) 56 | } 57 | ucp := strings.TrimSpace(fields[2]) 58 | ucps := regexp.MustCompile("\\s+").Split(ucp, -1) 59 | if len(ucps) == 1 { 60 | ucps = append(ucps, ucps[0]) 61 | } 62 | 63 | for tableN, uni := range ucps { 64 | if uni == "-" { 65 | table[tableN][charCode] = 0xfffd 66 | } else { 67 | value, err := strconv.ParseUint(uni[2:], 16, 16) 68 | if err != nil { 69 | log.Fatalln(err) 70 | } 71 | table[tableN][int(charCode)] = rune(value) 72 | } 73 | } 74 | } 75 | if s.Err() != nil { 76 | log.Fatalln(s.Err()) 77 | } 78 | var out bytes.Buffer 79 | out.WriteString("package cbm\n") 80 | out.WriteString("var petsciiUnshifted = map[uint8]rune {\n") 81 | for i, val := range table[0] { 82 | ch := string(val) 83 | if val == '\'' { 84 | ch = "\\" + ch 85 | } 86 | if printable(i) { 87 | out.WriteString(fmt.Sprintf("0x%02x: '%s',\n", i, ch)) 88 | } 89 | } 90 | out.WriteString("}\n") 91 | out.WriteString("var petsciiShifted = map[uint8]rune {\n") 92 | for i, val := range table[1] { 93 | ch := string(val) 94 | if val == '\'' { 95 | ch = "\\" + ch 96 | } 97 | if printable(i) { 98 | out.WriteString(fmt.Sprintf("0x%02x: '%s',\n", i, ch)) 99 | } 100 | } 101 | out.WriteString("}\n") 102 | outfile := filepath.Join(targetDir, "petscii_table.go") 103 | err = ioutil.WriteFile(outfile, out.Bytes(), 0644) 104 | if err != nil { 105 | log.Fatalln(err) 106 | } 107 | } 108 | 109 | func printable(i int) bool { 110 | if i >= 0x20 && i <= 0x7f { 111 | return true 112 | } 113 | if i >= 0xa0 && i <= 0xbf { 114 | return true 115 | } 116 | return false 117 | } 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # retro-cs 2 | 3 | [![Build Status](https://travis-ci.com/blackchip-org/retro-cs.svg?branch=master)](https://travis-ci.com/blackchip-org/retro-cs) [![GoDoc](https://godoc.org/github.com/blackchip-org/retro-cs?status.svg)](https://godoc.org/github.com/blackchip-org/retro-cs) 4 | 5 | The Retro-Computing Systems. 6 | 7 | Inspired by the Vintage Computer Club. This project is no longer being actively 8 | worked on but that could always change. Feel free to contact me for more 9 | information. 10 | 11 | See my [VCF West 2022](https://vcfed.org/) presentation here: 12 | 13 | [![Adventures in Emulation](https://img.youtube.com/vi/VRFtXx4i8lo/0.jpg)](https://www.youtube.com/watch?v=VRFtXx4i8lo "Adventures In Emulation") 14 | 15 | 16 | See my [VCF East 2022](https://vcfed.org/) class here: 17 | 18 | [![Writing an Emulator](https://img.youtube.com/vi/kq0_PiOVN0E/0.jpg)](https://www.youtube.com/watch?v=kq0_PiOVN0E "Writing an Emulator") 19 | 20 | See my [MAGFest 2020](https://www.magfest.org/) presentation on this emulator here: 21 | 22 | [![Adventures in Writing an Emulator](https://img.youtube.com/vi/kO0rGXFjIA8/0.jpg)](https://www.youtube.com/watch?v=kO0rGXFjIA8 "Adventures in Writing an Emulator") 23 | 24 | 25 | Notes on the systems in progress: 26 | 27 | - [Commodore 64](doc/c64.md) 28 | - [Commodore 128](doc/c128.md) 29 | - [Pac-Man](doc/pacman.md) 30 | - and Ms. Pac-Man 31 | - [Galaga](doc/galaga.md) 32 | 33 | Development notes: 34 | 35 | - [Memory](doc/memory.md) 36 | - [MOS Technology 6502 series processor](doc/m6502.md) 37 | - [Pac-Man](https://github.com/blackchip-org/retro-cs/blob/master/doc/pacman.md#development-notes) 38 | - [Zilog Z80 processor](doc/z80.md) 39 | 40 | ## Requirements 41 | 42 | Go and SDL2 are needed to build the application. 43 | 44 | ### Linux 45 | 46 | Install SDL with: 47 | 48 | ```bash 49 | sudo apt-get install libsdl2{,-image,-mixer,-ttf,-gfx}-dev 50 | ``` 51 | 52 | Install go from here: 53 | 54 | https://golang.org/dl 55 | 56 | ### macOS 57 | 58 | Install go and SDL with: 59 | 60 | ```bash 61 | brew install go sdl2{,_image,_mixer,_ttf,_gfx} pkg-config 62 | ``` 63 | 64 | ### Windows 65 | 66 | It's never easy on Windows. Go needs to use mingw to compile the SDL bindings. Follow the instructions on the go-sdl2 page: 67 | 68 | https://github.com/veandco/go-sdl2#requirements 69 | 70 | Install go from here: 71 | 72 | https://golang.org/dl 73 | 74 | ### ROMs 75 | 76 | ROMs are not included in this repository. Follow the directions for each system to obtain the proper ROMs or ask for the resource pack. 77 | 78 | 79 | ## Installation 80 | 81 | ``` 82 | go get github.com/blackchip-org/retro-cs/... 83 | ``` 84 | 85 | ## Run 86 | 87 | ``` 88 | ~/go/bin/retro-cs -s 89 | ``` 90 | 91 | where `` is one of the following: 92 | 93 | - `c64` 94 | - `c128` 95 | - `galaga` 96 | - `mspacman` 97 | - `pacman` 98 | 99 | Use the `-m` flag to enable the [monitor](doc/monitor.md). 100 | 101 | Escape key to exit if in full screen mode. 102 | 103 | ## License 104 | 105 | MIT 106 | -------------------------------------------------------------------------------- /rcs/m6502/addressing.go: -------------------------------------------------------------------------------- 1 | package m6502 2 | 3 | func (c *CPU) loadAbsolute() uint8 { 4 | c.addrLoad = c.fetch2() 5 | return c.mem.Read(c.addrLoad) 6 | } 7 | 8 | func (c *CPU) loadAbsoluteX() uint8 { 9 | arg := c.fetch2() 10 | c.addrLoad = arg + int(c.X) 11 | if c.addrLoad&0xff00 != arg&0xff00 { 12 | c.pageCross = true 13 | } 14 | return c.mem.Read(c.addrLoad) 15 | } 16 | 17 | func (c *CPU) loadAbsoluteY() uint8 { 18 | arg := c.fetch2() 19 | c.addrLoad = arg + int(c.Y) 20 | if c.addrLoad&0xff00 != arg&0xff00 { 21 | c.pageCross = true 22 | } 23 | return c.mem.Read(c.addrLoad) 24 | } 25 | 26 | func (c *CPU) loadA() uint8 { 27 | return c.A 28 | } 29 | 30 | func (c *CPU) loadX() uint8 { 31 | return c.X 32 | } 33 | 34 | func (c *CPU) loadY() uint8 { 35 | return c.Y 36 | } 37 | 38 | func (c *CPU) loadSP() uint8 { 39 | return c.SP 40 | } 41 | 42 | func (c *CPU) loadSR() uint8 { 43 | return c.SR | (1 << 5) 44 | } 45 | 46 | func (c *CPU) loadImmediate() uint8 { 47 | return c.fetch() 48 | } 49 | 50 | func (c *CPU) loadIndirectX() uint8 { 51 | zpaddr := int(c.fetch() + c.X) 52 | c.addrLoad = c.mem.ReadLE(zpaddr) 53 | return c.mem.Read(c.addrLoad) 54 | } 55 | 56 | func (c *CPU) loadIndirectY() uint8 { 57 | c.addrLoad = c.mem.ReadLE(int(c.fetch())) 58 | indexed := c.addrLoad + int(c.Y) 59 | if c.addrLoad&0xff00 != indexed&0xff00 { 60 | c.pageCross = true 61 | } 62 | return c.mem.Read(indexed) 63 | } 64 | 65 | func (c *CPU) loadZeroPage() uint8 { 66 | c.addrLoad = int(c.fetch()) 67 | return c.mem.Read(c.addrLoad) 68 | } 69 | 70 | func (c *CPU) loadZeroPageX() uint8 { 71 | c.addrLoad = int(c.fetch() + c.X) 72 | return c.mem.Read(c.addrLoad) 73 | } 74 | 75 | func (c *CPU) loadZeroPageY() uint8 { 76 | c.addrLoad = int(c.fetch() + c.Y) 77 | return c.mem.Read(c.addrLoad) 78 | } 79 | 80 | func (c *CPU) storeAbsolute(value uint8) { 81 | c.mem.Write(c.fetch2(), value) 82 | } 83 | 84 | func (c *CPU) storeAbsoluteX(value uint8) { 85 | c.mem.Write(c.fetch2()+int(c.X), value) 86 | } 87 | 88 | func (c *CPU) storeAbsoluteY(v uint8) { 89 | c.mem.Write(c.fetch2()+int(c.Y), v) 90 | } 91 | 92 | func (c *CPU) storeIndirectX(v uint8) { 93 | zpaddr := int(c.fetch() + c.X) 94 | c.mem.Write(c.mem.ReadLE(zpaddr), v) 95 | } 96 | 97 | func (c *CPU) storeIndirectY(v uint8) { 98 | addr := c.mem.ReadLE(int(c.fetch())) + int(c.Y) 99 | c.mem.Write(addr, v) 100 | } 101 | 102 | func (c *CPU) storeZeroPage(v uint8) { 103 | c.mem.Write(int(c.fetch()), v) 104 | } 105 | 106 | func (c *CPU) storeZeroPageX(v uint8) { 107 | c.mem.Write(int(c.fetch()+c.X), v) 108 | } 109 | 110 | func (c *CPU) storeZeroPageY(v uint8) { 111 | c.mem.Write(int(c.fetch()+c.Y), v) 112 | } 113 | 114 | func (c *CPU) storeA(v uint8) { 115 | c.A = v 116 | } 117 | 118 | func (c *CPU) storeX(v uint8) { 119 | c.X = v 120 | } 121 | 122 | func (c *CPU) storeY(v uint8) { 123 | c.Y = v 124 | } 125 | 126 | func (c *CPU) storeSP(v uint8) { 127 | c.SP = v 128 | } 129 | 130 | func (c *CPU) storeSR(v uint8) { 131 | c.SR = v 132 | } 133 | 134 | func (c *CPU) storeBack(v uint8) { 135 | c.mem.Write(c.addrLoad, v) 136 | } 137 | -------------------------------------------------------------------------------- /rcs/audio.go: -------------------------------------------------------------------------------- 1 | package rcs 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | 7 | "github.com/veandco/go-sdl2/sdl" 8 | ) 9 | 10 | // FIXME: This need to be more specific names or moved out of this scope 11 | const ( 12 | SampleRate = 22050 13 | Channels = 2 14 | AudioFormat = sdl.AUDIO_U16LSB 15 | Buffer = 5 16 | ) 17 | 18 | type Voice struct { 19 | Freq int 20 | Vol float64 21 | Waveform []float64 22 | 23 | cycleFreq int 24 | cycleVol float64 25 | cycleWave []float64 26 | sampleRate int 27 | phase int 28 | period int 29 | } 30 | 31 | func NewVoice(sampleRate int32) *Voice { 32 | return &Voice{sampleRate: int(sampleRate)} 33 | } 34 | 35 | func (v *Voice) Fill(out []float64, n int) { 36 | for i := 0; i < n; i++ { 37 | if v.phase == 0 { 38 | v.cycleFreq = v.Freq 39 | v.cycleVol = v.Vol 40 | v.cycleWave = v.Waveform 41 | if v.cycleFreq > 0 { 42 | v.period = (v.sampleRate / v.cycleFreq) - 1 43 | } 44 | } 45 | if v.cycleFreq == 0 || v.cycleWave == nil { 46 | out[i] = 0 47 | } else { 48 | pct := float64(v.phase) / float64(v.period) 49 | pos := math.Round(pct * float64(len(v.cycleWave)-1)) 50 | out[i] = float64(v.cycleWave[int(pos)]) * v.cycleVol 51 | } 52 | v.phase++ 53 | if v.phase > v.period { 54 | v.phase = 0 55 | } 56 | } 57 | } 58 | 59 | type Synth struct { 60 | Spec sdl.AudioSpec 61 | V []*Voice 62 | 63 | samples [][]float64 64 | mixed []float64 65 | data []byte 66 | } 67 | 68 | func NewSynth(spec sdl.AudioSpec, voiceN int) (*Synth, error) { 69 | if spec.Format != sdl.AUDIO_S16LSB { 70 | return nil, fmt.Errorf("expecting format %x but got %x", sdl.AUDIO_U16LSB, spec.Format) 71 | } 72 | if spec.Channels != 2 { 73 | return nil, fmt.Errorf("expecting 2 channels but got %x", spec.Channels) 74 | } 75 | s := &Synth{} 76 | s.Spec = spec 77 | s.V = make([]*Voice, voiceN) 78 | samplesLen := s.Spec.Samples * Buffer 79 | s.samples = make([][]float64, voiceN, voiceN) 80 | for v := 0; v < voiceN; v++ { 81 | s.V[v] = NewVoice(s.Spec.Freq) 82 | s.samples[v] = make([]float64, samplesLen, samplesLen) 83 | } 84 | s.mixed = make([]float64, samplesLen) 85 | dataLen := 4 * int(samplesLen) 86 | s.data = make([]byte, dataLen, dataLen) 87 | return s, nil 88 | } 89 | 90 | func (s *Synth) Queue() error { 91 | q := sdl.GetQueuedAudioSize(1) / 4 92 | n := int(s.Spec.Samples*Buffer) - int(q) 93 | if n <= 0 { 94 | return nil 95 | } 96 | for i := 0; i < len(s.V); i++ { 97 | s.V[i].Fill(s.samples[i], n) 98 | } 99 | for i := 0; i < n; i++ { 100 | sample := float64(0) 101 | for j := 0; j < len(s.V); j++ { 102 | sample += s.samples[j][i] 103 | } 104 | s.mixed[i] = sample / float64(len(s.V)) 105 | } 106 | for i, d := 0, 0; i < n; i, d = i+1, d+4 { 107 | sample := convert(s.mixed[i]) 108 | s.data[d+0] = byte(sample & 0xff) 109 | s.data[d+1] = byte(sample >> 8) 110 | s.data[d+2] = byte(sample & 0xff) 111 | s.data[d+3] = byte(sample >> 8) 112 | } 113 | return sdl.QueueAudio(1, s.data[0:n*4]) 114 | } 115 | 116 | func convert(f float64) int16 { 117 | v := f * ((1 << 15) - 1) 118 | return int16(v) 119 | } 120 | -------------------------------------------------------------------------------- /system/pacman/inputs.go: -------------------------------------------------------------------------------- 1 | package pacman 2 | 3 | import ( 4 | "github.com/veandco/go-sdl2/sdl" 5 | ) 6 | 7 | type keyboard struct { 8 | s *system 9 | } 10 | 11 | func newKeyboard(s *system) *keyboard { 12 | return &keyboard{s: s} 13 | } 14 | 15 | func (k *keyboard) handle(e *sdl.KeyboardEvent) error { 16 | s := k.s 17 | if e.Type == sdl.KEYDOWN { 18 | switch e.Keysym.Sym { 19 | case sdl.K_1: 20 | s.in1 |= 1 << 5 21 | case sdl.K_2: 22 | s.in1 |= 1 << 6 23 | case sdl.K_c: 24 | s.in0 |= 1 << 5 25 | case sdl.K_r: 26 | s.in0 |= 1 << 4 27 | case sdl.K_UP: 28 | s.in0 &^= 1 << 0 29 | case sdl.K_LEFT: 30 | s.in0 &^= 1 << 1 31 | case sdl.K_RIGHT: 32 | s.in0 &^= 1 << 2 33 | case sdl.K_DOWN: 34 | s.in0 &^= 1 << 3 35 | } 36 | } else if e.Type == sdl.KEYUP { 37 | switch e.Keysym.Sym { 38 | case sdl.K_1: 39 | s.in1 &^= 1 << 5 40 | case sdl.K_2: 41 | s.in1 &^= 1 << 6 42 | case sdl.K_c: 43 | s.in0 &^= 1 << 5 44 | case sdl.K_r: 45 | s.in0 &^= 1 << 4 46 | case sdl.K_UP: 47 | s.in0 |= 1 << 0 48 | case sdl.K_LEFT: 49 | s.in0 |= 1 << 1 50 | case sdl.K_RIGHT: 51 | s.in0 |= 1 << 2 52 | case sdl.K_DOWN: 53 | s.in0 |= 1 << 3 54 | } 55 | } 56 | return nil 57 | } 58 | 59 | type joyPos int 60 | 61 | const ( 62 | joyNone joyPos = iota 63 | joyUp 64 | joyLeft 65 | joyRight 66 | joyDown 67 | ) 68 | 69 | type joystick struct { 70 | s *system 71 | pos joyPos 72 | } 73 | 74 | func newJoystick(s *system) *joystick { 75 | return &joystick{s: s} 76 | } 77 | 78 | func (j *joystick) buttonHandler(e *sdl.ControllerButtonEvent) error { 79 | s := j.s 80 | if e.Type == sdl.CONTROLLERBUTTONDOWN { 81 | switch e.Button { 82 | case sdl.CONTROLLER_BUTTON_BACK: 83 | s.in0 |= 1 << 5 84 | case sdl.CONTROLLER_BUTTON_START: 85 | s.in1 |= 1 << 5 86 | case sdl.CONTROLLER_BUTTON_DPAD_UP: 87 | if j.pos == joyNone { 88 | j.pos = joyUp 89 | s.in0 &^= 1 << 0 90 | } 91 | case sdl.CONTROLLER_BUTTON_DPAD_LEFT: 92 | if j.pos == joyNone { 93 | j.pos = joyLeft 94 | s.in0 &^= 1 << 1 95 | } 96 | case sdl.CONTROLLER_BUTTON_DPAD_RIGHT: 97 | if j.pos == joyNone { 98 | j.pos = joyRight 99 | s.in0 &^= 1 << 2 100 | } 101 | case sdl.CONTROLLER_BUTTON_DPAD_DOWN: 102 | if j.pos == joyNone { 103 | j.pos = joyDown 104 | s.in0 &^= 1 << 3 105 | } 106 | } 107 | } else if e.Type == sdl.CONTROLLERBUTTONUP { 108 | switch e.Button { 109 | case sdl.CONTROLLER_BUTTON_BACK: 110 | s.in0 &^= 1 << 5 111 | case sdl.CONTROLLER_BUTTON_START: 112 | s.in1 &^= 1 << 5 113 | case sdl.CONTROLLER_BUTTON_DPAD_UP: 114 | if j.pos == joyUp { 115 | j.pos = joyNone 116 | s.in0 |= 1 << 0 117 | } 118 | case sdl.CONTROLLER_BUTTON_DPAD_LEFT: 119 | if j.pos == joyLeft { 120 | j.pos = joyNone 121 | s.in0 |= 1 << 1 122 | } 123 | case sdl.CONTROLLER_BUTTON_DPAD_RIGHT: 124 | if j.pos == joyRight { 125 | j.pos = joyNone 126 | s.in0 |= 1 << 2 127 | } 128 | case sdl.CONTROLLER_BUTTON_DPAD_DOWN: 129 | if j.pos == joyDown { 130 | j.pos = joyNone 131 | s.in0 |= 1 << 3 132 | } 133 | } 134 | } 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /rcs/cpu.go: -------------------------------------------------------------------------------- 1 | package rcs 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Proc interface { 9 | Next() 10 | } 11 | 12 | // CPU is a central processing unit. 13 | // 14 | // The program counter is an integer to accomodate address busses of at 15 | // least 32-bit. The program counter stored within the actual struct 16 | // should proablby be the actual size of the bus. 17 | // 18 | // Offset is for CPUs, like the 6502, that increment the program counter 19 | // before fetching the instruction opcode. In this case, one should be 20 | // returned. If the program counter is incremented after the fetch, zero 21 | // should be returned. 22 | type CPU interface { 23 | Next() // Execute the next instruction 24 | PC() int // Address of the program counter 25 | SetPC(int) // Set the address of the program counter 26 | Offset() int // The next instruction is at PC() + Offset() 27 | Memory() *Memory // View of memory 28 | } 29 | 30 | // CPUDisassembler provides a disassembler instance for CPUs that 31 | // support this method. 32 | type CPUDisassembler interface { 33 | NewDisassembler() *Disassembler 34 | } 35 | 36 | // Stmt represents a single statement in a disassembly. 37 | type Stmt struct { 38 | Addr int // Address of the instruction 39 | Label string // Label for this address, "CHROUT" 40 | Op string // Formated operation, "lda #$40" 41 | Bytes []uint8 // Bytes that represent this instruction 42 | Comment string // Any notes from the source code 43 | } 44 | 45 | // CodeReader reads the next instruction using provided pointer and 46 | // fills in the fields found in the Stmt. 47 | type CodeReader func(StmtEval) 48 | 49 | // CodeFormat formats a statement in a string suitible for display to the 50 | // end user. 51 | type CodeFormatter func(Stmt) string 52 | 53 | type Disassembler struct { 54 | mem *Memory 55 | ptr *Pointer 56 | read CodeReader 57 | format CodeFormatter 58 | } 59 | 60 | type StmtEval struct { 61 | Ptr *Pointer 62 | Stmt *Stmt 63 | } 64 | 65 | func NewDisassembler(mem *Memory, r CodeReader, f CodeFormatter) *Disassembler { 66 | return &Disassembler{ 67 | mem: mem, 68 | ptr: NewPointer(mem), 69 | read: r, 70 | format: f, 71 | } 72 | } 73 | 74 | func (d *Disassembler) NextStmt() Stmt { 75 | eval := StmtEval{ 76 | Ptr: d.ptr, 77 | Stmt: &Stmt{ 78 | Bytes: make([]byte, 0, 0), 79 | }, 80 | } 81 | d.read(eval) 82 | return *eval.Stmt 83 | } 84 | 85 | func (d *Disassembler) Next() string { 86 | return d.format(d.NextStmt()) 87 | } 88 | 89 | func (d *Disassembler) SetPC(addr int) { 90 | d.ptr.SetAddr(addr) 91 | } 92 | 93 | func (d *Disassembler) PC() int { 94 | return d.ptr.Addr() 95 | } 96 | 97 | type FormatOptions struct { 98 | BytesFormat string 99 | } 100 | 101 | func FormatStmt(s Stmt, options FormatOptions) string { 102 | bytes := []string{} 103 | for _, b := range s.Bytes { 104 | bytes = append(bytes, fmt.Sprintf("%02x", b)) 105 | } 106 | format := options.BytesFormat 107 | if format == "" { 108 | format = "%v" 109 | } 110 | sbytes := fmt.Sprintf(format, strings.Join(bytes, " ")) 111 | return fmt.Sprintf("$%04x: %s %s", s.Addr, sbytes, s.Op) 112 | } 113 | -------------------------------------------------------------------------------- /system/c64/c64.go: -------------------------------------------------------------------------------- 1 | // Package c64 is the Commodore 64. 2 | package c64 3 | 4 | import ( 5 | "github.com/blackchip-org/retro-cs/config" 6 | "github.com/blackchip-org/retro-cs/rcs" 7 | "github.com/blackchip-org/retro-cs/rcs/cbm" 8 | "github.com/blackchip-org/retro-cs/rcs/cbm/petscii" 9 | "github.com/blackchip-org/retro-cs/rcs/m6502" 10 | ) 11 | 12 | type system struct { 13 | cpu *m6502.CPU 14 | mem *rcs.Memory 15 | screen rcs.Screen 16 | vic *cbm.VIC 17 | ram []uint8 18 | io []uint8 19 | bank uint8 20 | } 21 | 22 | func New(ctx rcs.SDLContext) (*rcs.Mach, error) { 23 | s := &system{} 24 | roms, err := rcs.LoadROMs(config.DataDir, SystemROM) 25 | if err != nil { 26 | return nil, err 27 | } 28 | s.ram = make([]uint8, 0x10000, 0x10000) 29 | s.io = make([]uint8, 0x1000, 0x1000) 30 | 31 | s.mem = newMemory(s.ram, s.io, roms) 32 | kb := newKeyboard() 33 | 34 | v, err := cbm.NewVIC(ctx.Renderer, s.mem, roms["chargen"]) 35 | if err != nil { 36 | return nil, err 37 | } 38 | s.vic = v 39 | s.screen = rcs.Screen{ 40 | W: v.W, 41 | H: v.H, 42 | Texture: v.Texture, 43 | ScanLineH: true, 44 | Draw: v.Draw, 45 | } 46 | 47 | for b := 0; b < 32; b++ { 48 | s.mem.SetBank(b) 49 | // setup IO port on the 6510, map address 1 to "PLA"s 50 | s.mem.MapLoad(0x01, s.ioPortLoad) 51 | s.mem.MapStore(0x01, s.ioPortStore) 52 | 53 | s.mem.MapRW(0xd020, &s.vic.BorderColor) 54 | s.mem.MapRW(0xd021, &s.vic.BgColor) 55 | 56 | s.mem.MapRW(0x0091, &kb.stkey) // stop key 57 | s.mem.MapRW(0x00c6, &kb.ndx) // buffer index 58 | s.mem.MapRAM(0x0277, kb.buf) 59 | 60 | s.mem.MapRW(0xdc00, &kb.joy2) 61 | } 62 | // Initialize to bank 31 63 | s.mem.SetBank(31) 64 | // GAME and EXROM on to start 65 | s.bank = 0x18 66 | // HIMEM, LOMEM, CHAREN on to start 67 | s.mem.Write(1, 0x7) 68 | 69 | // CPU should be created after memory is completely setup to obtain 70 | // the correct reset vector 71 | s.cpu = m6502.New(s.mem) 72 | 73 | mach := &rcs.Mach{ 74 | Sys: s, 75 | Comps: []rcs.Component{ 76 | rcs.NewComponent("c64", "c64", "", s), 77 | rcs.NewComponent("cpu", "m6502", "mem", s.cpu), 78 | rcs.NewComponent("mem", "mem", "", s.mem), 79 | }, 80 | CharDecoders: map[string]rcs.CharDecoder{ 81 | "petscii": petscii.Decoder, 82 | "petscii-shifted": petscii.ShiftedDecoder, 83 | "screen": cbm.ScreenDecoder, 84 | "screen-shifted": cbm.ScreenShiftedDecoder, 85 | }, 86 | DefaultEncoding: "petscii", 87 | Ctx: ctx, 88 | VBlankFunc: func() { 89 | s.cpu.IRQ = true 90 | }, 91 | Screen: s.screen, 92 | Keyboard: kb.handle, 93 | } 94 | 95 | return mach, nil 96 | } 97 | 98 | func (s *system) ioPortStore(v uint8) { 99 | // PLA information is in the bottom 3 bits 100 | s.bank &^= 0x7 101 | s.bank |= v & 0x7 102 | s.mem.SetBank(int(s.bank)) 103 | } 104 | 105 | func (s *system) ioPortLoad() uint8 { 106 | // Only return the bottom 3 bits for now 107 | return s.bank & 0x7 108 | } 109 | 110 | func (s *system) Save(enc *rcs.Encoder) { 111 | s.cpu.Save(enc) 112 | enc.Encode(s.ram) 113 | enc.Encode(s.io) 114 | } 115 | 116 | func (s *system) Load(dec *rcs.Decoder) { 117 | s.cpu.Load(dec) 118 | dec.Decode(&s.ram) 119 | dec.Decode(&s.io) 120 | } 121 | -------------------------------------------------------------------------------- /doc/m6502.md: -------------------------------------------------------------------------------- 1 | # m6502 2 | 3 | MOS Technology 6502 series processor. 4 | 5 | Compatible with: 6 | - 6502 7 | - 6510 8 | - 8502 9 | 10 | ## Development Notes 11 | 12 | ### Program Counter 13 | The program counter on the 6502 increments *before* fetching the opcode, not after. To run code starting at address $400, the PC needs to be set to $3ff. This kept confusing me when switching between this processor and the Z80 which does not have this behavior. The solution is to obtain the address of the next instruction using `cpu.PC() + cpu.Offset()`. On the Z80, Offset returns 0 but on the 6502 returns 1. 14 | 15 | ### BRK/PHP/PLP instructions 16 | All these years I thought there was a [break flag](https://wiki.nesdev.com/w/index.php/Status_flags#The_B_flag) on the processor but apparently that is not true. The only time this is "seen" is during a `brk` instruction. When the status register is pushed to the stack in this case, bit 4, the so-called break flag, is set. I implemented the `brk` instruction wrong at least twice. The correct implementation, after decoding the opcode, appears to be: 17 | 18 | * fetch another byte which is discarded 19 | * push the value of the PC + 1 to the stack 20 | * push the value of the status register with bit 5 set. 21 | * disable interrupts 22 | * set the PC to the value found at 0xfffe - 1 23 | 24 | When pushing the status register to the stack using the `php` instruction, ensure that both bits 4 and 5 are set. The `plp` instruction needs to make sure that bit 4, if set, is cleared when it is transfered to the status register. 25 | 26 | In this implementation the status register is a variable. In other implementaitons I made it a getter and setter function. To ensure bit 5 is always set and that bit 4 is always clear, those operations are applied after each instruction is executed. 27 | 28 | ```go 29 | c.push(c.SR | FlagB | Flag5) 30 | ``` 31 | 32 | ### TXS/TSX Instructions 33 | - `tsx` modifies the N and Z flags. 34 | - `txs` does not modify any flags. 35 | 36 | The reference I was using did not mention anything about the flags for these operations. 37 | 38 | ### Testing 39 | Unit tests were written when the 6502 emulator was developed to try and catch as many cases as possible but it has gaps in coverage. This provides a good quick first test to make sure the code is running as expected. 40 | 41 | For full coverage, the tests written by Klaus Dormann from the [6502_65C02_functional_tests repository](6502_65C02_functional_tests) are used. 42 | The assembly code for running these tests are not found in the repository. Download `bin_files/6502_functional_test.bin` and place it in a `~/rcs/ext/m6502` directory. Run the tests by using the build tag `ext`. 43 | 44 | ## References 45 | - Butterfield, Jim, "Machine Language for the Commodore 64, 128, and Other Commodore Computers. Revised and Expanded Edition", https://archive.org/details/Machine_Language_for_the_Commodore_Revised_and_Expanded_Edition 46 | - Clark, Bruce, "Decimal Mode", http://www.6502.org/tutorials/decimal_mode.html 47 | - Dormann, Klaus, "Tests for all valid opcodes of the 6502 and 65C02 processor", https://github.com/Klaus2m5/6502_65C02_functional_tests 48 | - Pickens, John, et al. "NMOS 6502 Opcodes", http://www.6502.org/tutorials/6502opcodes.html 49 | - "Status Flags", https://wiki.nesdev.com/w/index.php/Status_flags 50 | - Steil, Michael, "Internals of BRK/IRQ/NMI/RESET on a MOS 6502", https://www.pagetable.com/?p=410 51 | -------------------------------------------------------------------------------- /cmd/rcs-viewer/views.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | 6 | "github.com/blackchip-org/retro-cs/rcs" 7 | "github.com/blackchip-org/retro-cs/rcs/cbm" 8 | "github.com/blackchip-org/retro-cs/rcs/namco" 9 | "github.com/blackchip-org/retro-cs/system/c64" 10 | "github.com/blackchip-org/retro-cs/system/galaga" 11 | "github.com/blackchip-org/retro-cs/system/pacman" 12 | "github.com/veandco/go-sdl2/sdl" 13 | ) 14 | 15 | var views = map[string]view{ 16 | "c64:chars": view{ 17 | system: "c64", 18 | roms: c64.SystemROM, 19 | render: func(r *sdl.Renderer, d map[string][]byte) (rcs.TileSheet, error) { 20 | return cbm.CharGen(r, d["chargen"]) 21 | }, 22 | }, 23 | "c64:colors": view{ 24 | system: "c64", 25 | render: func(r *sdl.Renderer, _ map[string][]byte) (rcs.TileSheet, error) { 26 | palettes := [][]color.RGBA{cbm.Palette} 27 | return rcs.NewColorSheet(r, palettes) 28 | }, 29 | }, 30 | "galaga:sprites": view{ 31 | system: "galaga", 32 | roms: galaga.ROM["galaga"], 33 | render: func(r *sdl.Renderer, d map[string][]byte) (rcs.TileSheet, error) { 34 | return namco.NewTileSheet(r, d["sprites"], 35 | galaga.VideoConfig.SpriteLayout, namco.ViewerPalette) 36 | }, 37 | }, 38 | "galaga:tiles": view{ 39 | system: "galaga", 40 | roms: galaga.ROM["galaga"], 41 | render: func(r *sdl.Renderer, d map[string][]byte) (rcs.TileSheet, error) { 42 | return namco.NewTileSheet(r, d["tiles"], 43 | galaga.VideoConfig.TileLayout, namco.ViewerPalette) 44 | }, 45 | }, 46 | "mspacman:sprites": view{ 47 | system: "mspacman", 48 | roms: pacman.ROM["mspacman"], 49 | render: func(r *sdl.Renderer, d map[string][]byte) (rcs.TileSheet, error) { 50 | return namco.NewTileSheet(r, d["sprites"], 51 | pacman.VideoConfig.SpriteLayout, namco.ViewerPalette) 52 | }, 53 | }, 54 | "mspacman:tiles": view{ 55 | system: "mspacman", 56 | roms: pacman.ROM["mspacman"], 57 | render: func(r *sdl.Renderer, d map[string][]byte) (rcs.TileSheet, error) { 58 | return namco.NewTileSheet(r, d["tiles"], 59 | pacman.VideoConfig.TileLayout, namco.ViewerPalette) 60 | }, 61 | }, 62 | "pacman:colors": view{ 63 | system: "pacman", 64 | roms: pacman.ROM["pacman"], 65 | render: func(r *sdl.Renderer, d map[string][]byte) (rcs.TileSheet, error) { 66 | config := pacman.VideoConfig 67 | colors := namco.ColorTable(config, d["colors"]) 68 | return rcs.NewColorSheet(r, [][]color.RGBA{colors}) 69 | }, 70 | }, 71 | "pacman:palettes": view{ 72 | system: "pacman", 73 | roms: pacman.ROM["pacman"], 74 | render: func(r *sdl.Renderer, d map[string][]byte) (rcs.TileSheet, error) { 75 | config := pacman.VideoConfig 76 | colors := namco.ColorTable(config, d["colors"]) 77 | palettes := namco.PaletteTable(config, d["palettes"], colors) 78 | return rcs.NewColorSheet(r, palettes) 79 | }, 80 | }, 81 | "pacman:sprites": view{ 82 | system: "pacman", 83 | roms: pacman.ROM["pacman"], 84 | render: func(r *sdl.Renderer, d map[string][]byte) (rcs.TileSheet, error) { 85 | return namco.NewTileSheet(r, d["sprites"], 86 | pacman.VideoConfig.SpriteLayout, namco.ViewerPalette) 87 | }, 88 | }, 89 | "pacman:tiles": view{ 90 | system: "pacman", 91 | roms: pacman.ROM["pacman"], 92 | render: func(r *sdl.Renderer, d map[string][]byte) (rcs.TileSheet, error) { 93 | return namco.NewTileSheet(r, d["tiles"], 94 | pacman.VideoConfig.TileLayout, namco.ViewerPalette) 95 | }, 96 | }, 97 | } 98 | -------------------------------------------------------------------------------- /system/c64/memory.go: -------------------------------------------------------------------------------- 1 | package c64 2 | 3 | import ( 4 | "github.com/blackchip-org/retro-cs/rcs" 5 | ) 6 | 7 | func newMemory(ram []uint8, io []uint8, roms map[string][]byte) *rcs.Memory { 8 | basic := roms["basic"] 9 | kernal := roms["kernal"] 10 | chargen := roms["chargen"] 11 | 12 | iomem := rcs.NewMemory(1, 0x1000) 13 | iomem.MapRAM(0, io) 14 | 15 | var cartlo, carthi []uint8 16 | cart, ok := roms["cart"] 17 | if ok { 18 | cartlo = cart[0x0000:0x2000] 19 | carthi = cart[0x2000:0x4000] 20 | } 21 | 22 | mem := rcs.NewMemory(32, 0x10000) 23 | 24 | // https://www.c64-wiki.com/wiki/Bank_Switching 25 | mem.SetBank(31) 26 | mem.MapRAM(0x0000, ram) 27 | mem.MapROM(0xa000, basic) 28 | mem.Map(0xd000, iomem) 29 | mem.MapROM(0xe000, kernal) 30 | 31 | for _, bank := range []int{30, 14} { 32 | mem.SetBank(bank) 33 | mem.MapRAM(0x0000, ram) 34 | mem.Map(0xd000, iomem) 35 | mem.MapROM(0xe000, kernal) 36 | } 37 | 38 | for _, bank := range []int{29, 13} { 39 | mem.SetBank(bank) 40 | mem.MapRAM(0x0000, ram) 41 | mem.Map(0xd000, iomem) 42 | } 43 | 44 | for _, bank := range []int{28, 24} { 45 | mem.SetBank(bank) 46 | mem.MapRAM(0x0000, ram) 47 | } 48 | 49 | mem.SetBank(27) 50 | mem.MapRAM(0x0000, ram) 51 | mem.MapROM(0xa000, basic) 52 | mem.MapROM(0xd000, chargen) 53 | mem.MapROM(0xe000, kernal) 54 | 55 | for _, bank := range []int{26, 10} { 56 | mem.SetBank(bank) 57 | mem.MapRAM(0x0000, ram) 58 | mem.MapROM(0xd000, chargen) 59 | mem.MapROM(0xe000, kernal) 60 | } 61 | 62 | for _, bank := range []int{25, 9} { 63 | mem.SetBank(bank) 64 | mem.MapRAM(0x0000, ram) 65 | mem.MapROM(0xd000, chargen) 66 | } 67 | 68 | for bank := 23; bank >= 16; bank-- { 69 | mem.SetBank(bank) 70 | mem.MapRAM(0x0000, ram) 71 | for addr := 0x1000; addr <= 0x7fff; addr++ { 72 | mem.Unmap(addr) 73 | } 74 | mem.MapROM(0x8000, cartlo) 75 | for addr := 0xa000; addr <= 0xcfff; addr++ { 76 | mem.Unmap(addr) 77 | } 78 | mem.Map(0xd000, iomem) 79 | mem.MapROM(0xe000, carthi) 80 | } 81 | 82 | mem.SetBank(15) 83 | mem.MapRAM(0x0000, ram) 84 | mem.MapROM(0x8000, cartlo) 85 | mem.MapROM(0xa000, basic) 86 | mem.Map(0xd000, iomem) 87 | mem.MapROM(0xe000, kernal) 88 | 89 | for _, bank := range []int{12, 8, 4, 0} { 90 | mem.SetBank(bank) 91 | mem.MapRAM(0x0000, ram) 92 | } 93 | 94 | mem.SetBank(11) 95 | mem.MapRAM(0x0000, ram) 96 | mem.MapROM(0x8000, cartlo) 97 | mem.MapROM(0xa000, basic) 98 | mem.MapROM(0xd000, chargen) 99 | mem.MapROM(0xe000, kernal) 100 | 101 | mem.SetBank(7) 102 | mem.MapRAM(0x0000, ram) 103 | mem.MapROM(0x8000, cartlo) 104 | mem.MapROM(0xa000, carthi) 105 | mem.Map(0xd000, iomem) 106 | mem.MapROM(0xe000, kernal) 107 | 108 | mem.SetBank(6) 109 | mem.MapRAM(0x0000, ram) 110 | mem.MapROM(0xa000, carthi) 111 | mem.Map(0xd000, iomem) 112 | mem.MapROM(0xe000, kernal) 113 | 114 | mem.SetBank(5) 115 | mem.MapRAM(0x0000, ram) 116 | mem.Map(0xd000, iomem) 117 | 118 | mem.SetBank(3) 119 | mem.MapRAM(0x0000, ram) 120 | mem.MapROM(0x8000, cartlo) 121 | mem.MapROM(0xa000, carthi) 122 | mem.MapROM(0xd000, chargen) 123 | mem.MapROM(0xe000, kernal) 124 | 125 | mem.SetBank(2) 126 | mem.MapRAM(0x0000, ram) 127 | mem.MapROM(0xa000, carthi) 128 | mem.MapROM(0xd000, chargen) 129 | mem.MapROM(0xe000, kernal) 130 | 131 | mem.SetBank(1) 132 | mem.MapRAM(0x0000, ram) 133 | 134 | return mem 135 | } 136 | -------------------------------------------------------------------------------- /rcs/video.go: -------------------------------------------------------------------------------- 1 | package rcs 2 | 3 | import ( 4 | "fmt" 5 | "image/color" 6 | "math" 7 | 8 | "github.com/veandco/go-sdl2/sdl" 9 | ) 10 | 11 | type TileSheet struct { 12 | TextureW int32 13 | TextureH int32 14 | TileW int32 15 | TileH int32 16 | Texture *sdl.Texture 17 | } 18 | 19 | type Screen struct { 20 | W int32 21 | H int32 22 | X int32 23 | Y int32 24 | Scale int32 25 | Texture *sdl.Texture 26 | ScanLineH bool 27 | ScanLineV bool 28 | Draw func(*sdl.Renderer) error 29 | } 30 | 31 | func NewColorSheet(r *sdl.Renderer, palettes [][]color.RGBA) (TileSheet, error) { 32 | tileW := int32(32) 33 | tileH := int32(32) 34 | 35 | colorN := len(palettes) * len(palettes[0]) 36 | per := int32(math.Sqrt(float64(colorN))) 37 | 38 | texW := per * tileW 39 | texH := per * tileH 40 | 41 | t, err := r.CreateTexture(sdl.PIXELFORMAT_RGBA8888, 42 | sdl.TEXTUREACCESS_TARGET, texW, texH) 43 | if err != nil { 44 | return TileSheet{}, fmt.Errorf("unable to create sheet: %v", err) 45 | } 46 | r.SetRenderTarget(t) 47 | 48 | x := int32(0) 49 | y := int32(0) 50 | for _, pal := range palettes { 51 | for _, c := range pal { 52 | r.SetDrawColor(c.R, c.G, c.B, c.A) 53 | r.FillRect(&sdl.Rect{ 54 | X: x, 55 | Y: y, 56 | W: tileW, 57 | H: tileH, 58 | }) 59 | x += tileW 60 | if x >= texW { 61 | x = 0 62 | y += tileH 63 | } 64 | } 65 | } 66 | r.SetRenderTarget(nil) 67 | return TileSheet{ 68 | TextureW: texW, 69 | TextureH: texH, 70 | TileW: tileW, 71 | TileH: tileH, 72 | Texture: t, 73 | }, nil 74 | } 75 | 76 | func NewScanLinesV(r *sdl.Renderer, w int32, h int32, size int32) (*sdl.Texture, error) { 77 | tex, err := r.CreateTexture(sdl.PIXELFORMAT_RGBA8888, 78 | sdl.TEXTUREACCESS_TARGET, w, h) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | pixels := make([]uint32, w*h, w*h) 84 | for y := int32(0); y < h; y++ { 85 | for x := int32(0); x < w; x += 2 * size { 86 | ptr := (y * w) + x 87 | for i := int32(0); i < size; i++ { 88 | pixels[ptr] = 0x00000000 89 | ptr++ 90 | } 91 | for i := int32(size); i < size*2; i++ { 92 | pixels[ptr] = 0x00000020 93 | ptr++ 94 | } 95 | } 96 | } 97 | tex.SetBlendMode(sdl.BLENDMODE_BLEND) 98 | tex.UpdateRGBA(nil, pixels, int(w)) 99 | return tex, nil 100 | } 101 | 102 | func NewScanLinesH(r *sdl.Renderer, w int32, h int32, size int32) (*sdl.Texture, error) { 103 | tex, err := r.CreateTexture(sdl.PIXELFORMAT_RGBA8888, 104 | sdl.TEXTUREACCESS_TARGET, w, h) 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | pixels := make([]uint32, w*h, w*h) 110 | for x := int32(0); x < w; x++ { 111 | for y := int32(0); y < h; y += 2 * size { 112 | ptr := (y * w) + x 113 | for i := int32(0); i < size; i++ { 114 | pixels[ptr] = 0x00000000 115 | ptr += w 116 | } 117 | for i := int32(size); i < size*2; i++ { 118 | pixels[ptr] = 0x00000020 119 | ptr += w 120 | } 121 | } 122 | } 123 | tex.SetBlendMode(sdl.BLENDMODE_BLEND) 124 | tex.UpdateRGBA(nil, pixels, int(w)) 125 | return tex, nil 126 | } 127 | 128 | func FitInWindow(winW int32, winH int32, screen *Screen) { 129 | deltaW, deltaH := winW-screen.W, winH-screen.H 130 | scale := int32(1) 131 | if deltaW < deltaH { 132 | scale = winW / screen.W 133 | } else { 134 | scale = winH / screen.H 135 | } 136 | scaledW, scaledH := screen.W*scale, screen.H*scale 137 | screen.X = (winW - scaledW) / 2 138 | screen.Y = (winH - scaledH) / 2 139 | screen.Scale = scale 140 | } 141 | -------------------------------------------------------------------------------- /rcs/z80/dasm_test.go: -------------------------------------------------------------------------------- 1 | package z80 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/blackchip-org/retro-cs/mock" 8 | "github.com/blackchip-org/retro-cs/rcs" 9 | ) 10 | 11 | type harstonTest struct { 12 | Name string 13 | Op string 14 | Bytes []uint8 15 | } 16 | 17 | func TestDasm(t *testing.T) { 18 | for _, test := range harstonTests { 19 | t.Run(test.Name, func(t *testing.T) { 20 | mock.ResetMemory() 21 | ptr := rcs.NewPointer(mock.TestMemory) 22 | dasm := NewDisassembler(mock.TestMemory) 23 | dasm.SetPC(0x10) 24 | ptr.SetAddr(0x10) 25 | ptr.PutN(test.Bytes...) 26 | ptr.SetAddr(0x10) 27 | s := dasm.NextStmt() 28 | if s.Op != test.Op { 29 | t.Errorf("\n have: %v \n want: %v", s.Op, test.Op) 30 | } 31 | }) 32 | } 33 | } 34 | 35 | func TestInvalid(t *testing.T) { 36 | var tests = []struct { 37 | name string 38 | prefix []uint8 39 | }{ 40 | {"dd", []uint8{0xdd}}, 41 | {"ed", []uint8{0xed}}, 42 | {"fd", []uint8{0xfd}}, 43 | {"ddcb", []uint8{0xdd, 0xcb}}, 44 | {"fdcb", []uint8{0xfd, 0xcb}}, 45 | } 46 | 47 | for _, test := range tests { 48 | for opcode := 0; opcode < 0x100; opcode++ { 49 | if test.name == "dd" || test.name == "fd" { 50 | switch opcode { 51 | case 0xdd, 0xed, 0xfd, 0xcb: 52 | continue 53 | } 54 | } 55 | mock.ResetMemory() 56 | ptr := rcs.NewPointer(mock.TestMemory) 57 | ptr.Put(test.prefix[0]) 58 | if len(test.prefix) > 1 { 59 | ptr.Put(test.prefix[1]) 60 | ptr.Put(0) // displacement byte 61 | } 62 | ptr.Put(uint8(opcode)) 63 | dasm := NewDisassembler(mock.TestMemory) 64 | s := dasm.NextStmt() 65 | if s.Op[0] == '?' { 66 | name := fmt.Sprintf("%v%02x", test.name, opcode) 67 | t.Run(name, func(t *testing.T) { 68 | want := fmt.Sprintf("?%s%02x", test.name, opcode) 69 | if s.Op != want { 70 | t.Errorf("\n have: %v \n want: %v", s.Op, want) 71 | } 72 | }) 73 | } 74 | } 75 | } 76 | } 77 | 78 | func TestInvalidDD(t *testing.T) { 79 | for i := 0; i <= 0xff; i++ { 80 | name := fmt.Sprintf("%02x", i) 81 | t.Run(name, func(t *testing.T) { 82 | mock.ResetMemory() 83 | mem := mock.TestMemory 84 | mem.Write(0, 0xdd) 85 | mem.Write(1, uint8(i)) 86 | dasm := NewDisassembler(mem) 87 | s := dasm.NextStmt() 88 | if s.Op[0] == '?' && i != 0xdd && i != 0xed && i != 0xfd && i != 0xcb { 89 | want := fmt.Sprintf("?dd%02x", i) 90 | if s.Op != want { 91 | t.Errorf("\n have: %v \n want: %v", s.Op, want) 92 | } 93 | } 94 | }) 95 | } 96 | } 97 | 98 | func TestInvalidFD(t *testing.T) { 99 | for i := 0; i <= 0xff; i++ { 100 | name := fmt.Sprintf("%02x", i) 101 | t.Run(name, func(t *testing.T) { 102 | mock.ResetMemory() 103 | mem := mock.TestMemory 104 | mem.Write(0, 0xfd) 105 | mem.Write(1, uint8(i)) 106 | dasm := NewDisassembler(mem) 107 | s := dasm.NextStmt() 108 | if s.Op[0] == '?' && i != 0xdd && i != 0xed && i != 0xfd && i != 0xcb { 109 | want := fmt.Sprintf("?fd%02x", i) 110 | if s.Op != want { 111 | t.Errorf("\n have: %v \n want: %v", s.Op, want) 112 | } 113 | } 114 | }) 115 | } 116 | } 117 | 118 | func TestInvalidFDCB(t *testing.T) { 119 | for i := 0; i <= 0xff; i++ { 120 | name := fmt.Sprintf("%02x", i) 121 | t.Run(name, func(t *testing.T) { 122 | mock.ResetMemory() 123 | mem := mock.TestMemory 124 | mem.Write(0, 0xfd) 125 | mem.Write(1, 0xcb) 126 | mem.Write(2, 0) 127 | mem.Write(3, uint8(i)) 128 | dasm := NewDisassembler(mem) 129 | s := dasm.NextStmt() 130 | if s.Op[0] == '?' { 131 | want := fmt.Sprintf("?fdcb%02x", i) 132 | if s.Op != want { 133 | t.Errorf("\n have: %v \n want: %v", s.Op, want) 134 | } 135 | } 136 | }) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /rcs/z80/instructions_test.go: -------------------------------------------------------------------------------- 1 | package z80 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/blackchip-org/retro-cs/mock" 10 | "github.com/blackchip-org/retro-cs/rcs" 11 | ) 12 | 13 | // Set a test name here to test a single test 14 | var testSingle = "" 15 | 16 | // TODO: Write single tests for: 17 | // ADC/SBC: Check that both bytes are zero for zero flag when doing 16-bits 18 | 19 | func TestOps(t *testing.T) { 20 | for _, test := range fuseIn { 21 | if testSingle != "" && test.name != testSingle { 22 | continue 23 | } 24 | t.Run(test.name, func(t *testing.T) { 25 | log.SetOutput(&mock.PanicWriter{}) 26 | defer func() { 27 | log.SetOutput(os.Stderr) 28 | if r := recover(); r != nil { 29 | msg := r.(string) 30 | if strings.HasSuffix(msg, ": dd00\n") { 31 | return 32 | } 33 | if strings.HasSuffix(msg, ": ddfd\n") { 34 | return 35 | } 36 | t.Errorf("unexpected panic: %v", r) 37 | } 38 | }() 39 | cpu := load(test) 40 | i := 0 41 | setupPorts(cpu, fuseExpected[test.name]) 42 | for { 43 | cpu.Next() 44 | if test.name == "dd00" { 45 | if cpu.PC() == 0x0003 { 46 | break 47 | } 48 | } else if test.name == "ddfd00" { 49 | if cpu.PC() == 0x0004 { 50 | break 51 | } 52 | } else { 53 | if cpu.mem.Read(cpu.PC()) == 0 && cpu.PC() != 0 { 54 | break 55 | } 56 | if test.tstates == 1 { 57 | break 58 | } 59 | } 60 | if i > 100 { 61 | t.Fatalf("exceeded execution limit") 62 | } 63 | i++ 64 | } 65 | expected := load(fuseExpected[test.name]) 66 | 67 | if cpu.String() != expected.String() { 68 | t.Errorf("\n have: \n%v \n want: \n%v", cpu.String(), expected.String()) 69 | } 70 | testMemory(t, cpu.mem, fuseExpected[test.name].memory) 71 | testMemory(t, cpu.Ports, fuseExpected[test.name].portWrites) 72 | testHalt(t, cpu, fuseExpected[test.name]) 73 | }) 74 | } 75 | } 76 | 77 | func testMemory(t *testing.T, mem *rcs.Memory, expected [][]int) { 78 | for _, av := range expected { 79 | addr := av[0] 80 | value := uint8(av[1]) 81 | have := mem.Read(addr) 82 | if have != value { 83 | t.Errorf("\n addr %04x have: %02x \n addr %04x want: %02x", addr, have, addr, value) 84 | } 85 | } 86 | } 87 | 88 | func testHalt(t *testing.T, cpu *CPU, expected fuseTest) { 89 | if cpu.Halt != (expected.halt != 0) { 90 | t.Errorf("\n want: halt(%v) \n have: halt(%v)", cpu.Halt, expected.halt) 91 | } 92 | } 93 | 94 | func setupPorts(cpu *CPU, expected fuseTest) { 95 | for _, avs := range expected.portReads { 96 | addr := avs[0] 97 | values := avs[1:] 98 | cpu.Ports.MapLoad(addr, mock.MockRead(values)) 99 | } 100 | } 101 | 102 | func load(test fuseTest) *CPU { 103 | mock.ResetMemory() 104 | cpu := New(mock.TestMemory) 105 | 106 | cpu.A, cpu.F = uint8(test.af>>8), uint8(test.af) 107 | cpu.B, cpu.C = uint8(test.bc>>8), uint8(test.bc) 108 | cpu.D, cpu.E = uint8(test.de>>8), uint8(test.de) 109 | cpu.H, cpu.L = uint8(test.hl>>8), uint8(test.hl) 110 | 111 | cpu.A1, cpu.F1 = uint8(test.af1>>8), uint8(test.af1) 112 | cpu.B1, cpu.C1 = uint8(test.bc1>>8), uint8(test.bc1) 113 | cpu.D1, cpu.E1 = uint8(test.de1>>8), uint8(test.de1) 114 | cpu.H1, cpu.L1 = uint8(test.hl1>>8), uint8(test.hl1) 115 | 116 | cpu.IXH, cpu.IXL = uint8(test.ix>>8), uint8(test.ix) 117 | cpu.IYH, cpu.IYL = uint8(test.iy>>8), uint8(test.iy) 118 | 119 | cpu.SP = test.sp 120 | cpu.SetPC(int(test.pc)) 121 | cpu.I = test.i 122 | cpu.R = test.r 123 | cpu.IFF1 = test.iff1 != 0 124 | cpu.IFF2 = test.iff2 != 0 125 | cpu.IM = uint8(test.im) 126 | 127 | for _, av := range test.memory { 128 | addr := av[0] 129 | value := uint8(av[1]) 130 | mock.TestMemory.Write(addr, value) 131 | } 132 | 133 | return cpu 134 | } 135 | 136 | type fuseTest struct { 137 | name string 138 | af uint16 139 | bc uint16 140 | de uint16 141 | hl uint16 142 | af1 uint16 143 | bc1 uint16 144 | de1 uint16 145 | hl1 uint16 146 | ix uint16 147 | iy uint16 148 | sp uint16 149 | pc uint16 150 | i uint8 151 | r uint8 152 | iff1 int 153 | iff2 int 154 | im int 155 | halt int 156 | tstates int 157 | 158 | memory [][]int 159 | portReads [][]int 160 | portWrites [][]int 161 | } 162 | -------------------------------------------------------------------------------- /doc/monitor.md: -------------------------------------------------------------------------------- 1 | # monitor 2 | 3 | Enable the monitor by using -m on the command line. 4 | 5 | *NOTE*: This document needs an update as it is no longer correct. 6 | 7 | ## Arguments 8 | 9 | The arguments for *address* and *value* are decimal values, or other values using the following prefixes: 10 | 11 | - `$` or `0x`: hexadecimal 12 | - `%` or `0b`: binary 13 | 14 | Examples: 15 | ``` 16 | monitor> poke $1234 10 17 | monitor> poke $1234 $0a 18 | monitor> poke $1234 %1010 19 | ``` 20 | 21 | ## Conversions 22 | Typing in a number at the monitor prompt will show the value in decimal, 23 | hexadecimal, and binary. 24 | 25 | Examples: 26 | ``` 27 | monitor> 129 28 | 129 $81 %10000001 29 | ``` 30 | 31 | ## Commands 32 | 33 | ### b[reak] [list] 34 | 35 | List all active breakpoint addresses. 36 | 37 | ### b[reak] clear *address* 38 | 39 | Clear the breakpoint at *address*. 40 | 41 | ### b[reak] clear-all 42 | 43 | Clear all breakpoints. 44 | 45 | ### b[reak] set *address* 46 | 47 | Set a breakpoint at *address*. The CPU will be stopped before executing the instruction at this address. 48 | 49 | ### cpu 50 | 51 | Show the CPU status (registers and flags) 52 | 53 | ### cpu reg 54 | 55 | List available registers 56 | 57 | ### cpu reg *name* 58 | 59 | Show the value for the register with the given *name* 60 | 61 | ### cpu reg *name* *value* 62 | 63 | Set the *value* for the register with the given *name* 64 | 65 | ### cpu flag 66 | 67 | List available flags 68 | 69 | ### cpu flag *name* 70 | 71 | Show the value for the flag with the given *name* 72 | 73 | ### cpu flag *name* *value* 74 | 75 | Set the *value* for the flag with the given name 76 | 77 | ### d[asm] [list] [*start_address*] [*end_address*] 78 | 79 | Disassemble code from *start_address* to *end_address*. If *end_address* is not specified, disassemble an amount specified with the `dasm lines` command. If *start_address* is not specified, continue from the last disassembly. 80 | 81 | ### dasm lines 82 | 83 | Show the number of lines disassembled when an end address is not specified. A value of 0 means to disassemble an amount of lines that fit on the screen. 84 | 85 | ### dasm lines *count* 86 | 87 | Set the number of lines disassembled to *count* when an end address is not specified. A value of 0 means to disassemble an amount of lines that fit on the screen. 88 | 89 | ### g[o] 90 | 91 | Go. Start execution of the processors. 92 | 93 | ### load [*name*] 94 | 95 | Load state that was saved with the `save` command with the given *name*. If name isn't specified, `state` is used. 96 | 97 | ### m[em] [dump] [*start_address*] [*end_address*] 98 | 99 | Dump memory from *start_address* to *end_address*. If *end_address* is not specified, sump an amount specified with the `mem lines` command. If *start_address* is not specified, continue from the last dump. 100 | 101 | ### mem encoding 102 | 103 | List the character encodings available for display when dumping memory. 104 | 105 | ### mem encoding *name* 106 | 107 | Set the character encoding, with the given *name*, used when dumping memory. 108 | 109 | ### mem fill *start_address* *end_address* *value* 110 | 111 | Fill memory from *start_address* to *end_address* with *value*. 112 | 113 | ### mem lines 114 | 115 | Show the number of lines dumped when an end address is not specified. The default value is to dump a page. 116 | 117 | ### mem lines *count* 118 | 119 | Set the number of lines dumped to *count* when an end address is not specified. 120 | 121 | ### p[ause] 122 | 123 | Pause the execution of all processors. 124 | 125 | ### poke *address* *value* 126 | 127 | Set the memory *address* with the given *value* 128 | 129 | ### peek *address* 130 | 131 | Show the memory value at *address* 132 | 133 | ### q[uit] 134 | 135 | Exit. 136 | 137 | ### r 138 | 139 | Display the CPU status (registers and flags) 140 | 141 | ### save [*name*] 142 | 143 | Save the current state with the given *name*. If *name* is not specified, `state` is used. Use load to restore to this state. 144 | 145 | ### t[race] 146 | 147 | Toggle the tracing of instruction execution. 148 | 149 | ### w[atch] [list] 150 | 151 | Watch all active memory watches. 152 | 153 | ### w[atch] clear *address* 154 | 155 | Clear the watch at *address*. 156 | 157 | ### w[atch] clear-all 158 | 159 | Clear all watches. 160 | 161 | ### w[atch] set *address* *mode* 162 | 163 | Set a watch for *address*. Mode is either *r* for reads, *w* for writes, *rw* for reads and writes. 164 | 165 | ### x 166 | 167 | Stop all logging output. 168 | 169 | -------------------------------------------------------------------------------- /cmd/rcs-viewer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "sort" 10 | "strings" 11 | "unsafe" 12 | 13 | "github.com/blackchip-org/retro-cs/config" 14 | "github.com/blackchip-org/retro-cs/rcs" 15 | "github.com/veandco/go-sdl2/img" 16 | "github.com/veandco/go-sdl2/sdl" 17 | ) 18 | 19 | var ( 20 | scale int 21 | hscan bool 22 | vscan bool 23 | filename string 24 | ) 25 | 26 | func init() { 27 | flag.IntVar(&scale, "scale", 1, "image `scale`") 28 | flag.StringVar(&config.RCSDir, "home", "", "set the RCS `home` directory") 29 | flag.BoolVar(&hscan, "hscan", false, "add horizontal scan lines") 30 | flag.BoolVar(&vscan, "vscan", false, "add vertical scan lines") 31 | flag.StringVar(&filename, "out", "", "output to `filename`") 32 | 33 | flag.Usage = func() { 34 | o := flag.CommandLine.Output() 35 | fmt.Fprintf(o, "Usage: rcs-viewer [options] \n\n") 36 | flag.PrintDefaults() 37 | fmt.Fprintf(o, "\nAvailable values for :\n\n") 38 | list := []string{} 39 | for key := range views { 40 | list = append(list, key) 41 | } 42 | sort.Strings(list) 43 | fmt.Fprintln(o, strings.Join(list, "\n")) 44 | fmt.Fprintln(o) 45 | } 46 | } 47 | 48 | type view struct { 49 | system string 50 | roms []rcs.ROM 51 | render func(*sdl.Renderer, map[string][]byte) (rcs.TileSheet, error) 52 | } 53 | 54 | func main() { 55 | log.SetFlags(0) 56 | 57 | flag.Parse() 58 | if flag.NArg() != 1 { 59 | flag.Usage() 60 | os.Exit(1) 61 | } 62 | 63 | v, ok := views[flag.Arg(0)] 64 | if !ok { 65 | log.Fatalln("no such view") 66 | } 67 | 68 | var roms map[string][]byte 69 | if v.roms != nil { 70 | dir := filepath.Join(config.ResourceDir(), "data", v.system) 71 | r, err := rcs.LoadROMs(dir, v.roms) 72 | if err != nil { 73 | log.Fatalf("unable to load roms:\n%v\n", err) 74 | } 75 | roms = r 76 | } 77 | 78 | if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil { 79 | fmt.Fprintf(os.Stderr, "unable to initialize sdl: %v\n", err) 80 | os.Exit(1) 81 | } 82 | defer sdl.Quit() 83 | 84 | window, err := sdl.CreateWindow( 85 | flag.Arg(0), 86 | sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, 87 | 100, 100, 88 | sdl.WINDOW_HIDDEN, 89 | ) 90 | if err != nil { 91 | fmt.Fprintf(os.Stderr, "unable to initialize window: %v", err) 92 | os.Exit(1) 93 | } 94 | 95 | r, err := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED) 96 | if err != nil { 97 | log.Fatalf("unable to initialize renderer: %v", err) 98 | } 99 | 100 | sheet, err := v.render(r, roms) 101 | if err != nil { 102 | log.Fatalf("unable to create sheet: %v", err) 103 | } 104 | winX, winY := sheet.TextureW*int32(scale), sheet.TextureH*int32(scale) 105 | window.SetSize(winX, winY) 106 | window.SetPosition(sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED) 107 | window.Show() 108 | 109 | // err = sdl.GLSetSwapInterval(1) 110 | // if err != nil { 111 | // fmt.Printf("unable to set swap interval: %v\n", err) 112 | // } 113 | 114 | var scanlines *sdl.Texture 115 | // Now that the window has been shown, the texture needs to be rerendered. 116 | sheet, _ = v.render(r, roms) 117 | slwidth := int32(scale / 2) 118 | if slwidth == 0 { 119 | slwidth = 1 120 | } 121 | if hscan { 122 | scanlines, err = rcs.NewScanLinesH(r, winX, winY, slwidth) 123 | if err != nil { 124 | log.Fatal(err) 125 | } 126 | } 127 | if vscan { 128 | scanlines, err = rcs.NewScanLinesV(r, winX, winY, slwidth) 129 | if err != nil { 130 | log.Fatal(err) 131 | } 132 | } 133 | 134 | if filename != "" { 135 | surf, err := sdl.CreateRGBSurface(0, winX, winY, 32, 0, 0, 0, 0) 136 | if err != nil { 137 | log.Fatal(err) 138 | } 139 | r.SetRenderTarget(nil) 140 | r.SetDrawColor(0, 0, 0, 0) 141 | r.Clear() 142 | r.Copy(sheet.Texture, nil, nil) 143 | 144 | pixels := surf.Pixels() 145 | ptr := unsafe.Pointer(&pixels[0]) 146 | r.ReadPixels(nil, surf.Format.Format, ptr, int(surf.Pitch)) 147 | 148 | if err := img.SavePNG(surf, filename); err != nil { 149 | log.Fatal(err) 150 | } 151 | return 152 | } 153 | 154 | for { 155 | for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { 156 | if _, ok := event.(*sdl.QuitEvent); ok { 157 | os.Exit(0) 158 | } 159 | } 160 | 161 | r.SetRenderTarget(nil) 162 | r.SetDrawColor(0, 0, 0, 0) 163 | r.Clear() 164 | r.Copy(sheet.Texture, nil, nil) 165 | if scanlines != nil { 166 | r.Copy(scanlines, nil, nil) 167 | } 168 | sdl.Delay(250) 169 | r.Present() 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /rcs/cbm/petscii/petscii_table.go: -------------------------------------------------------------------------------- 1 | package petscii 2 | 3 | var tableUnshifted = map[uint8]rune{ 4 | 0x20: ' ', 5 | 0x21: '!', 6 | 0x22: '"', 7 | 0x23: '#', 8 | 0x24: '$', 9 | 0x25: '%', 10 | 0x26: '&', 11 | 0x27: '\'', 12 | 0x28: '(', 13 | 0x29: ')', 14 | 0x2a: '*', 15 | 0x2b: '+', 16 | 0x2c: ',', 17 | 0x2d: '-', 18 | 0x2e: '.', 19 | 0x2f: '/', 20 | 0x30: '0', 21 | 0x31: '1', 22 | 0x32: '2', 23 | 0x33: '3', 24 | 0x34: '4', 25 | 0x35: '5', 26 | 0x36: '6', 27 | 0x37: '7', 28 | 0x38: '8', 29 | 0x39: '9', 30 | 0x3a: ':', 31 | 0x3b: ';', 32 | 0x3c: '<', 33 | 0x3d: '=', 34 | 0x3e: '>', 35 | 0x3f: '?', 36 | 0x40: '@', 37 | 0x41: 'A', 38 | 0x42: 'B', 39 | 0x43: 'C', 40 | 0x44: 'D', 41 | 0x45: 'E', 42 | 0x46: 'F', 43 | 0x47: 'G', 44 | 0x48: 'H', 45 | 0x49: 'I', 46 | 0x4a: 'J', 47 | 0x4b: 'K', 48 | 0x4c: 'L', 49 | 0x4d: 'M', 50 | 0x4e: 'N', 51 | 0x4f: 'O', 52 | 0x50: 'P', 53 | 0x51: 'Q', 54 | 0x52: 'R', 55 | 0x53: 'S', 56 | 0x54: 'T', 57 | 0x55: 'U', 58 | 0x56: 'V', 59 | 0x57: 'W', 60 | 0x58: 'X', 61 | 0x59: 'Y', 62 | 0x5a: 'Z', 63 | 0x5b: '[', 64 | 0x5c: '£', 65 | 0x5d: ']', 66 | 0x5e: '↑', 67 | 0x5f: '←', 68 | 0x60: '─', 69 | 0x61: '♠', 70 | 0x62: '│', 71 | 0x63: '─', 72 | 0x64: '�', 73 | 0x65: '�', 74 | 0x66: '�', 75 | 0x67: '�', 76 | 0x68: '�', 77 | 0x69: '╮', 78 | 0x6a: '╰', 79 | 0x6b: '╯', 80 | 0x6c: '�', 81 | 0x6d: '╲', 82 | 0x6e: '╱', 83 | 0x6f: '�', 84 | 0x70: '�', 85 | 0x71: '●', 86 | 0x72: '�', 87 | 0x73: '♥', 88 | 0x74: '�', 89 | 0x75: '╭', 90 | 0x76: '╳', 91 | 0x77: '○', 92 | 0x78: '♣', 93 | 0x79: '�', 94 | 0x7a: '♦', 95 | 0x7b: '┼', 96 | 0x7c: '�', 97 | 0x7d: '│', 98 | 0x7e: 'π', 99 | 0x7f: '◥', 100 | 0xa0: ' ', 101 | 0xa1: '▌', 102 | 0xa2: '▄', 103 | 0xa3: '▔', 104 | 0xa4: '▁', 105 | 0xa5: '▏', 106 | 0xa6: '▒', 107 | 0xa7: '▕', 108 | 0xa8: '�', 109 | 0xa9: '◤', 110 | 0xaa: '�', 111 | 0xab: '├', 112 | 0xac: '▗', 113 | 0xad: '└', 114 | 0xae: '┐', 115 | 0xaf: '▂', 116 | 0xb0: '┌', 117 | 0xb1: '┴', 118 | 0xb2: '┬', 119 | 0xb3: '┤', 120 | 0xb4: '▎', 121 | 0xb5: '▍', 122 | 0xb6: '�', 123 | 0xb7: '�', 124 | 0xb8: '�', 125 | 0xb9: '▃', 126 | 0xba: '�', 127 | 0xbb: '▖', 128 | 0xbc: '▝', 129 | 0xbd: '┘', 130 | 0xbe: '▘', 131 | 0xbf: '▚', 132 | } 133 | var tableShifted = map[uint8]rune{ 134 | 0x20: ' ', 135 | 0x21: '!', 136 | 0x22: '"', 137 | 0x23: '#', 138 | 0x24: '$', 139 | 0x25: '%', 140 | 0x26: '&', 141 | 0x27: '\'', 142 | 0x28: '(', 143 | 0x29: ')', 144 | 0x2a: '*', 145 | 0x2b: '+', 146 | 0x2c: ',', 147 | 0x2d: '-', 148 | 0x2e: '.', 149 | 0x2f: '/', 150 | 0x30: '0', 151 | 0x31: '1', 152 | 0x32: '2', 153 | 0x33: '3', 154 | 0x34: '4', 155 | 0x35: '5', 156 | 0x36: '6', 157 | 0x37: '7', 158 | 0x38: '8', 159 | 0x39: '9', 160 | 0x3a: ':', 161 | 0x3b: ';', 162 | 0x3c: '<', 163 | 0x3d: '=', 164 | 0x3e: '>', 165 | 0x3f: '?', 166 | 0x40: '@', 167 | 0x41: 'a', 168 | 0x42: 'b', 169 | 0x43: 'c', 170 | 0x44: 'd', 171 | 0x45: 'e', 172 | 0x46: 'f', 173 | 0x47: 'g', 174 | 0x48: 'h', 175 | 0x49: 'i', 176 | 0x4a: 'j', 177 | 0x4b: 'k', 178 | 0x4c: 'l', 179 | 0x4d: 'm', 180 | 0x4e: 'n', 181 | 0x4f: 'o', 182 | 0x50: 'p', 183 | 0x51: 'q', 184 | 0x52: 'r', 185 | 0x53: 's', 186 | 0x54: 't', 187 | 0x55: 'u', 188 | 0x56: 'v', 189 | 0x57: 'w', 190 | 0x58: 'x', 191 | 0x59: 'y', 192 | 0x5a: 'z', 193 | 0x5b: '[', 194 | 0x5c: '£', 195 | 0x5d: ']', 196 | 0x5e: '↑', 197 | 0x5f: '←', 198 | 0x60: '─', 199 | 0x61: 'A', 200 | 0x62: 'B', 201 | 0x63: 'C', 202 | 0x64: 'D', 203 | 0x65: 'E', 204 | 0x66: 'F', 205 | 0x67: 'G', 206 | 0x68: 'H', 207 | 0x69: 'I', 208 | 0x6a: 'J', 209 | 0x6b: 'K', 210 | 0x6c: 'L', 211 | 0x6d: 'M', 212 | 0x6e: 'N', 213 | 0x6f: 'O', 214 | 0x70: 'P', 215 | 0x71: 'Q', 216 | 0x72: 'R', 217 | 0x73: 'S', 218 | 0x74: 'T', 219 | 0x75: 'U', 220 | 0x76: 'V', 221 | 0x77: 'W', 222 | 0x78: 'X', 223 | 0x79: 'Y', 224 | 0x7a: 'Z', 225 | 0x7b: '┼', 226 | 0x7c: '�', 227 | 0x7d: '│', 228 | 0x7e: '▒', 229 | 0x7f: '�', 230 | 0xa0: ' ', 231 | 0xa1: '▌', 232 | 0xa2: '▄', 233 | 0xa3: '▔', 234 | 0xa4: '▁', 235 | 0xa5: '▏', 236 | 0xa6: '▒', 237 | 0xa7: '▕', 238 | 0xa8: '�', 239 | 0xa9: '�', 240 | 0xaa: '�', 241 | 0xab: '├', 242 | 0xac: '▗', 243 | 0xad: '└', 244 | 0xae: '┐', 245 | 0xaf: '▂', 246 | 0xb0: '┌', 247 | 0xb1: '┴', 248 | 0xb2: '┬', 249 | 0xb3: '┤', 250 | 0xb4: '▎', 251 | 0xb5: '▍', 252 | 0xb6: '�', 253 | 0xb7: '�', 254 | 0xb8: '�', 255 | 0xb9: '▃', 256 | 0xba: '✓', 257 | 0xbb: '▖', 258 | 0xbc: '▝', 259 | 0xbd: '┘', 260 | 0xbe: '▘', 261 | 0xbf: '▚', 262 | } 263 | -------------------------------------------------------------------------------- /app/monitor/namco.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/chzyer/readline" 8 | 9 | "github.com/blackchip-org/retro-cs/rcs" 10 | "github.com/blackchip-org/retro-cs/rcs/namco" 11 | ) 12 | 13 | type modN06XX struct { 14 | mon *Monitor 15 | out *log.Logger 16 | n06xx *namco.N06XX 17 | } 18 | 19 | func newModN06XX(mon *Monitor, comp rcs.Component) module { 20 | return &modN06XX{ 21 | mon: mon, 22 | out: mon.out, 23 | n06xx: comp.C.(*namco.N06XX), 24 | } 25 | } 26 | 27 | func (m *modN06XX) Command(args []string) error { 28 | if err := checkLen(args, 1, maxArgs); err != nil { 29 | return err 30 | } 31 | switch args[0] { 32 | case "watch-data-write": 33 | return valueBool(m.out, &m.n06xx.WatchDataW, args[1:]) 34 | case "watch-data-read": 35 | return valueBool(m.out, &m.n06xx.WatchDataR, args[1:]) 36 | case "watch-control-write": 37 | return valueBool(m.out, &m.n06xx.WatchCtrlW, args[1:]) 38 | case "watch-control-read": 39 | return valueBool(m.out, &m.n06xx.WatchCtrlR, args[1:]) 40 | case "watch-nmi": 41 | return valueBool(m.out, &m.n06xx.WatchNMI, args[1:]) 42 | case "watch-all": 43 | return terminal(args[1:], func() error { 44 | m.n06xx.WatchDataW = true 45 | m.n06xx.WatchDataR = true 46 | m.n06xx.WatchCtrlW = true 47 | m.n06xx.WatchCtrlR = true 48 | m.n06xx.WatchNMI = true 49 | return nil 50 | }) 51 | case "watch-none": 52 | return terminal(args[1:], m.Silence) 53 | } 54 | return fmt.Errorf("no such command: %v", args[0]) 55 | } 56 | 57 | func (m *modN06XX) AutoComplete() []readline.PrefixCompleterInterface { 58 | return []readline.PrefixCompleterInterface{ 59 | readline.PcItem("watch-data-write"), 60 | readline.PcItem("watch-data-read"), 61 | readline.PcItem("watch-control-write"), 62 | readline.PcItem("watch-control-read"), 63 | readline.PcItem("watch-nmi"), 64 | readline.PcItem("watch-all"), 65 | readline.PcItem("watch-none"), 66 | } 67 | } 68 | 69 | func (m *modN06XX) Silence() error { 70 | m.n06xx.WatchDataW = false 71 | m.n06xx.WatchDataR = false 72 | m.n06xx.WatchCtrlW = false 73 | m.n06xx.WatchCtrlR = false 74 | m.n06xx.WatchNMI = false 75 | return nil 76 | } 77 | 78 | type modN51XX struct { 79 | mon *Monitor 80 | out *log.Logger 81 | n51xx *namco.N51XX 82 | } 83 | 84 | func newModN51XX(mon *Monitor, comp rcs.Component) module { 85 | return &modN51XX{ 86 | mon: mon, 87 | out: mon.out, 88 | n51xx: comp.C.(*namco.N51XX), 89 | } 90 | } 91 | 92 | func (m *modN51XX) Command(args []string) error { 93 | if err := checkLen(args, 1, maxArgs); err != nil { 94 | return err 95 | } 96 | switch args[0] { 97 | case "watch-write": 98 | return valueBool(m.out, &m.n51xx.WatchW, args[1:]) 99 | case "watch-read": 100 | return valueBool(m.out, &m.n51xx.WatchR, args[1:]) 101 | case "watch-all": 102 | return terminal(args[1:], func() error { 103 | m.n51xx.WatchW = true 104 | m.n51xx.WatchR = true 105 | return nil 106 | }) 107 | case "watch-none": 108 | return terminal(args[1:], m.Silence) 109 | } 110 | return fmt.Errorf("no such command: %v", args[0]) 111 | } 112 | 113 | func (m *modN51XX) AutoComplete() []readline.PrefixCompleterInterface { 114 | return []readline.PrefixCompleterInterface{ 115 | readline.PcItem("watch-write"), 116 | readline.PcItem("watch-read"), 117 | readline.PcItem("watch-all"), 118 | readline.PcItem("watch-none"), 119 | } 120 | } 121 | 122 | func (m *modN51XX) Silence() error { 123 | m.n51xx.WatchW = false 124 | m.n51xx.WatchR = false 125 | return nil 126 | } 127 | 128 | type modN54XX struct { 129 | mon *Monitor 130 | out *log.Logger 131 | n54xx *namco.N54XX 132 | } 133 | 134 | func newModN54XX(mon *Monitor, comp rcs.Component) module { 135 | return &modN54XX{ 136 | mon: mon, 137 | out: mon.out, 138 | n54xx: comp.C.(*namco.N54XX), 139 | } 140 | } 141 | 142 | func (m *modN54XX) Command(args []string) error { 143 | if err := checkLen(args, 1, maxArgs); err != nil { 144 | return err 145 | } 146 | switch args[0] { 147 | case "watch-write": 148 | return valueBool(m.out, &m.n54xx.WatchW, args[1:]) 149 | case "watch-read": 150 | return valueBool(m.out, &m.n54xx.WatchR, args[1:]) 151 | case "watch-all": 152 | return terminal(args[1:], func() error { 153 | m.n54xx.WatchW = true 154 | m.n54xx.WatchR = true 155 | return nil 156 | }) 157 | case "watch-none": 158 | return terminal(args[1:], m.Silence) 159 | } 160 | return fmt.Errorf("no such command: %v", args[0]) 161 | } 162 | 163 | func (m *modN54XX) AutoComplete() []readline.PrefixCompleterInterface { 164 | return []readline.PrefixCompleterInterface{ 165 | readline.PcItem("watch-write"), 166 | readline.PcItem("watch-read"), 167 | readline.PcItem("watch-all"), 168 | readline.PcItem("watch-none"), 169 | } 170 | } 171 | 172 | func (m *modN54XX) Silence() error { 173 | m.n54xx.WatchW = false 174 | m.n54xx.WatchR = false 175 | return nil 176 | } 177 | -------------------------------------------------------------------------------- /rcs/z80/zex_test.go: -------------------------------------------------------------------------------- 1 | // +build ext 2 | 3 | package z80 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "path/filepath" 11 | "strings" 12 | "testing" 13 | 14 | "github.com/blackchip-org/retro-cs/mock" 15 | "github.com/blackchip-org/retro-cs/rcs" 16 | ) 17 | 18 | var ( 19 | root = filepath.Join("..", "..") 20 | sourceDir = filepath.Join(root, "ext", "zex") 21 | ) 22 | 23 | var zexdocTests = []string{ 24 | "adc16", 25 | "add16", 26 | "add16x", 27 | "add16y", 28 | "alu8i", 29 | "alu8r", 30 | "alu8rx", 31 | "alu8x", 32 | "bitx", 33 | "bitz80", 34 | "cpd1", 35 | "cpi1", 36 | "daa", 37 | "inca", 38 | "incb", 39 | "incbc", 40 | "incc", 41 | "incd", 42 | "incde", 43 | "ince", 44 | "inch", 45 | "inchl", 46 | "incix", 47 | "inciy", 48 | "incl", 49 | "incm", 50 | "incsp", 51 | "incx", 52 | "incxh", 53 | "incxl", 54 | "incyh", 55 | "incyl", 56 | "ld161", 57 | "ld162", 58 | "ld163", 59 | "ld164", 60 | "ld165", 61 | "ld166", 62 | "ld167", 63 | "ld168", 64 | "ld16im", 65 | "ld16ix", 66 | "ld8bd", 67 | "ld8im", 68 | "ld8imx", 69 | "ld8ix1", 70 | "ld8ix2", 71 | "ld8ix3", 72 | "ld8ixy", 73 | "ld8rr", 74 | "ld8rrx", 75 | "lda", 76 | "ldd1", 77 | "ldd2", 78 | "ldi1", 79 | "ldi2", 80 | "neg", 81 | "rld", 82 | "rot8080", 83 | "rotxy", 84 | "rotz80", 85 | "srz80", 86 | "srzx", 87 | "st8ix1", 88 | "st8ix2", 89 | "st8ix3", 90 | "stabd", 91 | } 92 | 93 | var zexdoc []byte 94 | 95 | func init() { 96 | var err error 97 | zexdocFile := filepath.Join(sourceDir, "zexdoc.com") 98 | zexdoc, err = ioutil.ReadFile(zexdocFile) 99 | if err != nil { 100 | log.Panicf("unable to read %v: %v", zexdocFile, err) 101 | } 102 | } 103 | 104 | // Running the full zexdoc can take more than 10 minutes. This test instead 105 | // breaks up each test into an individual run. The HL register is loaded with 106 | // the address of the test and the program counter is set to the beginning 107 | // of the normal test loop. Execution is stopped when the program counter 108 | // returns to the top of the loop. Output is then checked for "ERROR" to 109 | // determine if the test passes or fails. 110 | const loopStart = 0x0122 111 | 112 | func TestZexdoc(t *testing.T) { 113 | testBaseAddr := uint16(0x013a) 114 | for i, test := range zexdocTests { 115 | addr := testBaseAddr + (uint16(i) * 2) 116 | t.Run(test, func(t *testing.T) { 117 | runner := newRunner(zexdoc, addr) 118 | passed := runner.Run() 119 | if !passed { 120 | t.Fail() 121 | } 122 | }) 123 | } 124 | } 125 | 126 | func BenchmarkZexdoc(b *testing.B) { 127 | testBaseAddr := uint16(0x013a) 128 | for i, test := range zexdocTests { 129 | addr := testBaseAddr + (uint16(i) * 2) 130 | runner := newRunner(zexdoc, addr) 131 | b.Run(test, func(b *testing.B) { 132 | for n := 0; n < b.N; n++ { 133 | runner.Next() 134 | if runner.Done() { 135 | runner.Reset() 136 | } 137 | } 138 | }) 139 | } 140 | } 141 | 142 | type zexRunner struct { 143 | mem *rcs.Memory 144 | cpu *CPU 145 | out bytes.Buffer 146 | testAddr uint16 147 | } 148 | 149 | func newRunner(code []byte, addr uint16) *zexRunner { 150 | // Follow the notes at: 151 | // https://floooh.github.io/2016/07/12/z80-rust-ms1.html 152 | mock.ResetMemory() 153 | // Import the code 154 | for i, b := range code { 155 | mock.TestMemory.Write(0x100+i, b) 156 | } 157 | c := New(mock.TestMemory) 158 | zr := &zexRunner{mem: mock.TestMemory, cpu: c, testAddr: addr} 159 | zr.Reset() 160 | return zr 161 | } 162 | 163 | func (z *zexRunner) Next() { 164 | z.cpu.Next() 165 | } 166 | 167 | func (z *zexRunner) Syscall() { 168 | // System call that outputs to the screen 169 | if z.cpu.PC() == 0x0005 { 170 | // Single character out 171 | if z.cpu.C == 0x02 { 172 | msg := fmt.Sprintf("%c", rune(z.cpu.C)) 173 | z.out.WriteString(msg) 174 | fmt.Print(msg) 175 | } 176 | // String out, terminated by $ 177 | if z.cpu.C == 0x09 { 178 | addr := int(z.cpu.D)<<8 | int(z.cpu.E) 179 | for { 180 | ch := rune(z.mem.Read(addr)) 181 | if ch == '$' { 182 | break 183 | } 184 | msg := fmt.Sprintf("%c", ch) 185 | z.out.WriteString(msg) 186 | fmt.Print(msg) 187 | addr++ 188 | } 189 | } 190 | // Return from subroutine 191 | z.cpu.SetPC(z.mem.ReadLE(int(z.cpu.SP))) 192 | z.cpu.SP += 2 193 | } 194 | } 195 | 196 | func (z *zexRunner) Done() bool { 197 | return z.cpu.PC() == loopStart 198 | } 199 | 200 | func (z *zexRunner) Passed() bool { 201 | return !strings.Contains(z.out.String(), "ERROR") 202 | } 203 | 204 | func (z *zexRunner) Reset() { 205 | z.cpu.SP = 0xf000 206 | // HL register is loaded with the address of the test to run 207 | z.cpu.H, z.cpu.L = uint8(z.testAddr>>8), uint8(z.testAddr) 208 | z.cpu.SetPC(loopStart) 209 | } 210 | 211 | func (z *zexRunner) Run() bool { 212 | for { 213 | z.Next() 214 | z.Syscall() 215 | if z.Done() { 216 | break 217 | } 218 | } 219 | return z.Passed() 220 | } 221 | -------------------------------------------------------------------------------- /rcs/z80/reader.go: -------------------------------------------------------------------------------- 1 | package z80 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/blackchip-org/retro-cs/rcs" 8 | ) 9 | 10 | func Reader(e rcs.StmtEval) { 11 | e.Stmt.Addr = e.Ptr.Addr() 12 | opcode := e.Ptr.Fetch() 13 | e.Stmt.Bytes = []uint8{opcode} 14 | dasmTable[opcode](e) 15 | } 16 | 17 | func Formatter() rcs.CodeFormatter { 18 | options := rcs.FormatOptions{ 19 | BytesFormat: "%-11s", 20 | } 21 | return func(s rcs.Stmt) string { 22 | return rcs.FormatStmt(s, options) 23 | } 24 | } 25 | 26 | func NewDisassembler(mem *rcs.Memory) *rcs.Disassembler { 27 | return rcs.NewDisassembler(mem, Reader, Formatter()) 28 | } 29 | 30 | func op1(e rcs.StmtEval, parts ...string) { 31 | var out strings.Builder 32 | for i, part := range parts { 33 | v := part 34 | switch { 35 | case i == 0: 36 | v = fmt.Sprintf("%-4s", part) 37 | case parts[0] == "rst" && i == 1: 38 | // Reset statements have the argment encoded in the opcode. Change 39 | // the hex notation from & to $ in the second part 40 | v = "$" + v[1:] 41 | case part == "&4546": 42 | // This is an address that is a 8-bit displacement from the 43 | // current program counter 44 | delta := e.Ptr.Fetch() 45 | e.Stmt.Bytes = append(e.Stmt.Bytes, delta) 46 | addr := displace(e.Stmt.Addr+2, delta) 47 | v = fmt.Sprintf("$%04x", addr) 48 | case part == "&0000": 49 | lo := e.Ptr.Fetch() 50 | e.Stmt.Bytes = append(e.Stmt.Bytes, lo) 51 | hi := e.Ptr.Fetch() 52 | e.Stmt.Bytes = append(e.Stmt.Bytes, hi) 53 | addr := int(hi)<<8 | int(lo) 54 | v = fmt.Sprintf("$%04x", addr) 55 | case part == "(&0000)": 56 | lo := e.Ptr.Fetch() 57 | e.Stmt.Bytes = append(e.Stmt.Bytes, lo) 58 | hi := e.Ptr.Fetch() 59 | e.Stmt.Bytes = append(e.Stmt.Bytes, hi) 60 | addr := int(hi)<<8 | int(lo) 61 | v = fmt.Sprintf("($%04x)", addr) 62 | case part == "&00": 63 | arg := e.Ptr.Fetch() 64 | e.Stmt.Bytes = append(e.Stmt.Bytes, arg) 65 | v = fmt.Sprintf("$%02x", arg) 66 | case part == "(&00)": 67 | arg := e.Ptr.Fetch() 68 | e.Stmt.Bytes = append(e.Stmt.Bytes, arg) 69 | v = fmt.Sprintf("($%02x)", arg) 70 | case part == "(ix+0)": 71 | delta := e.Ptr.Fetch() 72 | e.Stmt.Bytes = append(e.Stmt.Bytes, delta) 73 | v = fmt.Sprintf("(ix+$%02x)", delta) 74 | case part == "(iy+0)": 75 | delta := e.Ptr.Fetch() 76 | e.Stmt.Bytes = append(e.Stmt.Bytes, delta) 77 | v = fmt.Sprintf("(iy+$%02x)", delta) 78 | } 79 | 80 | if i == 1 { 81 | out.WriteString(" ") 82 | } 83 | if i == 2 { 84 | out.WriteString(",") 85 | } 86 | out.WriteString(v) 87 | } 88 | e.Stmt.Op = strings.TrimSpace(out.String()) 89 | } 90 | 91 | func op2(e rcs.StmtEval, parts ...string) { 92 | var out strings.Builder 93 | for i, part := range parts { 94 | v := part 95 | switch { 96 | case i == 0: 97 | v = fmt.Sprintf("%-4s", part) 98 | case part == "(ix+0)": 99 | delta := e.Stmt.Bytes[len(e.Stmt.Bytes)-2] 100 | v = fmt.Sprintf("(ix+$%02x)", delta) 101 | case part == "(iy+0)": 102 | delta := e.Stmt.Bytes[len(e.Stmt.Bytes)-2] 103 | v = fmt.Sprintf("(iy+$%02x)", delta) 104 | } 105 | 106 | if i == 1 { 107 | out.WriteString(" ") 108 | } 109 | if i == 2 { 110 | out.WriteString(",") 111 | } 112 | out.WriteString(v) 113 | } 114 | e.Stmt.Op = strings.TrimSpace(out.String()) 115 | } 116 | 117 | func opDD(e rcs.StmtEval) { 118 | next := e.Ptr.Peek() 119 | if next == 0xdd || next == 0xed || next == 0xfd { 120 | e.Stmt.Op = "?dd" 121 | return 122 | } 123 | opcode := e.Ptr.Fetch() 124 | e.Stmt.Bytes = append(e.Stmt.Bytes, opcode) 125 | if opcode == 0xcb { 126 | opDDCB(e) 127 | return 128 | } 129 | dasmTableDD[opcode](e) 130 | } 131 | 132 | func opFD(e rcs.StmtEval) { 133 | next := e.Ptr.Peek() 134 | if next == 0xdd || next == 0xed || next == 0xfd { 135 | e.Stmt.Op = "?fd" 136 | return 137 | } 138 | opcode := e.Ptr.Fetch() 139 | e.Stmt.Bytes = append(e.Stmt.Bytes, opcode) 140 | if opcode == 0xcb { 141 | opFDCB(e) 142 | return 143 | } 144 | dasmTableFD[opcode](e) 145 | } 146 | 147 | func opCB(e rcs.StmtEval) { 148 | opcode := e.Ptr.Fetch() 149 | e.Stmt.Bytes = append(e.Stmt.Bytes, opcode) 150 | dasmTableCB[opcode](e) 151 | } 152 | 153 | func opED(e rcs.StmtEval) { 154 | opcode := e.Ptr.Fetch() 155 | e.Stmt.Bytes = append(e.Stmt.Bytes, opcode) 156 | dasmTableED[opcode](e) 157 | } 158 | 159 | func opFDCB(e rcs.StmtEval) { 160 | delta := e.Ptr.Fetch() 161 | e.Stmt.Bytes = append(e.Stmt.Bytes, delta) 162 | opcode := e.Ptr.Fetch() 163 | e.Stmt.Bytes = append(e.Stmt.Bytes, opcode) 164 | dasmTableFDCB[opcode](e) 165 | } 166 | 167 | func opDDCB(e rcs.StmtEval) { 168 | delta := e.Ptr.Fetch() 169 | e.Stmt.Bytes = append(e.Stmt.Bytes, delta) 170 | opcode := e.Ptr.Fetch() 171 | e.Stmt.Bytes = append(e.Stmt.Bytes, opcode) 172 | dasmTableDDCB[opcode](e) 173 | } 174 | 175 | func displace(value int, delta uint8) uint16 { 176 | sdelta := int8(delta) 177 | v := int(value) + int(sdelta) 178 | return uint16(v) 179 | } 180 | -------------------------------------------------------------------------------- /system/galaga/galaga.go: -------------------------------------------------------------------------------- 1 | // Package galaga is the hardware cabinet for Galaga. 2 | package galaga 3 | 4 | import ( 5 | "github.com/blackchip-org/retro-cs/config" 6 | "github.com/blackchip-org/retro-cs/rcs" 7 | "github.com/blackchip-org/retro-cs/rcs/namco" 8 | "github.com/blackchip-org/retro-cs/rcs/z80" 9 | ) 10 | 11 | type System struct { 12 | cpu [3]*z80.CPU 13 | mem [3]*rcs.Memory 14 | ram []uint8 15 | n06xx *namco.N06XX 16 | n51xx *namco.N51XX 17 | n54xx *namco.N54XX 18 | 19 | video *namco.Video 20 | 21 | InterruptEnable0 uint8 // low bit 22 | InterruptEnable1 uint8 // low bit 23 | InterruptEnable2 uint8 // low bit 24 | reset uint8 25 | dipSwitches [8]uint8 26 | } 27 | 28 | func new(ctx rcs.SDLContext, set []rcs.ROM) (*rcs.Mach, error) { 29 | s := &System{} 30 | roms, err := rcs.LoadROMs(config.DataDir, set) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | // construct the common memory first 36 | mem := rcs.NewMemory(1, 0x10000) 37 | ram := make([]uint8, 0x2000, 0x2000) 38 | 39 | mem.MapRAM(0x6800, make([]uint8, 0x100, 0x100)) // temporary 40 | for i := 0; i < 8; i++ { 41 | mem.MapRW(0x6800+i, &s.dipSwitches[i]) 42 | } 43 | mem.MapRW(0x6820, &s.InterruptEnable0) 44 | mem.MapRW(0x6821, &s.InterruptEnable1) 45 | mem.MapRW(0x6822, &s.InterruptEnable2) 46 | mem.MapRW(0x6823, &s.reset) 47 | 48 | mem.MapRAM(0x7000, make([]uint8, 0x1000, 0x1000)) 49 | mem.MapRAM(0x8000, ram) 50 | mem.MapRAM(0xa000, make([]uint8, 0x1000, 0x1000)) 51 | 52 | s.n51xx = namco.NewN51XX() 53 | s.n54xx = namco.NewN54XX() 54 | 55 | s.n06xx = namco.NewN06XX() 56 | s.n06xx.DeviceW[0] = s.n51xx.Write 57 | s.n06xx.DeviceR[0] = s.n51xx.Read 58 | s.n06xx.DeviceW[3] = s.n54xx.Write 59 | s.n06xx.DeviceR[3] = s.n54xx.Read 60 | for i, addr := 0, 0x7000; addr < 0x7100; addr, i = addr+1, i+1 { 61 | j := i 62 | mem.MapLoad(addr, s.n06xx.ReadData(j)) 63 | mem.MapStore(addr, s.n06xx.WriteData(j)) 64 | } 65 | for i, addr := 0, 0x7100; addr < 0x7200; addr, i = addr+1, i+1 { 66 | j := i 67 | mem.MapLoad(addr, s.n06xx.ReadCtrl(j)) 68 | mem.MapStore(addr, s.n06xx.WriteCtrl(j)) 69 | } 70 | 71 | var screen rcs.Screen 72 | var video *namco.Video 73 | if ctx.Renderer != nil { 74 | data := namco.Data{ 75 | Palettes: roms["palettes"], 76 | Colors: roms["colors"], 77 | Tiles: roms["tiles"], 78 | Sprites: roms["sprites"], 79 | } 80 | video, err = newVideo(ctx.Renderer, data) 81 | if err != nil { 82 | return nil, err 83 | } 84 | mem.MapRAM(0x8000, video.TileMemory) 85 | mem.MapRAM(0x8400, video.ColorMemory) 86 | 87 | screen = rcs.Screen{ 88 | W: namco.W, 89 | H: namco.H, 90 | Texture: video.Texture, 91 | ScanLineV: true, 92 | Draw: video.Draw, 93 | } 94 | } 95 | 96 | s.dipSwitches[3] = 1 97 | s.dipSwitches[4] = 2 98 | s.dipSwitches[5] = 1 99 | s.dipSwitches[6] = 1 100 | 101 | // HACK 102 | mem.Write(0x9100, 0xff) 103 | mem.Write(0x9101, 0xff) 104 | 105 | // memory for each CPU 106 | s.mem[0] = rcs.NewMemory(1, 0x10000) 107 | s.mem[0].Name = "mem1" 108 | s.mem[0].Map(0, mem) 109 | s.mem[0].MapROM(0x0000, roms["code1"]) 110 | 111 | s.mem[1] = rcs.NewMemory(1, 0x10000) 112 | s.mem[1].Name = "mem2" 113 | s.mem[1].Map(0, mem) 114 | s.mem[1].MapROM(0x0000, roms["code2"]) 115 | 116 | s.mem[2] = rcs.NewMemory(1, 0x10000) 117 | s.mem[2].Name = "mem3" 118 | s.mem[2].Map(0, mem) 119 | s.mem[2].MapROM(0x0000, roms["code3"]) 120 | 121 | s.cpu[0] = z80.New(s.mem[0]) 122 | s.cpu[0].Name = "cpu1" 123 | s.cpu[1] = z80.New(s.mem[1]) 124 | s.cpu[1].Name = "cpu2" 125 | s.cpu[2] = z80.New(s.mem[2]) 126 | s.cpu[2].Name = "cpu3" 127 | 128 | vblank := func() { 129 | if s.InterruptEnable0 != 0 { 130 | s.cpu[0].IRQ = true 131 | } 132 | if s.InterruptEnable1 != 0 { 133 | s.cpu[1].IRQ = true 134 | } 135 | //s.cpu[2].IRQ = true 136 | if s.InterruptEnable2 != 0 { 137 | // FIXME: Is this correct??? Probably not 138 | s.cpu[2].NMI = true 139 | } 140 | if s.reset != 0 { 141 | s.reset = 0 142 | s.cpu[1].RESET = true 143 | s.cpu[2].RESET = true 144 | } 145 | } 146 | 147 | s.n06xx.NMI = func() { 148 | s.cpu[0].NMI = true 149 | } 150 | 151 | mach := &rcs.Mach{ 152 | Sys: s, 153 | Comps: []rcs.Component{ 154 | rcs.NewComponent("galaga", "galaga", "", s), 155 | rcs.NewComponent("mem1", "mem", "", s.mem[0]), 156 | rcs.NewComponent("mem2", "mem", "", s.mem[1]), 157 | rcs.NewComponent("mem3", "mem", "", s.mem[2]), 158 | rcs.NewComponent("cpu1", "z80", "mem1", s.cpu[0]), 159 | rcs.NewComponent("cpu2", "z80", "mem2", s.cpu[1]), 160 | rcs.NewComponent("cpu3", "z80", "mem3", s.cpu[2]), 161 | rcs.NewComponent("n06xx", "n06xx", "", s.n06xx), 162 | rcs.NewComponent("n51xx", "n51xx", "", s.n51xx), 163 | rcs.NewComponent("n54xx", "n54xx", "", s.n54xx), 164 | }, 165 | CharDecoders: map[string]rcs.CharDecoder{ 166 | "galaga": GalagaDecoder, 167 | }, 168 | Ctx: ctx, 169 | Screen: screen, 170 | VBlankFunc: vblank, 171 | } 172 | return mach, nil 173 | } 174 | 175 | func New(ctx rcs.SDLContext) (*rcs.Mach, error) { 176 | return new(ctx, ROM["galaga"]) 177 | } 178 | -------------------------------------------------------------------------------- /doc/memory.md: -------------------------------------------------------------------------------- 1 | # memory 2 | 3 | If it was only as easy as... 4 | 5 | ```go 6 | var mem [65536]uint8 7 | ``` 8 | 9 | Sometime around the year 2009, I embarked on writing my first emulator. I did not implement memory as an array because I knew that the Commodore 64 used banked memory. The 6510 processor has 16 address lines and can, therefore, access up to 64K of different memory locations. The computer has 64K of RAM available, but all of that RAM might not be visible depending on the bank that is selected. 10 | 11 | Powering on the Commodore 64 with no cartridges plugged in lands the user at a "READY" prompt where it patiently waits for BASIC commands. The ROM that holds the code for the BASIC interpreter needs to be accessible to the CPU somewhere. In this case it is mapped to the address range starting with 0xa000 and ending with 0xbfff. While the BASIC ROM is banked in, the RAM at these addresses cannot be read. 12 | 13 | In that first emulator, I came up with an abstraction to memory that was overly complicated and horribly inefficient. The goal of that emulator was to hack up an implementation of a 6502 series processor and to put some fun fluff on top. Emulating an actual Commodore was never in the plan. I'm not sure why I ever thought I would need banked memory. I guess I was in more fear of the 64K boundary than I was of the cringe-worthy code that I was writing. I lost interest in that project while there was still wide expanses of empty memory. I should have used an array. 14 | 15 | Early in the year 2018, I started work on this emulator which was planned from the start to emulate the Commodore 64 and only the Commodore 64. This time banked memory would be necessary. And once again, I came up with an abstraction that was overly complicated. Memory was divided into seven different regions and various "chunks" could be mapped to a region depending on the bank that was selected. 16 | 17 | It ignored the fact that a lot of the addresses in the IO region are mapped to registers on various chips. When the IO region is mapped in, the memory address 0xd000 does not point to RAM or ROM at all. It reads or writes to a register on the Video Interface Chip (VIC-II) and this register controls the X coordinate for sprite #0. In this emulator the IO region was created as a RAM chunk and external chips had to use this RAM for their registers. Not great but I at least got the emulator to boot to a "READY" prompt with this technique. 18 | 19 | The Pac-Man emulator came next. I couldn't use the memory with the seven regions from the Commodore emulator because that didn't apply here. It was too Commodore 64 specific. I decided on blocks of memory that could be mapped at page boundaries. It seemed like a good idea at the time. Everything seemed to line up at page boundaries. 20 | 21 | I should have read the memory map instead of skimming it. 22 | 23 | When I finally had the parts of the Pac-Man emulator ready for assembly I realized this page boundary scheme might be a problem. There were certain addresses that would write to one value but read from another. For example, a write to 0x5000 enables or disables interrupts but a read returns the value of port IN0--inputs for joysticks, coin slots, etc. By the way, IN0 can also be read at 0x5001, 0x5002, and every address up to 0x503f. So much for being exact. 24 | 25 | At this point, I had a common interface that memory types implemented: 26 | 27 | ```go 28 | type Memory interface { 29 | Read(addr int) uint8 30 | Write(addr int, val uint8) 31 | } 32 | ``` 33 | There was RAM which was backed by a byte array, ROM which was also backed by a byte array that ignored writes, null memory that did nothing, page mapped memory, and memory that would spy on reads and writes. I introduced another memory, IO, that had two values per address, one for reading and one for writing. And instead of being actual values, they were pointers to values. 34 | 35 | Galaga was next and the CPU now had to interface with two different chips, 36 | the N51XX and N54XX, but accesses to these chips were through another chip, the N06XX. A write to an address could go to either the N51XX or the N54XX depending on a value set in the N06XX. What was really needed was neither a value, nor a pointer to a value, but a function. I wanted to introduce another memory type that had two functions per address, one for reading and one for writing. This was starting to get complicated. Again. 37 | 38 | How about making all memory addresses have a read and write function and scrap all the other memory types? Refactoring memory started the grand refactor by combining the Commodore 64 and Pac-Man/Galaga code together to make the Retro-CS. 39 | 40 | Will this be the [final memory scheme](https://godoc.org/github.com/blackchip-org/retro-cs/rcs#Memory)? Who knows. But I'm good at making things overly complicated. 41 | 42 | ## Retrospection 43 | 44 | Factors to consider when emulating memory: 45 | 46 | - An address might point to different values depending on a selected bank 47 | - An address might be mapped to a register on an external chip 48 | - An address value might map to two values--one for reading, one for writing. 49 | - A value might map to multiple addresses. 50 | - A read or write to an address value might "do something" 51 | 52 | Use functions. 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /rcs/cbm/vic.go: -------------------------------------------------------------------------------- 1 | package cbm 2 | 3 | import ( 4 | "image/color" 5 | 6 | "github.com/blackchip-org/retro-cs/rcs" 7 | "github.com/veandco/go-sdl2/sdl" 8 | ) 9 | 10 | const ( 11 | width = 320 12 | height = 200 13 | screenW = 404 // actually 403? 14 | screenH = 284 15 | borderW = (screenW - width) / 2 16 | borderH = (screenH - height) / 2 17 | charSheetW = 32 18 | charSheetH = 16 19 | ) 20 | 21 | type VIC struct { 22 | W int32 23 | H int32 24 | Texture *sdl.Texture 25 | 26 | BorderColor uint8 27 | BgColor uint8 28 | 29 | charSheet rcs.TileSheet 30 | mem *rcs.Memory 31 | } 32 | 33 | func NewVIC(r *sdl.Renderer, mem *rcs.Memory, charData []uint8) (*VIC, error) { 34 | v := &VIC{W: screenW, H: screenH, mem: mem} 35 | if r == nil { 36 | return v, nil 37 | } 38 | t, err := r.CreateTexture(sdl.PIXELFORMAT_RGBA8888, sdl.TEXTUREACCESS_TARGET, 39 | screenW, screenH) 40 | if err != nil { 41 | return nil, err 42 | } 43 | v.Texture = t 44 | charSheet, err := CharGen(r, charData) 45 | if err != nil { 46 | return nil, err 47 | } 48 | v.charSheet = charSheet 49 | return v, nil 50 | } 51 | 52 | func (v *VIC) Draw(r *sdl.Renderer) error { 53 | v.mem.Write(0xd012, 00) // HACK: set raster line to zero 54 | r.SetRenderTarget(v.Texture) 55 | r.SetDrawBlendMode(sdl.BLENDMODE_BLEND) 56 | v.drawBorder(r) 57 | v.drawBackground(r) 58 | v.drawCharacters(r) 59 | r.SetRenderTarget(nil) 60 | return nil 61 | } 62 | 63 | func (v *VIC) drawBorder(r *sdl.Renderer) { 64 | c := Palette[v.BorderColor&0x0f] 65 | r.SetDrawColor(c.R, c.G, c.B, c.A) 66 | topBorder := sdl.Rect{ 67 | X: 0, 68 | Y: 0, 69 | W: screenW, 70 | H: borderH, 71 | } 72 | r.FillRect(&topBorder) 73 | bottomBorder := sdl.Rect{ 74 | X: 0, 75 | Y: borderH + height, 76 | W: screenW, 77 | H: borderH, 78 | } 79 | r.FillRect(&bottomBorder) 80 | leftBorder := sdl.Rect{ 81 | X: 0, 82 | Y: borderH, 83 | W: borderW, 84 | H: height, 85 | } 86 | r.FillRect(&leftBorder) 87 | rightBorder := sdl.Rect{ 88 | X: borderW + width, 89 | Y: borderH, 90 | W: borderW, 91 | H: height, 92 | } 93 | r.FillRect(&rightBorder) 94 | } 95 | 96 | func (v *VIC) drawBackground(r *sdl.Renderer) { 97 | c := Palette[v.BgColor&0x0f] 98 | r.SetDrawColor(c.R, c.G, c.B, c.A) 99 | background := sdl.Rect{ 100 | X: borderW, 101 | Y: borderH, 102 | W: width, 103 | H: height, 104 | } 105 | r.FillRect(&background) 106 | } 107 | 108 | func (v *VIC) drawCharacters(r *sdl.Renderer) { 109 | addrScreenMem := 0x0400 110 | addrColorMem := 0xd800 111 | baseX := 0 112 | baseY := 0 113 | for baseY < height { 114 | ch := v.mem.Read(addrScreenMem) 115 | clr := Palette[v.mem.Read(addrColorMem)&0x0f] 116 | v.charSheet.Texture.SetColorMod(clr.R, clr.G, clr.B) 117 | chx := int32(ch) % charSheetW * 8 118 | chy := int32(ch) / charSheetW * 8 119 | src := sdl.Rect{X: chx, Y: chy, W: 8, H: 8} 120 | dest := sdl.Rect{ 121 | X: int32(baseX + borderW), 122 | Y: int32(baseY + borderH), 123 | W: 8, 124 | H: 8, 125 | } 126 | r.Copy(v.charSheet.Texture, &src, &dest) 127 | addrScreenMem++ 128 | addrColorMem++ 129 | baseX += 8 130 | if baseX >= width { 131 | baseX = 0 132 | baseY += 8 133 | } 134 | } 135 | } 136 | 137 | var ( 138 | Palette = []color.RGBA{ 139 | color.RGBA{0x00, 0x00, 0x00, 0xff}, // black 140 | color.RGBA{0xff, 0xff, 0xff, 0xff}, // white 141 | color.RGBA{0x88, 0x00, 0x00, 0xff}, // red 142 | color.RGBA{0xaa, 0xff, 0xee, 0xff}, // cyan 143 | color.RGBA{0xcc, 0x44, 0xcc, 0xff}, // purple 144 | color.RGBA{0x00, 0xcc, 0x55, 0xff}, // green 145 | color.RGBA{0x00, 0x00, 0xaa, 0xff}, // blue 146 | color.RGBA{0xee, 0xee, 0x77, 0xff}, // yellow 147 | color.RGBA{0xdd, 0x88, 0x55, 0xff}, // orange 148 | color.RGBA{0x66, 0x44, 0x00, 0xff}, // brown 149 | color.RGBA{0xff, 0x77, 0x77, 0xff}, // light red 150 | color.RGBA{0x33, 0x33, 0x33, 0xff}, // dark gray 151 | color.RGBA{0x77, 0x77, 0x77, 0xff}, // gray 152 | color.RGBA{0xaa, 0xff, 0x66, 0xff}, // light green 153 | color.RGBA{0x00, 0x88, 0xff, 0xff}, // light blue 154 | color.RGBA{0xbb, 0xbb, 0xbb, 0xff}, // light gray 155 | } 156 | ) 157 | 158 | func CharGen(r *sdl.Renderer, data []uint8) (rcs.TileSheet, error) { 159 | tileW, tileH := int32(8), int32(8) 160 | texW := tileW * 32 161 | texH := tileH * 16 162 | t, err := r.CreateTexture(sdl.PIXELFORMAT_RGBA8888, 163 | sdl.TEXTUREACCESS_TARGET, texW, texH) 164 | if err != nil { 165 | return rcs.TileSheet{}, err 166 | } 167 | r.SetRenderTarget(t) 168 | r.SetDrawColor(0, 0, 0, 0) 169 | r.FillRect(nil) 170 | r.SetDrawColor(0xff, 0xff, 0xff, 0xff) 171 | baseX := int32(0) 172 | baseY := int32(0) 173 | addr := 0 174 | for baseY < texH { 175 | for y := baseY; y < baseY+8; y++ { 176 | line := data[addr] 177 | addr++ 178 | for x := baseX; x < baseX+8; x++ { 179 | bit := line & 0x80 180 | line = line << 1 181 | if bit != 0 { 182 | r.DrawPoint(x, y) 183 | } 184 | } 185 | } 186 | baseX += 8 187 | if baseX >= texW { 188 | baseX = 0 189 | baseY += 8 190 | } 191 | } 192 | t.SetBlendMode(sdl.BLENDMODE_BLEND) 193 | r.SetRenderTarget(nil) 194 | return rcs.TileSheet{ 195 | TextureW: texW, 196 | TextureH: texH, 197 | TileW: tileW, 198 | TileH: tileH, 199 | Texture: t, 200 | }, nil 201 | } 202 | -------------------------------------------------------------------------------- /system/c128/c128.go: -------------------------------------------------------------------------------- 1 | // Package c128 is the Commodore 128. 2 | package c128 3 | 4 | import ( 5 | "github.com/blackchip-org/retro-cs/config" 6 | "github.com/blackchip-org/retro-cs/rcs" 7 | "github.com/blackchip-org/retro-cs/rcs/cbm" 8 | "github.com/blackchip-org/retro-cs/rcs/cbm/petscii" 9 | "github.com/blackchip-org/retro-cs/rcs/m6502" 10 | ) 11 | 12 | type System struct { 13 | cpu *m6502.CPU 14 | mem *rcs.Memory 15 | mmu *MMU 16 | screen rcs.Screen 17 | vdc *VDC 18 | vic *cbm.VIC 19 | 20 | BasicLo []uint8 21 | BasicHi []uint8 22 | CharGen []uint8 23 | Kernal []uint8 24 | RAM0 []uint8 25 | RAM1 []uint8 26 | IORAM []uint8 27 | IO *rcs.Memory 28 | } 29 | 30 | func New(ctx rcs.SDLContext) (*rcs.Mach, error) { 31 | s := &System{} 32 | roms, err := rcs.LoadROMs(config.DataDir, SystemROM) 33 | if err != nil { 34 | return nil, err 35 | } 36 | s.mem = rcs.NewMemory(256, 0x10000) 37 | s.BasicLo = roms["basiclo"] 38 | s.BasicHi = roms["basichi"] 39 | s.CharGen = roms["chargen"] 40 | s.Kernal = roms["kernal"] 41 | s.RAM0 = make([]uint8, 0x10000, 0x10000) 42 | s.RAM1 = make([]uint8, 0xc000, 0xc000) 43 | 44 | s.IORAM = make([]uint8, 0x1000, 0x1000) 45 | s.IO = rcs.NewMemory(1, 0x1000) 46 | s.IO.MapRAM(0, s.IORAM) 47 | 48 | s.mmu = NewMMU(s.mem) 49 | s.vdc = NewVDC() 50 | v, err := cbm.NewVIC(ctx.Renderer, s.mem, roms["chargen"]) 51 | if err != nil { 52 | return nil, err 53 | } 54 | s.vic = v 55 | s.screen = rcs.Screen{ 56 | W: v.W, 57 | H: v.H, 58 | Texture: v.Texture, 59 | ScanLineH: true, 60 | Draw: v.Draw, 61 | } 62 | 63 | // IO mappings 64 | s.IO.MapRW(0x020, &s.vic.BorderColor) 65 | s.IO.MapRW(0x021, &s.vic.BgColor) 66 | s.IO.MapLoad(0x500, s.mmu.ReadCR) 67 | s.IO.MapStore(0x500, s.mmu.WriteCR) 68 | // PCR 69 | for i := 0; i < 4; i++ { 70 | i := i 71 | s.IO.MapLoad(0x501+i, func() uint8 { return s.mmu.ReadPCR(i) }) 72 | s.IO.MapStore(0x501+i, func(v uint8) { s.mmu.WritePCR(i, v) }) 73 | } 74 | // HACK 75 | s.IO.MapLoad(0x505, func() uint8 { 76 | return (1 << 7) | (1 << 4) | (1 << 5) 77 | }) 78 | // HACK 79 | s.IO.MapLoad(0xd00, func() uint8 { 80 | return (1 << 7) | (1 << 6) 81 | }) 82 | 83 | s.IO.MapLoad(0x600, s.vdc.ReadStatus) 84 | s.IO.MapStore(0x600, s.vdc.WriteAddr) 85 | s.IO.MapLoad(0x601, s.vdc.ReadData) 86 | s.IO.MapStore(0x601, s.vdc.WriteData) 87 | 88 | // map banks 89 | for _, i := range usedBanks { 90 | s.mem.SetBank(i) 91 | cr := uint8(i) 92 | blockRAM := rcs.SliceBits(cr, 6, 7) 93 | blockC000 := rcs.SliceBits(cr, 4, 5) 94 | block8000 := rcs.SliceBits(cr, 2, 3) 95 | block4000 := rcs.SliceBits(cr, 1, 1) 96 | blockIO := rcs.SliceBits(cr, 0, 0) 97 | 98 | switch blockRAM { 99 | case 0: 100 | s.mem.MapRAM(0x0000, s.RAM0) 101 | case 1: 102 | s.mem.MapRAM(0x0000, s.RAM0) 103 | s.mem.MapRAM(0x0400, s.RAM1) 104 | // no block RAM 2 or 3. If set, accesses 0 and 1 instead. 105 | case 2: 106 | s.mem.MapRAM(0x0000, s.RAM0) 107 | case 3: 108 | s.mem.MapRAM(0x0000, s.RAM0) 109 | s.mem.MapRAM(0x0400, s.RAM1) 110 | } 111 | 112 | switch blockC000 { 113 | case 0: 114 | s.mem.MapROM(0xd000, s.CharGen) 115 | s.mem.MapROM(0xc000, s.Kernal) 116 | case 1: 117 | // internal function ROM 118 | case 2: 119 | // external function ROM 120 | case 3: 121 | // RAM 122 | } 123 | 124 | switch block8000 { 125 | case 0: 126 | s.mem.MapROM(0x8000, s.BasicHi) 127 | case 1: 128 | // internal function ROM 129 | case 2: 130 | // external function ROM 131 | case 3: 132 | // RAM 133 | } 134 | 135 | switch block4000 { 136 | case 0: 137 | s.mem.MapROM(0x4000, s.BasicLo) 138 | case 1: 139 | // RAM 140 | } 141 | 142 | switch blockIO { 143 | case 0: 144 | s.mem.Map(0xd000, s.IO) 145 | case 1: 146 | // RAM or ROM as selected by bits 4 and 5 147 | } 148 | 149 | s.mem.MapLoad(0xff00, s.mmu.ReadCR) 150 | s.mem.MapStore(0xff00, s.mmu.WriteCR) 151 | for i := 0; i < 4; i++ { 152 | i := i 153 | s.mem.MapLoad(0xff01+i, func() uint8 { return s.mmu.ReadLCR(i) }) 154 | s.mem.MapStore(0xff01+i, func(v uint8) { s.mmu.WriteLCR(i, v) }) 155 | } 156 | } 157 | s.mem.SetBank(0) // bank 15 158 | 159 | s.mem.Write(0xd011, 0xff) // HACK 160 | s.mem.Write(0xd011, (1 << 7)) 161 | s.mem.Write(0xd012, 0x09) // HACK: bcc on $8 162 | s.mem.Write(0xd600, 0xff) // HACK 163 | s.mem.Write(0xdc01, 0xff) // HACK: keyboard no press 164 | 165 | s.cpu = m6502.New(s.mem) 166 | mach := &rcs.Mach{ 167 | Sys: s, 168 | Comps: []rcs.Component{ 169 | rcs.NewComponent("cpu", "m6502", "mem", s.cpu), 170 | rcs.NewComponent("mem", "mem", "", s.mem), 171 | rcs.NewComponent("mmu", "c128/mmu", "", s.mmu), 172 | rcs.NewComponent("vdc", "c128/vdc", "", s.vdc), 173 | }, 174 | CharDecoders: map[string]rcs.CharDecoder{ 175 | "petscii": petscii.Decoder, 176 | "petscii-shifted": petscii.ShiftedDecoder, 177 | "screen": cbm.ScreenDecoder, 178 | "screen-shifted": cbm.ScreenShiftedDecoder, 179 | }, 180 | DefaultEncoding: "petscii", 181 | Ctx: ctx, 182 | VBlankFunc: func() { 183 | s.cpu.IRQ = true 184 | }, 185 | Screen: s.screen, 186 | } 187 | return mach, nil 188 | } 189 | 190 | var usedBanks = []int{ 191 | 0x3f, // bank 0 192 | 0x7f, // bank 1 193 | 0x01, // bank 14 194 | 0x00, // bank 15 195 | 196 | 0xc0, // on init 197 | 0x80, // on init 198 | 0x40, // on init 199 | 0x2a, // on init 200 | 0x16, // on init 201 | } 202 | -------------------------------------------------------------------------------- /gen/z80/fuse/fuse.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate go run . 4 | //go:generate go fmt ../../../rcs/z80/fuse_in_test.go 5 | //go:generate go fmt ../../../rcs/z80/fuse_expected_test.go 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "fmt" 11 | "html/template" 12 | "io/ioutil" 13 | "os" 14 | "path/filepath" 15 | "regexp" 16 | "strconv" 17 | "strings" 18 | 19 | "github.com/blackchip-org/retro-cs/config" 20 | ) 21 | 22 | var ( 23 | root = filepath.Join("..", "..", "..") 24 | targetDir = filepath.Join(root, "rcs", "z80") 25 | sourceDir = filepath.Join(config.ResourceDir(), "ext", "fuse") 26 | ) 27 | 28 | var out bytes.Buffer 29 | var whitespace = regexp.MustCompile(" +") 30 | 31 | func main() { 32 | out.WriteString("// Code generated by gen/z80/fuse/fuse.go. DO NOT EDIT.\n\n") 33 | out.WriteString("package z80\n") 34 | out.WriteString("var fuseIn = []fuseTest{\n") 35 | loadTests() 36 | out.WriteString("}\n\n") 37 | 38 | fileIn := filepath.Join(targetDir, "fuse_in_test.go") 39 | err := ioutil.WriteFile(fileIn, out.Bytes(), 0644) 40 | if err != nil { 41 | fatal("unable to save file", err) 42 | } 43 | out.Reset() 44 | 45 | out.WriteString("// Code generated by gen/z80/fuse/fuse.go. DO NOT EDIT.\n\n") 46 | out.WriteString("package z80\n") 47 | out.WriteString("var fuseExpected = map[string]fuseTest{\n") 48 | loadExpected() 49 | out.WriteString("}\n") 50 | 51 | fileExpected := filepath.Join(targetDir, "fuse_expected_test.go") 52 | err = ioutil.WriteFile(fileExpected, out.Bytes(), 0644) 53 | if err != nil { 54 | fatal("unable to save file", err) 55 | } 56 | 57 | } 58 | 59 | func loadTests() { 60 | testsIn := filepath.Join(sourceDir, "tests.in") 61 | file, err := os.Open(testsIn) 62 | if err != nil { 63 | fatal("unable to open", err) 64 | } 65 | defer file.Close() 66 | 67 | scanner := bufio.NewScanner(file) 68 | for scanner.Scan() { 69 | line := scanner.Text() 70 | if line == "" { 71 | continue 72 | } else { 73 | name := line 74 | scanner.Scan() 75 | parseTest(name, scanner) 76 | } 77 | } 78 | } 79 | 80 | func loadExpected() { 81 | testsExp := filepath.Join(sourceDir, "tests.expected") 82 | file, err := os.Open(testsExp) 83 | if err != nil { 84 | fatal("unable to open", err) 85 | } 86 | defer file.Close() 87 | 88 | scanner := bufio.NewScanner(file) 89 | for scanner.Scan() { 90 | line := scanner.Text() 91 | if line == "" { 92 | continue 93 | } else { 94 | name := line 95 | out.WriteString("\"" + name + "\": ") 96 | line = "" 97 | scanner.Scan() 98 | parseTest(name, scanner) 99 | } 100 | } 101 | } 102 | 103 | func parseTest(name string, scanner *bufio.Scanner) { 104 | t := make(map[string]string) 105 | t["name"] = name 106 | 107 | // Scan for events (on expected results) 108 | portReadsMap := map[string][]string{} 109 | portWritesMap := map[string]string{} 110 | 111 | for { 112 | line := scanner.Text() 113 | // If the line does not start with a space, there are 114 | // no more events 115 | if !strings.HasPrefix(line, " ") { 116 | break 117 | } 118 | line = whitespace.ReplaceAllString(line, " ") 119 | f := strings.Fields(line) 120 | if f[1] == "PR" { 121 | addr := f[2][2:4] 122 | value := fmt.Sprintf("0x%v", f[3]) 123 | values, ok := portReadsMap[addr] 124 | if !ok { 125 | portReadsMap[addr] = []string{"0x" + addr, value} 126 | } else { 127 | values = append(values, value) 128 | portReadsMap[addr] = values 129 | } 130 | } else if f[1] == "PW" { 131 | addr := f[2][2:4] 132 | value := f[3] 133 | portWritesMap[addr] = fmt.Sprintf( 134 | "[]int{0x%v, 0x%v},\n", addr, value) 135 | } 136 | scanner.Scan() 137 | } 138 | 139 | portWrites := "" 140 | for _, v := range portWritesMap { 141 | portWrites += v 142 | } 143 | 144 | portReads := "" 145 | for _, v := range portReadsMap { 146 | portReads += fmt.Sprintf("[]int{%v},\n", strings.Join(v, ",")) 147 | } 148 | 149 | f1 := strings.Fields(scanner.Text()) 150 | t["af"] = f1[0] 151 | t["bc"] = f1[1] 152 | t["de"] = f1[2] 153 | t["hl"] = f1[3] 154 | t["af1"] = f1[4] 155 | t["bc1"] = f1[5] 156 | t["de1"] = f1[6] 157 | t["hl1"] = f1[7] 158 | t["ix"] = f1[8] 159 | t["iy"] = f1[9] 160 | t["sp"] = f1[10] 161 | t["pc"] = f1[11] 162 | 163 | scanner.Scan() 164 | text2 := whitespace.ReplaceAllString(scanner.Text(), " ") 165 | f2 := strings.Fields(text2) 166 | t["i"] = f2[0] 167 | t["r"] = f2[1] 168 | t["iff1"] = f2[2] 169 | t["iff2"] = f2[3] 170 | t["im"] = f2[4] 171 | t["halt"] = f2[5] 172 | t["tstates"] = f2[6] 173 | 174 | t["memory"] = parseMemory(scanner) 175 | t["portReads"] = portReads 176 | t["portWrites"] = portWrites 177 | 178 | testTemplate.Execute(&out, t) 179 | } 180 | 181 | func parseMemory(scanner *bufio.Scanner) string { 182 | var tests bytes.Buffer 183 | for { 184 | scanner.Scan() 185 | line := strings.Fields(scanner.Text()) 186 | if len(line) == 0 || line[0] == "-1" { 187 | break 188 | } 189 | addr, _ := strconv.ParseUint(line[0], 16, 16) 190 | for i, value := range line[1 : len(line)-1] { 191 | tests.WriteString(fmt.Sprintf("[]int{0x%04x, 0x%v},\n", int(addr)+i, value)) 192 | } 193 | } 194 | return tests.String() 195 | } 196 | 197 | func fatal(message string, err error) { 198 | fmt.Printf("error: %v: %v\n", message, err) 199 | os.Exit(1) 200 | } 201 | 202 | var testTemplate = template.Must(template.New("").Parse(`fuseTest{ 203 | name: "{{.name}}", 204 | af: 0x{{.af}}, 205 | bc: 0x{{.bc}}, 206 | de: 0x{{.de}}, 207 | hl: 0x{{.hl}}, 208 | af1: 0x{{.af1}}, 209 | bc1: 0x{{.bc1}}, 210 | de1: 0x{{.de1}}, 211 | hl1: 0x{{.hl1}}, 212 | ix: 0x{{.ix}}, 213 | iy: 0x{{.iy}}, 214 | sp: 0x{{.sp}}, 215 | pc: 0x{{.pc}}, 216 | i: 0x{{.i}}, 217 | r: 0x{{.r}}, 218 | iff1: {{.iff1}}, 219 | iff2: {{.iff2}}, 220 | im: {{.im}}, 221 | halt: {{.halt}}, 222 | tstates: {{.tstates}}, 223 | memory: [][]int{ 224 | {{.memory}} 225 | }, 226 | portReads: [][]int{ 227 | {{.portReads}} 228 | }, 229 | portWrites: [][]int{ 230 | {{.portWrites}} 231 | }, 232 | }, 233 | `)) 234 | -------------------------------------------------------------------------------- /rcs/z80/addressing.go: -------------------------------------------------------------------------------- 1 | package z80 2 | 3 | func (c *CPU) storeNil(v uint8) {} 4 | 5 | func (c *CPU) storeIndImm(v uint8) { c.mem.Write(c.fetch2(), v) } 6 | func (c *CPU) store16IndImm(v int) { c.mem.WriteLE(c.fetch2(), v) } 7 | 8 | func (c *CPU) storeA(v uint8) { c.A = v } 9 | func (c *CPU) storeF(v uint8) { c.F = v } 10 | func (c *CPU) storeB(v uint8) { c.B = v } 11 | func (c *CPU) storeC(v uint8) { c.C = v } 12 | func (c *CPU) storeD(v uint8) { c.D = v } 13 | func (c *CPU) storeE(v uint8) { c.E = v } 14 | func (c *CPU) storeH(v uint8) { c.H = v } 15 | func (c *CPU) storeL(v uint8) { c.L = v } 16 | func (c *CPU) storeI(v uint8) { c.I = v } 17 | func (c *CPU) storeR(v uint8) { c.R = v } 18 | func (c *CPU) storeIXH(v uint8) { c.IXH = v } 19 | func (c *CPU) storeIXL(v uint8) { c.IXL = v } 20 | func (c *CPU) storeIYH(v uint8) { c.IYH = v } 21 | func (c *CPU) storeIYL(v uint8) { c.IYL = v } 22 | 23 | func (c *CPU) storeA1(v uint8) { c.A1 = v } 24 | func (c *CPU) storeF1(v uint8) { c.F1 = v } 25 | func (c *CPU) storeB1(v uint8) { c.B1 = v } 26 | func (c *CPU) storeC1(v uint8) { c.C1 = v } 27 | func (c *CPU) storeD1(v uint8) { c.D1 = v } 28 | func (c *CPU) storeE1(v uint8) { c.E1 = v } 29 | func (c *CPU) storeH1(v uint8) { c.H1 = v } 30 | func (c *CPU) storeL1(v uint8) { c.L1 = v } 31 | 32 | func (c *CPU) storeAF(v int) { c.A, c.F = uint8(v>>8), uint8(v) } 33 | func (c *CPU) storeBC(v int) { c.B, c.C = uint8(v>>8), uint8(v) } 34 | func (c *CPU) storeDE(v int) { c.D, c.E = uint8(v>>8), uint8(v) } 35 | func (c *CPU) storeHL(v int) { c.H, c.L = uint8(v>>8), uint8(v) } 36 | func (c *CPU) storeSP(v int) { c.SP = uint16(v) } 37 | func (c *CPU) storeIX(v int) { c.IXH, c.IXL = uint8(v>>8), uint8(v) } 38 | func (c *CPU) storeIY(v int) { c.IYH, c.IYL = uint8(v>>8), uint8(v) } 39 | 40 | func (c *CPU) store16IndSP(v int) { c.mem.WriteLE(int(c.SP), v) } 41 | 42 | func (c *CPU) storeAF1(v int) { c.A1, c.F1 = uint8(v>>8), uint8(v) } 43 | func (c *CPU) storeBC1(v int) { c.B1, c.C1 = uint8(v>>8), uint8(v) } 44 | func (c *CPU) storeDE1(v int) { c.D1, c.E1 = uint8(v>>8), uint8(v) } 45 | func (c *CPU) storeHL1(v int) { c.H1, c.L1 = uint8(v>>8), uint8(v) } 46 | 47 | func (c *CPU) storeIndHL(v uint8) { c.mem.Write(int(c.H)<<8|int(c.L), v) } 48 | 49 | func (c *CPU) storeIndBC(v uint8) { c.mem.Write(int(c.B)<<8|int(c.C), v) } 50 | func (c *CPU) storeIndDE(v uint8) { c.mem.Write(int(c.D)<<8|int(c.E), v) } 51 | 52 | func (c *CPU) loadZero() uint8 { return 0 } 53 | func (c *CPU) loadImm() uint8 { return c.fetch() } 54 | func (c *CPU) loadImm16() int { return c.fetch2() } 55 | func (c *CPU) loadIndImm() uint8 { return c.mem.Read(c.fetch2()) } 56 | func (c *CPU) load16IndImm() int { return c.mem.ReadLE(c.fetch2()) } 57 | 58 | func (c *CPU) loadA() uint8 { return c.A } 59 | func (c *CPU) loadF() uint8 { return c.F } 60 | func (c *CPU) loadB() uint8 { return c.B } 61 | func (c *CPU) loadC() uint8 { return c.C } 62 | func (c *CPU) loadD() uint8 { return c.D } 63 | func (c *CPU) loadE() uint8 { return c.E } 64 | func (c *CPU) loadH() uint8 { return c.H } 65 | func (c *CPU) loadL() uint8 { return c.L } 66 | func (c *CPU) loadI() uint8 { return c.I } 67 | func (c *CPU) loadR() uint8 { return c.R } 68 | func (c *CPU) loadIXL() uint8 { return c.IXL } 69 | func (c *CPU) loadIXH() uint8 { return c.IXH } 70 | func (c *CPU) loadIYL() uint8 { return c.IYL } 71 | func (c *CPU) loadIYH() uint8 { return c.IYH } 72 | func (c *CPU) loadIndC() uint8 { return c.Ports.Read(int(c.C)) } 73 | 74 | func (c *CPU) loadA1() uint8 { return c.A1 } 75 | func (c *CPU) loadF1() uint8 { return c.F1 } 76 | func (c *CPU) loadB1() uint8 { return c.B1 } 77 | func (c *CPU) loadC1() uint8 { return c.C1 } 78 | func (c *CPU) loadD1() uint8 { return c.D1 } 79 | func (c *CPU) loadE1() uint8 { return c.E1 } 80 | func (c *CPU) loadH1() uint8 { return c.H1 } 81 | func (c *CPU) loadL1() uint8 { return c.L1 } 82 | 83 | func (c *CPU) loadAF() int { return int(c.A)<<8 | int(c.F) } 84 | func (c *CPU) loadBC() int { return int(c.B)<<8 | int(c.C) } 85 | func (c *CPU) loadDE() int { return int(c.D)<<8 | int(c.E) } 86 | func (c *CPU) loadHL() int { return int(c.H)<<8 | int(c.L) } 87 | func (c *CPU) loadSP() int { return int(c.SP) } 88 | func (c *CPU) loadIX() int { return int(c.IXH)<<8 | int(c.IXL) } 89 | func (c *CPU) loadIY() int { return int(c.IYH)<<8 | int(c.IYL) } 90 | func (c *CPU) load16IndSP() int { return c.mem.ReadLE(int(c.SP)) } 91 | 92 | func (c *CPU) loadAF1() int { return int(c.A1)<<8 | int(c.F1) } 93 | func (c *CPU) loadBC1() int { return int(c.B1)<<8 | int(c.C1) } 94 | func (c *CPU) loadDE1() int { return int(c.D1)<<8 | int(c.E1) } 95 | func (c *CPU) loadHL1() int { return int(c.H1)<<8 | int(c.L1) } 96 | 97 | func (c *CPU) loadIndHL() uint8 { return c.mem.Read(int(c.H)<<8 | int(c.L)) } 98 | 99 | func (c *CPU) loadIndBC() uint8 { return c.mem.Read(int(c.B)<<8 | int(c.C)) } 100 | func (c *CPU) loadIndDE() uint8 { return c.mem.Read(int(c.D)<<8 | int(c.E)) } 101 | 102 | func (c *CPU) loadIndIX() uint8 { 103 | ix := int(c.IXH)<<8 | int(c.IXL) 104 | c.iaddr = ix + int(int8(c.delta)) 105 | return c.mem.Read(c.iaddr) 106 | } 107 | 108 | func (c *CPU) loadIndIY() uint8 { 109 | iy := int(c.IYH)<<8 | int(c.IYL) 110 | c.iaddr = iy + int(int8(c.delta)) 111 | return c.mem.Read(c.iaddr) 112 | } 113 | 114 | func (c *CPU) storeIndIX(v uint8) { 115 | ix := int(c.IXH)<<8 | int(c.IXL) 116 | addr := ix + int(int8(c.delta)) 117 | c.mem.Write(addr, v) 118 | } 119 | 120 | func (c *CPU) storeIndIY(v uint8) { 121 | iy := int(c.IYH)<<8 | int(c.IYL) 122 | addr := iy + int(int8(c.delta)) 123 | c.mem.Write(addr, v) 124 | } 125 | 126 | func (c *CPU) storeLastInd(v uint8) { 127 | c.mem.Write(c.iaddr, v) 128 | } 129 | 130 | func (c *CPU) outIndImm(v uint8) { 131 | addr := int(c.fetch()) 132 | c.Ports.Write(addr, v) 133 | } 134 | 135 | func (c *CPU) inIndImm() uint8 { 136 | addr := int(c.fetch()) 137 | return c.Ports.Read(addr) 138 | } 139 | 140 | func (c *CPU) outIndC(v uint8) { 141 | c.Ports.Write(int(c.C), v) 142 | } 143 | 144 | func (c *CPU) inIndC() uint8 { 145 | return c.Ports.Read(int(c.C)) 146 | } 147 | -------------------------------------------------------------------------------- /rcs/m6502/dasm.go: -------------------------------------------------------------------------------- 1 | package m6502 2 | 3 | type mode int 4 | 5 | const ( 6 | absolute mode = iota 7 | absoluteX 8 | absoluteY 9 | accumulator 10 | immediate 11 | implied 12 | indirect 13 | indirectX 14 | indirectY 15 | relative 16 | zeroPage 17 | zeroPageX 18 | zeroPageY 19 | ) 20 | 21 | type op struct { 22 | inst string 23 | mode mode 24 | } 25 | 26 | var operandLengths = map[mode]int{ 27 | absolute: 2, 28 | absoluteX: 2, 29 | absoluteY: 2, 30 | accumulator: 0, 31 | immediate: 1, 32 | implied: 0, 33 | indirect: 2, 34 | indirectX: 1, 35 | indirectY: 1, 36 | relative: 1, 37 | zeroPage: 1, 38 | zeroPageX: 1, 39 | zeroPageY: 1, 40 | } 41 | 42 | var operandFormats = map[mode]string{ 43 | absolute: "$%04x", 44 | absoluteX: "$%04x,x", 45 | absoluteY: "$%04x,y", 46 | accumulator: "a", 47 | immediate: "#$%02x", 48 | indirect: "($%04x)", 49 | indirectX: "($%02x,x)", 50 | indirectY: "($%02x),y", 51 | relative: "$%04x", 52 | zeroPage: "$%02x", 53 | zeroPageX: "$%02x,x", 54 | zeroPageY: "$%02x,y", 55 | } 56 | 57 | var dasmTable = map[uint8]op{ 58 | 0x00: op{"brk", implied}, 59 | 0x01: op{"ora", indirectX}, 60 | 0x05: op{"ora", zeroPage}, 61 | 0x06: op{"asl", zeroPage}, 62 | 0x08: op{"php", implied}, 63 | 0x09: op{"ora", immediate}, 64 | 0x0a: op{"asl", accumulator}, 65 | 0x0d: op{"ora", absolute}, 66 | 0x0e: op{"asl", absolute}, 67 | 68 | 0x10: op{"bpl", relative}, 69 | 0x11: op{"ora", indirectY}, 70 | 0x15: op{"ora", zeroPageX}, 71 | 0x16: op{"asl", zeroPageX}, 72 | 0x18: op{"clc", implied}, 73 | 0x19: op{"ora", absoluteY}, 74 | 0x1d: op{"ora", absoluteX}, 75 | 0x1e: op{"asl", absoluteX}, 76 | 77 | 0x20: op{"jsr", absolute}, 78 | 0x21: op{"and", indirectX}, 79 | 0x24: op{"bit", zeroPage}, 80 | 0x25: op{"and", zeroPage}, 81 | 0x26: op{"rol", zeroPage}, 82 | 0x28: op{"plp", implied}, 83 | 0x29: op{"and", immediate}, 84 | 0x2a: op{"rol", accumulator}, 85 | 0x2c: op{"bit", absolute}, 86 | 0x2d: op{"and", absolute}, 87 | 0x2e: op{"rol", absolute}, 88 | 89 | 0x30: op{"bmi", relative}, 90 | 0x31: op{"and", indirectY}, 91 | 0x35: op{"and", zeroPageX}, 92 | 0x36: op{"rol", zeroPageX}, 93 | 0x38: op{"sec", implied}, 94 | 0x39: op{"and", absoluteY}, 95 | 0x3d: op{"and", absoluteX}, 96 | 0x3e: op{"rol", absoluteX}, 97 | 98 | 0x40: op{"rti", implied}, 99 | 0x41: op{"eor", indirectX}, 100 | 0x45: op{"eor", zeroPage}, 101 | 0x46: op{"lsr", zeroPage}, 102 | 0x48: op{"pha", implied}, 103 | 0x49: op{"eor", immediate}, 104 | 0x4a: op{"lsr", accumulator}, 105 | 0x4c: op{"jmp", absolute}, 106 | 0x4d: op{"eor", absolute}, 107 | 0x4e: op{"lsr", absolute}, 108 | 109 | 0x50: op{"bvc", relative}, 110 | 0x51: op{"eor", indirectY}, 111 | 0x55: op{"eor", zeroPageX}, 112 | 0x56: op{"lsr", zeroPageX}, 113 | 0x58: op{"cli", implied}, 114 | 0x59: op{"eor", absoluteY}, 115 | 0x5d: op{"eor", absoluteX}, 116 | 0x5e: op{"lsr", absoluteX}, 117 | 118 | 0x60: op{"rts", implied}, 119 | 0x61: op{"adc", indirectX}, 120 | 0x65: op{"adc", zeroPage}, 121 | 0x66: op{"ror", zeroPage}, 122 | 0x68: op{"pla", implied}, 123 | 0x69: op{"adc", immediate}, 124 | 0x6a: op{"ror", accumulator}, 125 | 0x6c: op{"jmp", indirect}, 126 | 0x6d: op{"adc", absolute}, 127 | 0x6e: op{"ror", absolute}, 128 | 129 | 0x70: op{"bvs", relative}, 130 | 0x71: op{"adc", indirectY}, 131 | 0x75: op{"adc", zeroPageX}, 132 | 0x76: op{"ror", zeroPageX}, 133 | 0x78: op{"sei", implied}, 134 | 0x79: op{"adc", absoluteY}, 135 | 0x7d: op{"adc", absoluteX}, 136 | 0x7e: op{"ror", absoluteX}, 137 | 138 | 0x81: op{"sta", indirectX}, 139 | 0x84: op{"sty", zeroPage}, 140 | 0x85: op{"sta", zeroPage}, 141 | 0x86: op{"stx", zeroPage}, 142 | 0x88: op{"dey", implied}, 143 | 0x8a: op{"txa", implied}, 144 | 0x8c: op{"sty", absolute}, 145 | 0x8d: op{"sta", absolute}, 146 | 0x8e: op{"stx", absolute}, 147 | 148 | 0x90: op{"bcc", relative}, 149 | 0x91: op{"sta", indirectY}, 150 | 0x94: op{"sty", zeroPageX}, 151 | 0x95: op{"sta", zeroPageX}, 152 | 0x96: op{"stx", zeroPageY}, 153 | 0x98: op{"tya", implied}, 154 | 0x99: op{"sta", absoluteY}, 155 | 0x9a: op{"txs", implied}, 156 | 0x9d: op{"sta", absoluteX}, 157 | 158 | 0xa0: op{"ldy", immediate}, 159 | 0xa1: op{"lda", indirectX}, 160 | 0xa2: op{"ldx", immediate}, 161 | 0xa4: op{"ldy", zeroPage}, 162 | 0xa5: op{"lda", zeroPage}, 163 | 0xa6: op{"ldx", zeroPage}, 164 | 0xa8: op{"tay", implied}, 165 | 0xa9: op{"lda", immediate}, 166 | 0xaa: op{"tax", implied}, 167 | 0xac: op{"ldy", absolute}, 168 | 0xad: op{"lda", absolute}, 169 | 0xae: op{"ldx", absolute}, 170 | 171 | 0xb0: op{"bcs", relative}, 172 | 0xb1: op{"lda", indirectY}, 173 | 0xb4: op{"ldy", zeroPageX}, 174 | 0xb5: op{"lda", zeroPageX}, 175 | 0xb6: op{"ldx", zeroPageY}, 176 | 0xb8: op{"clv", implied}, 177 | 0xb9: op{"lda", absoluteY}, 178 | 0xba: op{"tsx", implied}, 179 | 0xbd: op{"lda", absoluteX}, 180 | 0xbc: op{"ldy", absoluteX}, 181 | 0xbe: op{"ldx", absoluteY}, 182 | 183 | 0xc0: op{"cpy", immediate}, 184 | 0xc1: op{"cmp", indirectX}, 185 | 0xc4: op{"cpy", zeroPage}, 186 | 0xc5: op{"cmp", zeroPage}, 187 | 0xc6: op{"dec", zeroPage}, 188 | 0xc8: op{"iny", implied}, 189 | 0xc9: op{"cmp", immediate}, 190 | 0xca: op{"dex", implied}, 191 | 0xcc: op{"cpy", absolute}, 192 | 0xcd: op{"cmp", absolute}, 193 | 0xce: op{"dec", absolute}, 194 | 195 | 0xd0: op{"bne", relative}, 196 | 0xd1: op{"cmp", indirectY}, 197 | 0xd5: op{"cmp", zeroPageX}, 198 | 0xd6: op{"dec", zeroPageX}, 199 | 0xd8: op{"cld", implied}, 200 | 0xd9: op{"cmp", absoluteY}, 201 | 0xdd: op{"cmp", absoluteX}, 202 | 0xde: op{"dec", absoluteX}, 203 | 204 | 0xe0: op{"cpx", immediate}, 205 | 0xe1: op{"sbc", indirectX}, 206 | 0xe4: op{"cpx", zeroPage}, 207 | 0xe5: op{"sbc", zeroPage}, 208 | 0xe6: op{"inc", zeroPage}, 209 | 0xe8: op{"inx", implied}, 210 | 0xe9: op{"sbc", immediate}, 211 | 0xea: op{"nop", implied}, 212 | 0xec: op{"cpx", absolute}, 213 | 0xed: op{"sbc", absolute}, 214 | 0xee: op{"inc", absolute}, 215 | 216 | 0xf0: op{"beq", relative}, 217 | 0xf1: op{"sbc", indirectY}, 218 | 0xf5: op{"sbc", zeroPageX}, 219 | 0xf6: op{"inc", zeroPageX}, 220 | 0xf8: op{"sed", implied}, 221 | 0xf9: op{"sbc", absoluteY}, 222 | 0xfd: op{"sbc", absoluteX}, 223 | 0xfe: op{"inc", absoluteX}, 224 | } 225 | -------------------------------------------------------------------------------- /rcs/rcs.go: -------------------------------------------------------------------------------- 1 | // Package rcs contains the common components used to create retro-computing 2 | // systems. 3 | package rcs 4 | 5 | import ( 6 | "encoding/gob" 7 | "fmt" 8 | "io" 9 | "math/bits" 10 | "strconv" 11 | 12 | "github.com/veandco/go-sdl2/sdl" 13 | ) 14 | 15 | func X(v int) string { 16 | return fmt.Sprintf("$%x", v) 17 | } 18 | 19 | func X8(v uint8) string { 20 | return fmt.Sprintf("$%02x", v) 21 | } 22 | 23 | func X16(v uint16) string { 24 | return fmt.Sprintf("$%04x", v) 25 | } 26 | 27 | func B(v int) string { 28 | return "%" + formatBits("%b", v) 29 | } 30 | 31 | func B8(v uint8) string { 32 | return "%" + formatBits("%08b", int(v)) 33 | } 34 | 35 | func B16(v uint16) string { 36 | return "%" + formatBits("%016b", int(v)) 37 | } 38 | 39 | func formatBits(f string, v int) string { 40 | in := fmt.Sprintf(f, v) 41 | out := "" 42 | bit := 0 43 | for i := len(in) - 1; i >= 0; i-- { 44 | if bit != 0 { 45 | switch { 46 | case bit%8 == 0: 47 | out = string(":") + out 48 | case bit%4 == 0: 49 | out = string('.') + out 50 | } 51 | } 52 | out = string(in[i]) + out 53 | bit++ 54 | } 55 | return out 56 | } 57 | 58 | // Load8 is a function which loads an unsigned 8-bit value 59 | type Load8 func() uint8 60 | 61 | // Store8 is a function which stores an unsiged 8-bit value 62 | type Store8 func(uint8) 63 | 64 | // Load is a function which loads an integer value 65 | type Load func() int 66 | 67 | // Store is a function which stores an integer value 68 | type Store func(int) 69 | 70 | // FromBCD converts a binary-coded decimal to an integer value. 71 | func FromBCD(v uint8) uint8 { 72 | low := v & 0x0f 73 | high := v >> 4 74 | return high*10 + low 75 | } 76 | 77 | // ToBCD converts an integer value to a binary-coded decimal. 78 | func ToBCD(v uint8) uint8 { 79 | low := v % 10 80 | high := (v / 10) % 10 81 | return high<<4 | low 82 | } 83 | 84 | // ParseBits parses the base-2 string value s to a uint8. Panics if s is not 85 | // a valid number. Use strconv.ParseUint for input which may be malformed. 86 | func ParseBits(s string) uint8 { 87 | value, err := strconv.ParseUint(s, 2, 8) 88 | if err != nil { 89 | panic(err) 90 | } 91 | return uint8(value) 92 | } 93 | 94 | // SliceBits extracts a sequence of bits in value from bit lo to bit hi, 95 | // inclusive. 96 | func SliceBits(value uint8, lo int, hi int) uint8 { 97 | value = value >> uint(lo) 98 | bits := uint(hi - lo + 1) 99 | mask := uint8(1)<= 0xff-in1 { 112 | carryOut = 1 113 | } 114 | out = in0 + in1 + 1 115 | } else { 116 | if in0 > 0xff-in1 { 117 | carryOut = 1 118 | } 119 | out = in0 + in1 120 | } 121 | carryIns := out ^ in0 ^ in1 122 | 123 | c = carryOut != 0 124 | h = carryIns&(1<<4) != 0 125 | v = (carryIns>>7)^carryOut != 0 126 | return 127 | } 128 | 129 | // Sub performs subtraction of in1 from in0 with a borrow and returns the result 130 | // along with the new values for the borrow, half-borrow, and overflow 131 | // flags. 132 | func Sub(in0, in1 uint8, borrow bool) (out uint8, fc, fh, fv bool) { 133 | fc = !borrow 134 | out, fc, fh, fv = Add(in0, ^in1, fc) 135 | fc = !fc 136 | fh = !fh 137 | return 138 | } 139 | 140 | // Parity returns true if there are an even number of bits set in the 141 | // given value. 142 | func Parity(v uint8) bool { 143 | p := bits.OnesCount8(v) 144 | return p == 0 || p == 2 || p == 4 || p == 6 || p == 8 145 | } 146 | 147 | // BitPlane4 returns the nth 2-bit value stored in 4-bit planes found in v. 148 | // If n is 0, returns bits 0 and 4. If n is 1, returns bits 1 and 5, etc. 149 | func BitPlane4(v uint8, n int) uint8 { 150 | result := 0 151 | for i, start := range []int{0, 4} { 152 | checkBit := uint8(1) << uint(start+n) 153 | if v&checkBit != 0 { 154 | result += 1 << uint(i) 155 | } 156 | } 157 | return uint8(result) 158 | } 159 | 160 | // CharDecoder converts a byte value to a unicode character and indicates 161 | // if this character is considered to be printable. 162 | type CharDecoder func(uint8) (ch rune, printable bool) 163 | 164 | // ASCIIDecoder is a pass through of byte values to unicode characters. 165 | // Values 32 to 128 are considered printable. 166 | var ASCIIDecoder = func(code uint8) (rune, bool) { 167 | printable := code >= 32 && code < 128 168 | return rune(code), printable 169 | } 170 | 171 | const MaxGameControllers = 4 172 | 173 | // SDLContext contains the window for rendering and the audio specs 174 | // available for use. 175 | type SDLContext struct { 176 | Window *sdl.Window 177 | Renderer *sdl.Renderer 178 | AudioSpec sdl.AudioSpec 179 | GameControllers [MaxGameControllers]*sdl.GameController 180 | } 181 | 182 | type Encoder struct { 183 | Err error 184 | enc *gob.Encoder 185 | } 186 | 187 | func NewEncoder(w io.Writer) *Encoder { 188 | return &Encoder{ 189 | enc: gob.NewEncoder(w), 190 | } 191 | } 192 | 193 | func (e *Encoder) Encode(v interface{}) { 194 | if e.Err != nil { 195 | return 196 | } 197 | e.Err = e.enc.Encode(v) 198 | } 199 | 200 | type Decoder struct { 201 | Err error 202 | dec *gob.Decoder 203 | } 204 | 205 | func NewDecoder(r io.Reader) *Decoder { 206 | return &Decoder{ 207 | dec: gob.NewDecoder(r), 208 | } 209 | } 210 | 211 | func (d *Decoder) Decode(v interface{}) { 212 | if d.Err != nil { 213 | return 214 | } 215 | d.Err = d.dec.Decode(v) 216 | } 217 | 218 | type Saver interface { 219 | Save(*Encoder) 220 | } 221 | 222 | type Loader interface { 223 | Load(*Decoder) 224 | } 225 | 226 | type Component struct { 227 | Name string 228 | Module string 229 | Parent string 230 | C interface{} 231 | } 232 | 233 | func NewComponent(name string, mod string, parent string, c interface{}) Component { 234 | return Component{Name: name, Module: mod, Parent: parent, C: c} 235 | } 236 | 237 | type FlagRW struct { 238 | R bool // Read 239 | W bool // Write 240 | } 241 | -------------------------------------------------------------------------------- /rcs/m6502/cpu.go: -------------------------------------------------------------------------------- 1 | package m6502 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/blackchip-org/retro-cs/rcs" 8 | ) 9 | 10 | // http://www.6502.org/tutorials/6502opcodes.html 11 | 12 | const ( 13 | addrStack = 0x0100 // starting address of the stack 14 | addrReset = 0xfffc // reset vector 15 | ) 16 | 17 | // CPU is the MOS Technology 6502 series processor. 18 | type CPU struct { 19 | Name string 20 | pc uint16 // program counter 21 | A uint8 // accumulator 22 | X uint8 // x register 23 | Y uint8 // y register 24 | SP uint8 // stack pointer 25 | SR uint8 // status register 26 | 27 | IRQ bool // interrupt request 28 | 29 | BreakFunc func() 30 | WatchIRQ bool 31 | WatchBRK bool 32 | WatchStack bool 33 | 34 | mem *rcs.Memory // CPU's view into memory 35 | ops map[uint8]func(*CPU) // opcode table 36 | addrLoad int // memory address where the last value was loaded from 37 | pageCross bool // if set, add a one cycle penalty for crossing a page boundary 38 | } 39 | 40 | const ( 41 | // FlagC is the carry flag 42 | FlagC = uint8(1 << 0) 43 | 44 | // FlagZ is the zero flag 45 | FlagZ = uint8(1 << 1) 46 | 47 | // FlagI is the interrupt disable flag 48 | FlagI = uint8(1 << 2) 49 | 50 | // FlagD is the decimal mode flag 51 | FlagD = uint8(1 << 3) 52 | 53 | // FlagB is the break flag 54 | FlagB = uint8(1 << 4) 55 | 56 | // Flag5 is the 5th bit flag, always on 57 | Flag5 = uint8(1 << 5) 58 | 59 | // FlagV is the overflow flag 60 | FlagV = uint8(1 << 6) 61 | 62 | // FlagN is the signed/negative flag 63 | FlagN = uint8(1 << 7) 64 | ) 65 | 66 | // New creates a new CPU with a view of the provided memory. 67 | func New(mem *rcs.Memory) *CPU { 68 | return &CPU{ 69 | Name: "cpu", 70 | mem: mem, 71 | pc: uint16(mem.ReadLE(addrReset) - 1), // reset vector 72 | ops: opcodes, 73 | } 74 | } 75 | 76 | // Next executes the next instruction. 77 | func (c *CPU) Next() { 78 | here := uint16(c.PC() + 1) 79 | c.pageCross = false 80 | opcode := c.fetch() 81 | execute, ok := c.ops[opcode] 82 | if !ok { 83 | log.Printf("(!) %v: illegal instruction %v, pc %v", c.Name, rcs.X8(opcode), rcs.X16(here)) 84 | return 85 | } 86 | execute(c) 87 | c.SR |= Flag5 88 | c.SR &^= FlagB 89 | 90 | if c.IRQ { 91 | c.IRQ = false 92 | if c.SR&FlagI == 0 { 93 | c.irqAck(false) 94 | } 95 | } 96 | } 97 | 98 | // interrupt handler 99 | func (c *CPU) irqAck(brk bool) { 100 | here := uint16(c.pc) 101 | // http://www.6502.org/tutorials/6502opcodes.html#RTI 102 | // Note that unlike RTS, the return address on the stack is the 103 | // actual address rather than the address-1. 104 | ret := c.pc + 1 105 | vector := uint16(c.mem.ReadLE(0xfffe)) 106 | if !brk && c.WatchIRQ { 107 | log.Printf("%v: irq, vector %v, return %v", c.Name, rcs.X16(vector), rcs.X16(ret)) 108 | } 109 | if brk && c.WatchBRK { 110 | log.Printf("%v: brk, vector %v, pc %v", c.Name, rcs.X16(vector), rcs.X16(here)) 111 | } 112 | 113 | c.push2(ret) 114 | sr := c.SR | Flag5 115 | if brk { 116 | sr |= FlagB 117 | } 118 | c.push(sr) 119 | c.SR |= FlagI 120 | c.pc = vector - 1 121 | } 122 | 123 | // PC returns the value of the program counter. 124 | func (c *CPU) PC() int { 125 | return int(c.pc) 126 | } 127 | 128 | // SetPC sets the value of the program counter. 129 | func (c *CPU) SetPC(addr int) { 130 | c.pc = uint16(addr) 131 | } 132 | 133 | // Offset is the value to be added to the program counter to get the 134 | // address of the next instruction. The value is 1 for this CPU since 135 | // the program counter is incremented before fetching the opcode. 136 | func (c *CPU) Offset() int { 137 | return 1 138 | } 139 | 140 | // Memory is the memory that can been seen by this CPU. 141 | func (c *CPU) Memory() *rcs.Memory { 142 | return c.mem 143 | } 144 | 145 | // NewDisassembler creates a disassembler that can handle 6502 machine 146 | // code. 147 | func (c *CPU) NewDisassembler() *rcs.Disassembler { 148 | dasm := rcs.NewDisassembler(c.mem, Reader, Formatter()) 149 | return dasm 150 | } 151 | 152 | // String returns the status of the CPU in the form of: 153 | // pc sr ac xr yr sp n v - b d i z c 154 | // 1234 20 00 00 00 ff . . * . . . . . 155 | func (c *CPU) String() string { 156 | b := func(v bool) string { 157 | if v { 158 | return "*" 159 | } 160 | return "." 161 | } 162 | return fmt.Sprintf(""+ 163 | " pc sr ac xr yr sp n v - - d i z c\n"+ 164 | "%04x %02x %02x %02x %02x %02x %s %s %s %s %s %s %s %s", 165 | c.pc, 166 | c.SR|(1<<5), // bit 5 hard wired on 167 | c.A, 168 | c.X, 169 | c.Y, 170 | c.SP, 171 | b(c.SR&FlagN != 0), 172 | b(c.SR&FlagV != 0), 173 | b(true), 174 | b(false), 175 | b(c.SR&FlagD != 0), 176 | b(c.SR&FlagI != 0), 177 | b(c.SR&FlagZ != 0), 178 | b(c.SR&FlagC != 0), 179 | ) 180 | } 181 | 182 | // Increment the program counter and return the 8-bit value at the 183 | // program counter. 184 | func (c *CPU) fetch() uint8 { 185 | c.pc++ 186 | return c.mem.Read(int(c.pc)) 187 | } 188 | 189 | // Like fetch, but return the next 16-bit value. 190 | func (c *CPU) fetch2() int { 191 | return int(c.fetch()) + (int(c.fetch()) << 8) 192 | } 193 | 194 | // Push a 8-bit value to the stack. 195 | func (c *CPU) push(v uint8) { 196 | if c.WatchStack { 197 | log.Printf("%v: stack[%v] <= %v", c.Name, rcs.X8(c.SP), rcs.X8(v)) 198 | } 199 | c.mem.Write(addrStack+int(c.SP), v) 200 | if c.SP == 0 { 201 | log.Printf("(!) %v: stack overflow", c.Name) 202 | } 203 | c.SP-- 204 | } 205 | 206 | // Push a 16-bit value to the stack. 207 | func (c *CPU) push2(v uint16) { 208 | c.push(uint8(v >> 8)) 209 | c.push(uint8(v)) 210 | } 211 | 212 | // Pull a 8-bit value from the stack. 213 | func (c *CPU) pull() uint8 { 214 | if c.SP == 0xff { 215 | log.Printf("(!) %v: stack underflow", c.Name) 216 | } 217 | c.SP++ 218 | v := c.mem.Read(addrStack + int(c.SP)) 219 | if c.WatchStack { 220 | log.Printf("%v: %v <= stack[%v]", c.Name, rcs.X8(v), rcs.X8(c.SP)) 221 | } 222 | return v 223 | } 224 | 225 | // Pull a 16-bit value from the stack. 226 | func (c *CPU) pull2() uint16 { 227 | return uint16(c.pull()) | uint16(c.pull())<<8 228 | } 229 | 230 | func (c *CPU) Save(enc *rcs.Encoder) { 231 | enc.Encode(c.pc) 232 | enc.Encode(c.A) 233 | enc.Encode(c.X) 234 | enc.Encode(c.Y) 235 | enc.Encode(c.SP) 236 | enc.Encode(c.SR) 237 | } 238 | 239 | func (c *CPU) Load(dec *rcs.Decoder) { 240 | dec.Decode(&c.pc) 241 | dec.Decode(&c.A) 242 | dec.Decode(&c.X) 243 | dec.Decode(&c.Y) 244 | dec.Decode(&c.SP) 245 | dec.Decode(&c.SR) 246 | } 247 | -------------------------------------------------------------------------------- /cmd/retro-cs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "runtime" 11 | "runtime/pprof" 12 | "strings" 13 | 14 | "github.com/blackchip-org/retro-cs/app/monitor" 15 | "github.com/blackchip-org/retro-cs/mock" 16 | 17 | "github.com/veandco/go-sdl2/sdl" 18 | 19 | "github.com/blackchip-org/retro-cs/app" 20 | "github.com/blackchip-org/retro-cs/config" 21 | "github.com/blackchip-org/retro-cs/rcs" 22 | ) 23 | 24 | const ( 25 | defaultWidth = 1024 26 | defaultHeight = 786 27 | ) 28 | 29 | var ( 30 | optFullStart bool 31 | optProfC bool 32 | optPanic bool 33 | optSystem string 34 | optMonitor bool 35 | optImport string 36 | optNoAudio bool 37 | optNoVideo bool 38 | optTrace bool 39 | optWait bool 40 | ) 41 | 42 | func init() { 43 | flag.BoolVar(&optFullStart, "f", false, "full start -- do not bypass POST") 44 | flag.StringVar(&optImport, "i", "", "import state from `filename`") 45 | flag.BoolVar(&optProfC, "profc", false, "enable cpu profiling") 46 | flag.BoolVar(&optNoAudio, "no-audio", false, "disable audio") 47 | flag.BoolVar(&optNoVideo, "no-video", false, "disable video") 48 | flag.BoolVar(&optMonitor, "m", false, "enable monitor") 49 | flag.BoolVar(&optPanic, "panic", false, "install panic log writer") 50 | flag.StringVar(&optSystem, "s", "c64", "start this `system`") 51 | flag.BoolVar(&optTrace, "t", false, "enable tracing") 52 | flag.BoolVar(&optWait, "w", false, "wait for go command") 53 | } 54 | 55 | func main() { 56 | runtime.LockOSThread() 57 | log.SetFlags(0) 58 | flag.Parse() 59 | 60 | if optProfC { 61 | f, err := os.Create("./cpu.prof") 62 | if err != nil { 63 | log.Fatal("could not create CPU profile: ", err) 64 | } 65 | if err := pprof.StartCPUProfile(f); err != nil { 66 | log.Fatal("could not start CPU profile: ", err) 67 | } 68 | log.Println("starting profile") 69 | defer func() { 70 | pprof.StopCPUProfile() 71 | log.Println("profile saved") 72 | }() 73 | } 74 | if optNoVideo || optTrace || optWait { 75 | optMonitor = true 76 | } 77 | 78 | newMachine, ok := app.Systems[optSystem] 79 | if !ok { 80 | log.Fatalf("no such system: %v", optSystem) 81 | } 82 | config.System = optSystem 83 | config.UserDir = filepath.Join(config.UserHome, ".retro-cs") 84 | config.DataDir = filepath.Join(config.ResourceDir(), "data", optSystem) 85 | config.VarDir = filepath.Join(config.ResourceDir(), "var", optSystem) 86 | 87 | if err := os.MkdirAll(config.UserDir, 0755); err != nil { 88 | log.Fatalf("unable to create directory %v: %v", config.UserDir, err) 89 | } 90 | if err := os.MkdirAll(config.VarDir, 0755); err != nil { 91 | log.Fatalf("unable to create directory %v: %v", config.VarDir, err) 92 | } 93 | 94 | ctx := rcs.SDLContext{} 95 | if !optNoVideo { 96 | if err := sdl.Init(sdl.INIT_VIDEO); err != nil { 97 | log.Fatalf("unable to initialize video: %v", err) 98 | } 99 | fullScreen := uint32(0) 100 | if !optMonitor { 101 | fullScreen = sdl.WINDOW_FULLSCREEN_DESKTOP 102 | } 103 | window, err := sdl.CreateWindow( 104 | "retro-cs", 105 | sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, 106 | defaultWidth, defaultHeight, 107 | sdl.WINDOW_SHOWN|fullScreen, 108 | ) 109 | if err != nil { 110 | log.Fatalf("unable to initialize window: %v", err) 111 | } 112 | r, err := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED) 113 | if err != nil { 114 | log.Fatalf("unable to initialize renderer: %v", err) 115 | } 116 | info, err := r.GetInfo() 117 | if err != nil { 118 | log.Fatalf("unable to get renderer info: %v", err) 119 | } 120 | // FIXME: Change this to check for OpenGL 121 | if info.Name != "direct3d" { 122 | if _, err := window.GLCreateContext(); err != nil { 123 | log.Printf("unable to createl GL context: %v", err) 124 | } 125 | if err = sdl.GLSetSwapInterval(1); err != nil { 126 | log.Printf("unable to set swap interval: %v", err) 127 | } 128 | } 129 | ctx.Window = window 130 | ctx.Renderer = r 131 | } 132 | 133 | if !optNoAudio { 134 | requestSpec := sdl.AudioSpec{ 135 | Freq: 22050, 136 | Format: sdl.AUDIO_S16LSB, 137 | Channels: 2, 138 | Samples: 367, 139 | } 140 | if err := sdl.OpenAudio(&requestSpec, &ctx.AudioSpec); err != nil { 141 | log.Fatalf("unable to initialize audio: %v", err) 142 | } 143 | sdl.PauseAudio(false) 144 | } 145 | 146 | err := sdl.Init(sdl.INIT_JOYSTICK | sdl.INIT_GAMECONTROLLER) 147 | if err != nil { 148 | log.Fatalf("unable to initialize game controllers: %v", err) 149 | } 150 | mappingsDir := filepath.Join(config.ResourceDir(), "data", "game_controllers") 151 | mappings, err := ioutil.ReadDir(mappingsDir) 152 | if err != nil { 153 | log.Printf("(!) unable to load game controller mappings: %v", err) 154 | } 155 | for _, f := range mappings { 156 | if strings.HasSuffix(f.Name(), ".txt") { 157 | in, err := os.Open(filepath.Join(mappingsDir, f.Name())) 158 | if err != nil { 159 | log.Printf("(!) unable to open controller mapping: %v", err) 160 | continue 161 | } 162 | defer in.Close() 163 | s := bufio.NewScanner(in) 164 | for s.Scan() { 165 | line := strings.TrimSpace(s.Text()) 166 | if line == "" { 167 | continue 168 | } 169 | if line[0] == '#' { 170 | continue 171 | } 172 | sdl.GameControllerAddMapping(line) 173 | } 174 | } 175 | } 176 | 177 | mach, err := newMachine(ctx) 178 | if err != nil { 179 | log.Fatalf("unable to create machine: \n%v", err) 180 | } 181 | 182 | var mon *monitor.Monitor 183 | mon, err = monitor.New(mach) 184 | if err != nil { 185 | log.Fatalf("unable to create monitor: %v\n", err) 186 | } 187 | defer func() { 188 | mon.Close() 189 | }() 190 | 191 | if optMonitor { 192 | go func() { 193 | err := mon.Run() 194 | if err != nil { 195 | log.Fatalf("monitor error: %v", err) 196 | } 197 | }() 198 | } 199 | 200 | mach.Status = rcs.Run 201 | if optWait { 202 | mach.Status = rcs.Pause 203 | } 204 | if optTrace { 205 | mach.Command(rcs.MachTraceAll, true) 206 | } 207 | if optImport != "" { 208 | filename := filepath.Join(config.VarDir, optImport) 209 | mach.Command(rcs.MachImport, filename) 210 | } else if !optFullStart { 211 | filename := filepath.Join(config.DataDir, "init.state") 212 | if _, err := os.Stat(filename); !os.IsNotExist(err) { 213 | mach.Command(rcs.MachImport, filename) 214 | } 215 | } 216 | 217 | if optPanic { 218 | log.SetOutput(&mock.PanicWriter{}) 219 | } 220 | 221 | startFile := filepath.Join(config.UserDir, "startup") 222 | cmds, err := ioutil.ReadFile(startFile) 223 | if err == nil { 224 | mon.Eval(string(cmds)) 225 | } 226 | 227 | mach.Run() 228 | } 229 | -------------------------------------------------------------------------------- /system/c64/inputs.go: -------------------------------------------------------------------------------- 1 | package c64 2 | 3 | import ( 4 | "github.com/blackchip-org/retro-cs/rcs/cbm/petscii" 5 | "github.com/veandco/go-sdl2/sdl" 6 | ) 7 | 8 | const kbBufLen = 10 // length of keyboard buffer 9 | 10 | type keyboard struct { 11 | buf []uint8 12 | ndx uint8 // Number of characters in keyboard buffer 13 | stkey uint8 // Was STOP Key Pressed? 14 | joy2 uint8 // HACK: joystick 2, move elsewhere 15 | } 16 | 17 | func newKeyboard() *keyboard { 18 | return &keyboard{ 19 | buf: make([]uint8, kbBufLen, kbBufLen), 20 | joy2: 0xff, 21 | } 22 | } 23 | 24 | func (k *keyboard) handle(e *sdl.KeyboardEvent) error { 25 | ch, ok := k.lookup(e) 26 | if !ok { 27 | return nil 28 | } 29 | if k.ndx >= kbBufLen { 30 | return nil 31 | } 32 | k.buf[k.ndx] = ch 33 | k.ndx++ 34 | return nil 35 | } 36 | 37 | const ( 38 | keyCursorDown = uint8(0x11) 39 | keyCursorLeft = uint8(0x9d) 40 | keyCursorRight = uint8(0x1d) 41 | keyCursorUp = uint8(0x91) 42 | ) 43 | 44 | // https://wiki.libsdl.org/SDLKeycodeLookup 45 | type keymap map[sdl.Keycode]uint8 46 | 47 | var keys = keymap{ 48 | sdl.K_BACKSPACE: 0x14, 49 | sdl.K_RETURN: 0x0d, 50 | sdl.K_SPACE: 0x20, 51 | sdl.K_QUOTE: 0x27, 52 | sdl.K_PERIOD: 0x2e, 53 | sdl.K_COMMA: 0x2c, 54 | sdl.K_SLASH: 0x2f, 55 | sdl.K_0: 0x30, 56 | sdl.K_1: 0x31, 57 | sdl.K_2: 0x32, 58 | sdl.K_3: 0x33, 59 | sdl.K_4: 0x34, 60 | sdl.K_5: 0x35, 61 | sdl.K_6: 0x36, 62 | sdl.K_7: 0x37, 63 | sdl.K_8: 0x38, 64 | sdl.K_9: 0x39, 65 | sdl.K_SEMICOLON: 0x3b, 66 | sdl.K_EQUALS: 0x3d, 67 | sdl.K_LEFTBRACKET: 0x5b, 68 | sdl.K_BACKSLASH: 0x5c, // british pound 69 | sdl.K_RIGHTBRACKET: 0x5d, 70 | sdl.K_a: 0x41, 71 | sdl.K_b: 0x42, 72 | sdl.K_c: 0x43, 73 | sdl.K_d: 0x44, 74 | sdl.K_e: 0x45, 75 | sdl.K_f: 0x46, 76 | sdl.K_g: 0x47, 77 | sdl.K_h: 0x48, 78 | sdl.K_i: 0x49, 79 | sdl.K_j: 0x4a, 80 | sdl.K_k: 0x4b, 81 | sdl.K_l: 0x4c, 82 | sdl.K_m: 0x4d, 83 | sdl.K_n: 0x4e, 84 | sdl.K_o: 0x4f, 85 | sdl.K_p: 0x50, 86 | sdl.K_q: 0x51, 87 | sdl.K_r: 0x52, 88 | sdl.K_s: 0x53, 89 | sdl.K_t: 0x54, 90 | sdl.K_u: 0x55, 91 | sdl.K_v: 0x56, 92 | sdl.K_w: 0x57, 93 | sdl.K_x: 0x58, 94 | sdl.K_y: 0x59, 95 | sdl.K_z: 0x5a, 96 | sdl.K_DOWN: keyCursorDown, 97 | sdl.K_LEFT: keyCursorLeft, 98 | sdl.K_RIGHT: keyCursorRight, 99 | sdl.K_UP: keyCursorUp, 100 | } 101 | 102 | var keysShift = keymap{ 103 | sdl.K_QUOTE: 0x22, 104 | sdl.K_PERIOD: 0x3e, 105 | sdl.K_COMMA: 0x3c, 106 | sdl.K_SLASH: 0x3f, 107 | sdl.K_0: 0x29, 108 | sdl.K_1: 0x21, 109 | sdl.K_2: 0x40, 110 | sdl.K_3: 0x23, 111 | sdl.K_4: 0x24, 112 | sdl.K_5: 0x25, 113 | sdl.K_6: 0x5e, 114 | sdl.K_7: 0x26, 115 | sdl.K_8: 0x2a, 116 | sdl.K_9: 0x28, 117 | sdl.K_SEMICOLON: 0x3a, 118 | sdl.K_EQUALS: 0x2b, 119 | sdl.K_a: 0xc1, 120 | sdl.K_b: 0xc2, 121 | sdl.K_c: 0xc3, 122 | sdl.K_d: 0xc4, 123 | sdl.K_e: 0xc5, 124 | sdl.K_f: 0xc6, 125 | sdl.K_g: 0xc7, 126 | sdl.K_h: 0xc8, 127 | sdl.K_i: 0xc9, 128 | sdl.K_j: 0xca, 129 | sdl.K_k: 0xcb, 130 | sdl.K_l: 0xcc, 131 | sdl.K_m: 0xcd, 132 | sdl.K_n: 0xce, 133 | sdl.K_o: 0xcf, 134 | sdl.K_p: 0xd0, 135 | sdl.K_q: 0xd1, 136 | sdl.K_r: 0xd2, 137 | sdl.K_s: 0xd3, 138 | sdl.K_t: 0xd4, 139 | sdl.K_u: 0xd5, 140 | sdl.K_v: 0xd6, 141 | sdl.K_w: 0xd7, 142 | sdl.K_x: 0xd8, 143 | sdl.K_y: 0xd9, 144 | sdl.K_z: 0xda, 145 | } 146 | 147 | var keysControl = keymap{ 148 | sdl.K_1: petscii.Black, 149 | sdl.K_2: petscii.White, 150 | sdl.K_3: petscii.Red, 151 | sdl.K_4: petscii.Cyan, 152 | sdl.K_5: petscii.Purple, 153 | sdl.K_6: petscii.Green, 154 | sdl.K_7: petscii.Blue, 155 | sdl.K_8: petscii.Yellow, 156 | } 157 | 158 | var keysCommodore = keymap{ 159 | sdl.K_1: petscii.Orange, 160 | sdl.K_2: petscii.Brown, 161 | sdl.K_3: petscii.LightRed, 162 | sdl.K_4: petscii.DarkGray, 163 | sdl.K_5: petscii.MediumGray, 164 | sdl.K_6: petscii.LightGreen, 165 | sdl.K_7: petscii.LightBlue, 166 | sdl.K_8: petscii.LightGray, 167 | } 168 | 169 | var keymaps = map[sdl.Keymod]keymap{ 170 | sdl.KMOD_NONE: keys, 171 | sdl.KMOD_LSHIFT: keysShift, 172 | sdl.KMOD_RSHIFT: keysShift, 173 | sdl.KMOD_LCTRL: keysControl, 174 | sdl.KMOD_RCTRL: keysControl, 175 | sdl.KMOD_LGUI: keysCommodore, 176 | sdl.KMOD_RGUI: keysCommodore, 177 | } 178 | 179 | func (k *keyboard) lookup(e *sdl.KeyboardEvent) (uint8, bool) { 180 | keysym := e.Keysym 181 | switch { 182 | /* 183 | case keysym.Mod&sdl.KMOD_CTRL > 0 && keysym.Sym == sdl.K_ESCAPE: 184 | if e.Type == sdl.KEYUP { 185 | k.mach.Reset() 186 | return 0, false 187 | } 188 | */ 189 | case keysym.Mod&sdl.KMOD_CTRL > 0 && keysym.Sym == sdl.K_c: 190 | if e.Type == sdl.KEYDOWN { 191 | k.stkey = 0x7f 192 | } 193 | case keysym.Sym == sdl.K_c && e.Type == sdl.KEYUP: 194 | k.stkey = 0xff 195 | 196 | case keysym.Sym == sdl.K_UP && e.Type == sdl.KEYDOWN: 197 | k.joy2 &^= (1 << 0) 198 | case keysym.Sym == sdl.K_DOWN && e.Type == sdl.KEYDOWN: 199 | k.joy2 &^= (1 << 1) 200 | case keysym.Sym == sdl.K_LEFT && e.Type == sdl.KEYDOWN: 201 | k.joy2 &^= (1 << 2) 202 | case keysym.Sym == sdl.K_RIGHT && e.Type == sdl.KEYDOWN: 203 | k.joy2 &^= (1 << 3) 204 | case keysym.Sym == sdl.K_SPACE && e.Type == sdl.KEYDOWN: 205 | k.joy2 &^= (1 << 4) 206 | 207 | case keysym.Sym == sdl.K_UP && e.Type == sdl.KEYUP: 208 | k.joy2 |= (1 << 0) 209 | case keysym.Sym == sdl.K_DOWN && e.Type == sdl.KEYUP: 210 | k.joy2 |= (1 << 1) 211 | case keysym.Sym == sdl.K_LEFT && e.Type == sdl.KEYUP: 212 | k.joy2 |= (1 << 2) 213 | case keysym.Sym == sdl.K_RIGHT && e.Type == sdl.KEYUP: 214 | k.joy2 |= (1 << 3) 215 | case keysym.Sym == sdl.K_SPACE && e.Type == sdl.KEYUP: 216 | k.joy2 |= (1 << 4) 217 | } 218 | 219 | if e.Type != sdl.KEYDOWN { 220 | return 0, false 221 | } 222 | keymap0 := keymaps[sdl.KMOD_NONE] 223 | //mod := sdl.Keymod(keysym.Mod & 0x03) // Just take the lower two bits (why?) 224 | mod := sdl.Keymod(keysym.Mod) 225 | keymap, ok := keymaps[mod] 226 | if !ok { 227 | keymap = keymap0 228 | } 229 | ch, ok := keymap[keysym.Sym] 230 | if !ok { 231 | ch, ok = keymap0[keysym.Sym] 232 | } 233 | if !ok { 234 | return 0, false 235 | } 236 | return ch, true 237 | } 238 | 239 | func (k *keyboard) special(keysym sdl.Keysym) bool { 240 | return false 241 | } 242 | -------------------------------------------------------------------------------- /gen/z80/dasm/dasm.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate go run . 4 | //go:generate go fmt ../../../rcs/z80/dasm.go 5 | //go:generate go fmt ../../../rcs/z80/dasm_harston_test.go 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | "strconv" 14 | "strings" 15 | "unicode" 16 | 17 | "github.com/blackchip-org/retro-cs/config" 18 | ) 19 | 20 | var ( 21 | root = filepath.Join("..", "..", "..") 22 | targetDir = filepath.Join(root, "rcs", "z80") 23 | sourceDir = filepath.Join(config.ResourceDir(), "ext", "harston") 24 | ) 25 | 26 | const ( 27 | lineStart = 7 28 | lineEnd = 262 29 | ) 30 | 31 | var breaks = []int{ 32 | 3, 33 | 7, 34 | 18, 35 | 22, 36 | 33, 37 | 38, 38 | 45, 39 | 49, 40 | 62, 41 | 67, 42 | 80, 43 | } 44 | 45 | func dasm() { 46 | var out bytes.Buffer 47 | 48 | out.WriteString(` 49 | // Code generated by gen/z80/dasm/dasm.go. DO NOT EDIT. 50 | 51 | package z80 52 | 53 | import "github.com/blackchip-org/retro-cs/rcs" 54 | 55 | `) 56 | 57 | listFile := filepath.Join(sourceDir, "z80oplist.txt") 58 | data, err := ioutil.ReadFile(listFile) 59 | if err != nil { 60 | panic(err) 61 | } 62 | lines := strings.Split(string(data), "\n") 63 | 64 | // unprefixed 65 | out.WriteString("var dasmTable = map[uint8]func(rcs.StmtEval){\n") 66 | for i := lineStart; i <= lineEnd; i++ { 67 | line := lines[i] 68 | parseTable(&out, line, 0, "") 69 | } 70 | out.WriteString("}\n") 71 | 72 | // dd prefix 73 | out.WriteString("var dasmTableDD = map[uint8]func(rcs.StmtEval){\n") 74 | for i := lineStart; i <= lineEnd; i++ { 75 | line := lines[i] 76 | parseTable(&out, line, 2, "dd") 77 | } 78 | out.WriteString("}\n") 79 | 80 | // fd prefix 81 | out.WriteString("var dasmTableFD = map[uint8]func(rcs.StmtEval){\n") 82 | for i := lineStart; i <= lineEnd; i++ { 83 | line := lines[i] 84 | parseTable(&out, line, 2, "fd") 85 | } 86 | out.WriteString("}\n") 87 | 88 | // cb prefix 89 | out.WriteString("var dasmTableCB = map[uint8]func(rcs.StmtEval){\n") 90 | for i := lineStart; i <= lineEnd; i++ { 91 | line := lines[i] 92 | parseTable(&out, line, 4, "cb") 93 | } 94 | out.WriteString("}\n") 95 | 96 | // fd cb prefix 97 | out.WriteString("var dasmTableFDCB = map[uint8]func(rcs.StmtEval){\n") 98 | for i := lineStart; i <= lineEnd; i++ { 99 | line := lines[i] 100 | parseTable(&out, line, 6, "fdcb") 101 | } 102 | out.WriteString("}\n") 103 | 104 | // dd cb prefix 105 | out.WriteString("var dasmTableDDCB = map[uint8]func(rcs.StmtEval){\n") 106 | for i := lineStart; i <= lineEnd; i++ { 107 | line := lines[i] 108 | parseTable(&out, line, 6, "ddcb") 109 | } 110 | out.WriteString("}\n") 111 | 112 | // ed prefix 113 | out.WriteString("var dasmTableED = map[uint8]func(rcs.StmtEval){\n") 114 | for i := lineStart; i <= lineEnd; i++ { 115 | line := lines[i] 116 | parseTable(&out, line, 8, "ed") 117 | } 118 | out.WriteString("}\n") 119 | 120 | outFile := filepath.Join(targetDir, "dasm.go") 121 | err = ioutil.WriteFile(outFile, out.Bytes(), 0644) 122 | if err != nil { 123 | fmt.Printf("unable to write file: %v", err) 124 | os.Exit(1) 125 | } 126 | } 127 | 128 | func parseTable(out *bytes.Buffer, line string, firstBreak int, prefix string) { 129 | break1 := breaks[firstBreak] 130 | break2 := breaks[firstBreak+1] 131 | break3 := breaks[firstBreak+2] 132 | 133 | // Ensure the line is at least 80 characters long and then we don't 134 | // have to worry about different line lengths when slicing. 135 | line = fmt.Sprintf("%-80s", line) 136 | 137 | strOpcode := strings.TrimSpace(line[0:2]) 138 | opcode, _ := strconv.ParseUint(strOpcode, 16, 8) 139 | 140 | switch { 141 | case prefix == "" && opcode == 0xcb: 142 | out.WriteString("0xcb: func(e rcs.StmtEval) { opCB(e) },\n") 143 | return 144 | case prefix == "" && opcode == 0xdd: 145 | out.WriteString("0xdd: func(e rcs.StmtEval) { opDD(e) },\n") 146 | return 147 | case prefix == "" && opcode == 0xed: 148 | out.WriteString("0xed: func(e rcs.StmtEval) { opED(e) },\n") 149 | return 150 | case prefix == "" && opcode == 0xfd: 151 | out.WriteString("0xfd: func(e rcs.StmtEval) { opFD(e) },\n") 152 | return 153 | case 154 | prefix == "dd" && opcode == 0xcb, 155 | prefix == "dd" && opcode == 0xdd, 156 | prefix == "dd" && opcode == 0xed, 157 | prefix == "dd" && opcode == 0xfd, 158 | prefix == "fd" && opcode == 0xcb, 159 | prefix == "fd" && opcode == 0xdd, 160 | prefix == "fd" && opcode == 0xed, 161 | prefix == "fd" && opcode == 0xfd: 162 | return 163 | } 164 | 165 | out.WriteString("0x") 166 | out.WriteString(fmt.Sprintf("%02x", opcode)) 167 | if prefix == "ddcb" || prefix == "fdcb" { 168 | out.WriteString(": func(e rcs.StmtEval) { op2(e, ") 169 | } else { 170 | out.WriteString(": func(e rcs.StmtEval) { op1(e, ") 171 | } 172 | 173 | args := make([]string, 1) 174 | args[0] = strings.TrimSpace(line[break1:break2]) 175 | 176 | switch { 177 | case strings.HasPrefix(args[0], "MOS_"): 178 | args[0] = "-" 179 | case strings.HasPrefix(args[0], "ED_"): 180 | args[0] = "-" 181 | case unicode.IsLower(rune(args[0][0])): 182 | args[0] = "-" 183 | case args[0][0] == '[': 184 | args[0] = "-" 185 | } 186 | 187 | if args[0] == "-" { 188 | out.WriteString(fmt.Sprintf(`"?%v%02x"`, prefix, opcode)) 189 | } else { 190 | args[0] = `"` + args[0] + `"` 191 | fields := strings.Split(line[break2:break3], ",") 192 | for _, field := range fields { 193 | args = append(args, `"`+strings.TrimSpace(field)+`"`) 194 | } 195 | entry := strings.Join(args, ",") 196 | if prefix == "fd" { 197 | entry = strings.Replace(entry, "IX", "IY", -1) 198 | } 199 | if prefix == "ddcb" { 200 | entry = strings.Replace(entry, "IY", "IX", -1) 201 | } 202 | out.WriteString(strings.ToLower(entry)) 203 | } 204 | 205 | out.WriteString(") },\n") 206 | } 207 | 208 | func harston() { 209 | var out bytes.Buffer 210 | 211 | out.WriteString(` 212 | // Code generated by gen/z80/dasm/dasm.go. DO NOT EDIT. 213 | 214 | package z80 215 | 216 | var harstonTests = []harstonTest{ 217 | `) 218 | 219 | data, err := ioutil.ReadFile("expected.txt") 220 | if err != nil { 221 | panic(err) 222 | } 223 | lines := strings.Split(string(data), "\n") 224 | for i := 0; i < len(lines); i++ { 225 | line := lines[i] 226 | if strings.TrimSpace(line) == "" { 227 | continue 228 | } 229 | if line[0] == '=' { 230 | break 231 | } 232 | if line[0] == '#' { 233 | continue 234 | } 235 | data := strings.Split(line, " ") 236 | strdata := strings.Join(data, " ") 237 | hexdata := "0x" + strings.Join(data, ", 0x") 238 | i++ 239 | op := lines[i] 240 | out.WriteString(fmt.Sprintf(`harstonTest{"%v", "%v", []uint8{%v}},`, strdata, op, hexdata)) 241 | out.WriteString("\n") 242 | } 243 | out.WriteString("}\n") 244 | 245 | outFile := filepath.Join(targetDir, "dasm_harston_test.go") 246 | err = ioutil.WriteFile(outFile, out.Bytes(), 0644) 247 | if err != nil { 248 | fmt.Printf("unable to write file: %v", err) 249 | os.Exit(1) 250 | } 251 | } 252 | 253 | func main() { 254 | dasm() 255 | harston() 256 | } 257 | -------------------------------------------------------------------------------- /system/c64/memory_test.go: -------------------------------------------------------------------------------- 1 | package c64 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestMemoryLoad(t *testing.T) { 9 | const ( 10 | page00 = 0x0000 11 | page10 = 0x1000 12 | page80 = 0x8000 13 | pagea0 = 0xa000 14 | pagec0 = 0xc000 15 | paged0 = 0xd000 16 | pagee0 = 0xe000 17 | ) 18 | 19 | const ( 20 | vram = 0xf0 21 | vbasic = 0xba 22 | vkernal = 0xea 23 | vchar = 0xca 24 | vcartlo = 0x0c 25 | vcarthi = 0xc0 26 | vio = 0x10 27 | ) 28 | 29 | cart := make([]byte, 16*1024, 16*1024) 30 | cart[0x0000] = vcartlo 31 | cart[0x2000] = vcarthi 32 | 33 | roms := map[string][]byte{ 34 | "basic": []byte{vbasic}, 35 | "kernal": []byte{vkernal}, 36 | "chargen": []byte{vchar}, 37 | "cart": cart, 38 | } 39 | 40 | ram := make([]uint8, 0x10000, 0x10000) 41 | io := make([]uint8, 0x1000, 0x1000) 42 | mem := newMemory(ram, io, roms) 43 | mem.SetBank(31) 44 | mem.Write(paged0, vio) 45 | 46 | mem.SetBank(0) 47 | mem.Write(page00, vram) 48 | mem.Write(page10, vram) 49 | mem.Write(page80, vram) 50 | mem.Write(pagea0, vram) 51 | mem.Write(pagec0, vram) 52 | mem.Write(paged0, vram) 53 | mem.Write(pagee0, vram) 54 | 55 | var tests = []struct { 56 | mode int 57 | addr int 58 | want uint8 59 | }{ 60 | {0, page00, vram}, 61 | {0, page10, vram}, 62 | {0, page80, vram}, 63 | {0, pagea0, vram}, 64 | {0, pagec0, vram}, 65 | {0, paged0, vram}, 66 | {0, pagee0, vram}, 67 | 68 | {1, page00, vram}, 69 | {1, page10, vram}, 70 | {1, page80, vram}, 71 | {1, pagea0, vram}, 72 | {1, pagec0, vram}, 73 | {1, paged0, vram}, 74 | {1, pagee0, vram}, 75 | 76 | {2, page00, vram}, 77 | {2, page10, vram}, 78 | {2, page80, vram}, 79 | {2, pagea0, vcarthi}, 80 | {2, pagec0, vram}, 81 | {2, paged0, vchar}, 82 | {2, pagee0, vkernal}, 83 | 84 | {3, page00, vram}, 85 | {3, page10, vram}, 86 | {3, page80, vcartlo}, 87 | {3, pagea0, vcarthi}, 88 | {3, pagec0, vram}, 89 | {3, paged0, vchar}, 90 | {3, pagee0, vkernal}, 91 | 92 | {4, page00, vram}, 93 | {4, page10, vram}, 94 | {4, page80, vram}, 95 | {4, pagea0, vram}, 96 | {4, pagec0, vram}, 97 | {4, paged0, vram}, 98 | {4, pagee0, vram}, 99 | 100 | {5, page00, vram}, 101 | {5, page10, vram}, 102 | {5, page80, vram}, 103 | {5, pagea0, vram}, 104 | {5, pagec0, vram}, 105 | {5, paged0, vio}, 106 | {5, pagee0, vram}, 107 | 108 | {6, page00, vram}, 109 | {6, page10, vram}, 110 | {6, page80, vram}, 111 | {6, pagea0, vcarthi}, 112 | {6, pagec0, vram}, 113 | {6, paged0, vio}, 114 | {6, pagee0, vkernal}, 115 | 116 | {7, page00, vram}, 117 | {7, page10, vram}, 118 | {7, page80, vcartlo}, 119 | {7, pagea0, vcarthi}, 120 | {7, pagec0, vram}, 121 | {7, paged0, vio}, 122 | {7, pagee0, vkernal}, 123 | 124 | {8, page00, vram}, 125 | {8, page10, vram}, 126 | {8, page80, vram}, 127 | {8, pagea0, vram}, 128 | {8, pagec0, vram}, 129 | {8, paged0, vram}, 130 | {8, pagee0, vram}, 131 | 132 | {9, page00, vram}, 133 | {9, page10, vram}, 134 | {9, page80, vram}, 135 | {9, pagea0, vram}, 136 | {9, pagec0, vram}, 137 | {9, paged0, vchar}, 138 | {9, pagee0, vram}, 139 | 140 | {10, page00, vram}, 141 | {10, page10, vram}, 142 | {10, page80, vram}, 143 | {10, pagea0, vram}, 144 | {10, pagec0, vram}, 145 | {10, paged0, vchar}, 146 | {10, pagee0, vkernal}, 147 | 148 | {11, page00, vram}, 149 | {11, page10, vram}, 150 | {11, page80, vcartlo}, 151 | {11, pagea0, vbasic}, 152 | {11, pagec0, vram}, 153 | {11, paged0, vchar}, 154 | {11, pagee0, vkernal}, 155 | 156 | {12, page00, vram}, 157 | {12, page10, vram}, 158 | {12, page80, vram}, 159 | {12, pagea0, vram}, 160 | {12, pagec0, vram}, 161 | {12, paged0, vram}, 162 | {12, pagee0, vram}, 163 | 164 | {13, page00, vram}, 165 | {13, page10, vram}, 166 | {13, page80, vram}, 167 | {13, pagea0, vram}, 168 | {13, pagec0, vram}, 169 | {13, paged0, vio}, 170 | {13, pagee0, vram}, 171 | 172 | {14, page00, vram}, 173 | {14, page10, vram}, 174 | {14, page80, vram}, 175 | {14, pagea0, vram}, 176 | {14, pagec0, vram}, 177 | {14, paged0, vio}, 178 | {14, pagee0, vkernal}, 179 | 180 | {15, page00, vram}, 181 | {15, page10, vram}, 182 | {15, page80, vcartlo}, 183 | {15, pagea0, vbasic}, 184 | {15, pagec0, vram}, 185 | {15, paged0, vio}, 186 | {15, pagee0, vkernal}, 187 | 188 | {16, page00, vram}, 189 | {16, page10, 0}, 190 | {16, page80, vcartlo}, 191 | {16, pagea0, 0}, 192 | {16, pagec0, 0}, 193 | {16, paged0, vio}, 194 | {16, pagee0, vcarthi}, 195 | 196 | {17, page00, vram}, 197 | {17, page10, 0}, 198 | {17, page80, vcartlo}, 199 | {17, pagea0, 0}, 200 | {17, pagec0, 0}, 201 | {17, paged0, vio}, 202 | {17, pagee0, vcarthi}, 203 | 204 | {18, page00, vram}, 205 | {18, page10, 0}, 206 | {18, page80, vcartlo}, 207 | {18, pagea0, 0}, 208 | {18, pagec0, 0}, 209 | {18, paged0, vio}, 210 | {18, pagee0, vcarthi}, 211 | 212 | {19, page00, vram}, 213 | {19, page10, 0}, 214 | {19, page80, vcartlo}, 215 | {19, pagea0, 0}, 216 | {19, pagec0, 0}, 217 | {19, paged0, vio}, 218 | {19, pagee0, vcarthi}, 219 | 220 | {20, page00, vram}, 221 | {20, page10, 0}, 222 | {20, page80, vcartlo}, 223 | {20, pagea0, 0}, 224 | {20, pagec0, 0}, 225 | {20, paged0, vio}, 226 | {20, pagee0, vcarthi}, 227 | 228 | {21, page00, vram}, 229 | {21, page10, 0}, 230 | {21, page80, vcartlo}, 231 | {21, pagea0, 0}, 232 | {21, pagec0, 0}, 233 | {21, paged0, vio}, 234 | {21, pagee0, vcarthi}, 235 | 236 | {22, page00, vram}, 237 | {22, page10, 0}, 238 | {22, page80, vcartlo}, 239 | {22, pagea0, 0}, 240 | {22, pagec0, 0}, 241 | {22, paged0, vio}, 242 | {22, pagee0, vcarthi}, 243 | 244 | {23, page00, vram}, 245 | {23, page10, 0}, 246 | {23, page80, vcartlo}, 247 | {23, pagea0, 0}, 248 | {23, pagec0, 0}, 249 | {23, paged0, vio}, 250 | {23, pagee0, vcarthi}, 251 | 252 | {24, page00, vram}, 253 | {24, page10, vram}, 254 | {24, page80, vram}, 255 | {24, pagea0, vram}, 256 | {24, pagec0, vram}, 257 | {24, paged0, vram}, 258 | {24, pagee0, vram}, 259 | 260 | {25, page00, vram}, 261 | {25, page10, vram}, 262 | {25, page80, vram}, 263 | {25, pagea0, vram}, 264 | {25, pagec0, vram}, 265 | {25, paged0, vchar}, 266 | {25, pagee0, vram}, 267 | 268 | {26, page00, vram}, 269 | {26, page10, vram}, 270 | {26, page80, vram}, 271 | {26, pagea0, vram}, 272 | {26, pagec0, vram}, 273 | {26, paged0, vchar}, 274 | {26, pagee0, vkernal}, 275 | 276 | {27, page00, vram}, 277 | {27, page10, vram}, 278 | {27, page80, vram}, 279 | {27, pagea0, vbasic}, 280 | {27, pagec0, vram}, 281 | {27, paged0, vchar}, 282 | {27, pagee0, vkernal}, 283 | 284 | {28, page00, vram}, 285 | {28, page10, vram}, 286 | {28, page80, vram}, 287 | {28, pagea0, vram}, 288 | {28, pagec0, vram}, 289 | {28, paged0, vram}, 290 | {28, pagee0, vram}, 291 | 292 | {29, page00, vram}, 293 | {29, page10, vram}, 294 | {29, page80, vram}, 295 | {29, pagea0, vram}, 296 | {29, pagec0, vram}, 297 | {29, paged0, vio}, 298 | {29, pagee0, vram}, 299 | 300 | {30, page00, vram}, 301 | {30, page10, vram}, 302 | {30, page80, vram}, 303 | {30, pagea0, vram}, 304 | {30, pagec0, vram}, 305 | {30, paged0, vio}, 306 | {30, pagee0, vkernal}, 307 | 308 | {31, page00, vram}, 309 | {31, page10, vram}, 310 | {31, page80, vram}, 311 | {31, pagea0, vbasic}, 312 | {31, pagec0, vram}, 313 | {31, paged0, vio}, 314 | {31, pagee0, vkernal}, 315 | } 316 | 317 | for _, test := range tests { 318 | label := fmt.Sprintf("mode %02d addr %04x", test.mode, test.addr) 319 | t.Run(label, func(t *testing.T) { 320 | mem.SetBank(test.mode) 321 | have := mem.Read(test.addr) 322 | if test.want != have { 323 | t.Errorf("\n want: %02x \n have: %02x", test.want, have) 324 | } 325 | }) 326 | } 327 | } 328 | --------------------------------------------------------------------------------