├── .gitignore ├── res └── font.bmp ├── rom ├── basic.rom ├── kernal.rom └── chargen.rom ├── prgs ├── colors.prg ├── sprite.prg ├── eaglesoft.prg ├── playsound.prg └── superball.prg ├── images ├── rust64_github.png ├── rust64_youtube.png ├── rust64_youtube2.png └── rust64_github_prev.png ├── Makefile ├── Cargo.toml ├── src ├── c64 │ ├── clock.rs │ ├── vic_tables.rs │ ├── crt.rs │ ├── mod.rs │ ├── memory.rs │ ├── sid_tables.rs │ ├── io.rs │ ├── cpu.rs │ ├── cia.rs │ └── sid.rs ├── main.rs ├── debugger │ ├── font.rs │ └── mod.rs └── utils │ └── mod.rs ├── LICENSE ├── .github └── workflows │ └── rust.yml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /res/font.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/res/font.bmp -------------------------------------------------------------------------------- /rom/basic.rom: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/rom/basic.rom -------------------------------------------------------------------------------- /rom/kernal.rom: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/rom/kernal.rom -------------------------------------------------------------------------------- /prgs/colors.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/prgs/colors.prg -------------------------------------------------------------------------------- /prgs/sprite.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/prgs/sprite.prg -------------------------------------------------------------------------------- /rom/chargen.rom: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/rom/chargen.rom -------------------------------------------------------------------------------- /prgs/eaglesoft.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/prgs/eaglesoft.prg -------------------------------------------------------------------------------- /prgs/playsound.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/prgs/playsound.prg -------------------------------------------------------------------------------- /prgs/superball.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/prgs/superball.prg -------------------------------------------------------------------------------- /images/rust64_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/images/rust64_github.png -------------------------------------------------------------------------------- /images/rust64_youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/images/rust64_youtube.png -------------------------------------------------------------------------------- /images/rust64_youtube2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/images/rust64_youtube2.png -------------------------------------------------------------------------------- /images/rust64_github_prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kondrak/rust64/HEAD/images/rust64_github_prev.png -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # convenience makefile for Emacs M-x recompile 2 | 3 | all: 4 | @export PATH 5 | RUST_BACKTRACE=1 cargo run --release debugger 6 | debug: 7 | @export PATH 8 | cargo build 9 | release: 10 | @export PATH 11 | cargo build --release 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust64" 3 | version = "0.6.1" 4 | authors = [ "Krzysztof Kondrak " ] 5 | 6 | [dependencies] 7 | minifb = "0.20" 8 | rand = "0.3.14" 9 | time = "0.1.34" 10 | byteorder = "1.2.2" 11 | enum_primitive = "0.1" 12 | num = "0.1" 13 | 14 | 15 | [target.'cfg(not(target_os = "redox"))'.dependencies] 16 | sdl2 = "0.35.0" 17 | -------------------------------------------------------------------------------- /src/c64/clock.rs: -------------------------------------------------------------------------------- 1 | // timing clock structure 2 | extern crate time; 3 | 4 | pub struct Clock { 5 | curr_time: f64, 6 | last_time: f64, 7 | clock_period: f64, 8 | } 9 | 10 | impl Clock { 11 | pub fn new(freq: f64) -> Clock { 12 | let mut clock = Clock { 13 | curr_time: 0.0, 14 | last_time: 0.0, 15 | clock_period: 1.0 / freq, 16 | }; 17 | 18 | clock.last_time = time::precise_time_s(); 19 | clock 20 | } 21 | 22 | pub fn tick(&mut self) -> bool { 23 | self.curr_time = time::precise_time_s(); 24 | 25 | if self.curr_time - self.last_time >= self.clock_period { 26 | self.last_time = self.curr_time; 27 | return true 28 | } 29 | 30 | false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Krzysztof Kondrak 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate minifb; 2 | extern crate byteorder; 3 | extern crate num; 4 | 5 | #[macro_use] 6 | extern crate enum_primitive; 7 | 8 | #[macro_use] 9 | mod utils; 10 | mod c64; 11 | mod debugger; 12 | 13 | use minifb::*; 14 | use std::env; 15 | 16 | fn main() { 17 | let args: Vec = env::args().collect(); 18 | 19 | let mut prg_to_load = String::new(); 20 | let mut crt_to_load = String::new(); 21 | let mut debugger_on = false; 22 | let mut window_scale = Scale::X1; 23 | 24 | // process cmd line params 25 | for i in 1..args.len() { 26 | if args[i] == "debugger" { 27 | debugger_on = true; 28 | } 29 | else if args[i] == "x2" { 30 | window_scale = Scale::X2; 31 | } 32 | else if args[i].ends_with(".prg") { 33 | prg_to_load = args[i].clone(); 34 | } 35 | else if args[i].ends_with(".crt") { 36 | crt_to_load = args[i].clone(); 37 | } 38 | } 39 | 40 | let mut c64 = c64::C64::new(window_scale, debugger_on, &prg_to_load, &crt_to_load); 41 | c64.reset(); 42 | 43 | // main update loop 44 | while c64.main_window.is_open() { 45 | c64.run(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | name: Build and Test 15 | strategy: 16 | matrix: 17 | os: [Ubuntu-latest, Windows-latest, MacOS-latest] 18 | runs-on: ${{ matrix.os }} 19 | 20 | steps: 21 | - if: matrix.os == 'ubuntu-latest' 22 | name: Get SDL2 for Linux 23 | run: | 24 | sudo add-apt-repository ppa:team-xbmc/ppa -y 25 | sudo apt-get update -q 26 | sudo apt-get install libegl1-mesa-dev libgles2-mesa-dev libsdl2-dev 27 | - if: matrix.os == 'macOS-latest' 28 | name: Get SDL2 for MacOS 29 | run: | 30 | brew install SDL2 31 | - if: matrix.os == 'windows-latest' 32 | name: Get SDL2 for Windows 33 | shell: pwsh 34 | run: | 35 | Invoke-WebRequest -Uri "https://www.libsdl.org/release/SDL2-devel-2.0.20-VC.zip" -OutFile "SDL2-devel-2.0.20-VC.zip" 36 | 7z x SDL2-devel-2.0.20-VC.zip 37 | Copy-Item "SDL2-2.0.20\lib\x64\SDL2*" "C:\Users\runneradmin\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib" 38 | - uses: actions/checkout@v2 39 | - uses: actions-rs/toolchain@v1 40 | with: 41 | toolchain: stable 42 | override: true 43 | - name: cargo fetch 44 | uses: actions-rs/cargo@v1 45 | with: 46 | command: fetch 47 | - name: cargo test 48 | uses: actions-rs/cargo@v1 49 | with: 50 | command: test 51 | args: --verbose 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI](https://github.com/kondrak/rust64/actions/workflows/rust.yml/badge.svg)](https://github.com/kondrak/rust64/actions/workflows/rust.yml) 2 | 3 | # Rust64 - a C64 emulator written in Rust 4 | This is my attempt to study the Rust programming language and have fun at the same time. The goal is to present in the least obfuscated way how the Commodore 64 works and what's happening behind the scenes once you start a program. Emulation is cycle based and fairly accurate at this point. 5 | 6 | The emulator has a built-in visual debugger which lets you view the contents of each memory page in RAM, Color RAM, VIC registers, CIA registers and SID registers. The VIC window is a ICU64-style raster debugger where each pixel represents one VIC cycle and any events occuring at that time. 7 | 8 | Major dependencies 9 | ------------------ 10 | - minifb: https://crates.io/crates/minifb (works out of the box) 11 | - sdl2: https://crates.io/crates/sdl2 (requires extra steps, see [here](https://github.com/AngryLawyer/rust-sdl2) for instructions) 12 | 13 | Requires Rust 1.58.0 or higher to compile and run. 14 | 15 | ### Youtube demo #1: 16 | [![Screenshot](images/rust64_youtube.png?raw=true)](https://www.youtube.com/watch?v=b6OSsTPwLaE) 17 | ### Youtube demo #2: 18 | [![Screenshot](images/rust64_youtube2.png?raw=true)](https://www.youtube.com/watch?v=g4d_1vPV6So) 19 | ### Screenshot: 20 | [![Screenshot](images/rust64_github_prev.png?raw=true)](images/rust64_github.png?raw=true) 21 | 22 | 23 | 24 | Build instructions 25 | ------------------ 26 | ``` 27 | cargo build 28 | cargo run --release 29 | ``` 30 | 31 | You can pass a .prg file as a command line parameter to load it into memory once the emulator boots (just type RUN to start the program): 32 | ``` 33 | cargo run --release prgs/colors.prg 34 | ``` 35 | To run with double-sized window: 36 | ``` 37 | cargo run --release x2 prgs/colors.prg 38 | ``` 39 | To run with double-sized window and debug windows enabled: 40 | ``` 41 | cargo run --release x2 debugger prgs/colors.prg 42 | ``` 43 | 44 | C64 and special key mappings 45 | ------------------- 46 | ``` 47 | ESC - Run/Stop 48 | END - Restore 49 | TAB - Control 50 | LCTRL - C= 51 | ` - <- 52 | - - + 53 | INS - & 54 | HOME - CLR/Home 55 | BSPACE - INST/DEL 56 | [ - @ 57 | ] - * 58 | DEL - ^ 59 | ; - : 60 | ' - ; 61 | \ - = 62 | F11 - start asm output to console (very slow!) 63 | F12 - reset C64 64 | RCTRL - joystick fire button 65 | NUMLOCK - toggle between joystick ports 1 and 2 (default: port 2) 66 | 67 | In debugger window: 68 | PGUP/PGDWN - flip currently displayed memory page 69 | HOME/END - switch currently displayed memory banks between RAM, Color RAM, VIC, CIA and SID 70 | ``` 71 | 72 | TODO 73 | ------------------ 74 | - serial bus/disk drives (d64, t64, tap) 75 | - implement remaining undocumented ops 76 | - switch from SDL2 to [cpal](https://github.com/tomaka/cpal) for audio once it supports OSX 77 | - improve SID emulation 78 | 79 | Known Issues 80 | ------------------ 81 | - missing serial bus may cause some very specific programs to perform incorrectly or get stuck in infinite loops 82 | - elaborate programs that require very precise timing are not running correctly yet 83 | 84 | This is an on-off WIP project, so update frequency may vary. 85 | 86 | Resources 87 | ------------------ 88 | The following documents and websites have been used to create this emulator: 89 | 90 | - http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt 91 | - http://frodo.cebix.net/ (inspired the VIC-II and SID implementaiton) 92 | - https://www.c64-wiki.com 93 | - http://www.oxyron.de/html/opcodes02.html 94 | - http://www.6502.org/tutorials/6502opcodes.html 95 | - http://www.pagetable.com/c64rom/c64rom_en.html 96 | - http://archive.6502.org/datasheets/mos_6526_cia.pdf 97 | - https://www.yoyogames.com/tech_blog/95 98 | - http://code.google.com/p/hmc-6502/source/browse/trunk/emu/testvectors/AllSuiteA.asm 99 | - https://t.co/J40UKu7RBf 100 | - http://www.waitingforfriday.com/index.php/Commodore_SID_6581_Datasheet 101 | - http://sta.c64.org/cbm64mem.html 102 | - https://svn.code.sf.net/p/vice-emu/code/testprogs/ 103 | - http://www.classiccmp.org/cini/pdf/Commodore/ds_6581.pdf 104 | 105 | Special thanks 106 | ------------------ 107 | - [Daniel Collin](https://twitter.com/daniel_collin) and Magnus "Pantaloon" Sjöberg for excessive test programs! 108 | - [Jake Taylor](https://twitter.com/ferristweetsnow) for general Rust tips! 109 | -------------------------------------------------------------------------------- /src/c64/vic_tables.rs: -------------------------------------------------------------------------------- 1 | // VIC sprite X expansion tables - as found in Frodo emulator 4.1b 2 | 3 | pub const EXP_TABLE: [u16; 256] = [ 4 | 0x0000, 0x0003, 0x000C, 0x000F, 0x0030, 0x0033, 0x003C, 0x003F, 5 | 0x00C0, 0x00C3, 0x00CC, 0x00CF, 0x00F0, 0x00F3, 0x00FC, 0x00FF, 6 | 0x0300, 0x0303, 0x030C, 0x030F, 0x0330, 0x0333, 0x033C, 0x033F, 7 | 0x03C0, 0x03C3, 0x03CC, 0x03CF, 0x03F0, 0x03F3, 0x03FC, 0x03FF, 8 | 0x0C00, 0x0C03, 0x0C0C, 0x0C0F, 0x0C30, 0x0C33, 0x0C3C, 0x0C3F, 9 | 0x0CC0, 0x0CC3, 0x0CCC, 0x0CCF, 0x0CF0, 0x0CF3, 0x0CFC, 0x0CFF, 10 | 0x0F00, 0x0F03, 0x0F0C, 0x0F0F, 0x0F30, 0x0F33, 0x0F3C, 0x0F3F, 11 | 0x0FC0, 0x0FC3, 0x0FCC, 0x0FCF, 0x0FF0, 0x0FF3, 0x0FFC, 0x0FFF, 12 | 0x3000, 0x3003, 0x300C, 0x300F, 0x3030, 0x3033, 0x303C, 0x303F, 13 | 0x30C0, 0x30C3, 0x30CC, 0x30CF, 0x30F0, 0x30F3, 0x30FC, 0x30FF, 14 | 0x3300, 0x3303, 0x330C, 0x330F, 0x3330, 0x3333, 0x333C, 0x333F, 15 | 0x33C0, 0x33C3, 0x33CC, 0x33CF, 0x33F0, 0x33F3, 0x33FC, 0x33FF, 16 | 0x3C00, 0x3C03, 0x3C0C, 0x3C0F, 0x3C30, 0x3C33, 0x3C3C, 0x3C3F, 17 | 0x3CC0, 0x3CC3, 0x3CCC, 0x3CCF, 0x3CF0, 0x3CF3, 0x3CFC, 0x3CFF, 18 | 0x3F00, 0x3F03, 0x3F0C, 0x3F0F, 0x3F30, 0x3F33, 0x3F3C, 0x3F3F, 19 | 0x3FC0, 0x3FC3, 0x3FCC, 0x3FCF, 0x3FF0, 0x3FF3, 0x3FFC, 0x3FFF, 20 | 0xC000, 0xC003, 0xC00C, 0xC00F, 0xC030, 0xC033, 0xC03C, 0xC03F, 21 | 0xC0C0, 0xC0C3, 0xC0CC, 0xC0CF, 0xC0F0, 0xC0F3, 0xC0FC, 0xC0FF, 22 | 0xC300, 0xC303, 0xC30C, 0xC30F, 0xC330, 0xC333, 0xC33C, 0xC33F, 23 | 0xC3C0, 0xC3C3, 0xC3CC, 0xC3CF, 0xC3F0, 0xC3F3, 0xC3FC, 0xC3FF, 24 | 0xCC00, 0xCC03, 0xCC0C, 0xCC0F, 0xCC30, 0xCC33, 0xCC3C, 0xCC3F, 25 | 0xCCC0, 0xCCC3, 0xCCCC, 0xCCCF, 0xCCF0, 0xCCF3, 0xCCFC, 0xCCFF, 26 | 0xCF00, 0xCF03, 0xCF0C, 0xCF0F, 0xCF30, 0xCF33, 0xCF3C, 0xCF3F, 27 | 0xCFC0, 0xCFC3, 0xCFCC, 0xCFCF, 0xCFF0, 0xCFF3, 0xCFFC, 0xCFFF, 28 | 0xF000, 0xF003, 0xF00C, 0xF00F, 0xF030, 0xF033, 0xF03C, 0xF03F, 29 | 0xF0C0, 0xF0C3, 0xF0CC, 0xF0CF, 0xF0F0, 0xF0F3, 0xF0FC, 0xF0FF, 30 | 0xF300, 0xF303, 0xF30C, 0xF30F, 0xF330, 0xF333, 0xF33C, 0xF33F, 31 | 0xF3C0, 0xF3C3, 0xF3CC, 0xF3CF, 0xF3F0, 0xF3F3, 0xF3FC, 0xF3FF, 32 | 0xFC00, 0xFC03, 0xFC0C, 0xFC0F, 0xFC30, 0xFC33, 0xFC3C, 0xFC3F, 33 | 0xFCC0, 0xFCC3, 0xFCCC, 0xFCCF, 0xFCF0, 0xFCF3, 0xFCFC, 0xFCFF, 34 | 0xFF00, 0xFF03, 0xFF0C, 0xFF0F, 0xFF30, 0xFF33, 0xFF3C, 0xFF3F, 35 | 0xFFC0, 0xFFC3, 0xFFCC, 0xFFCF, 0xFFF0, 0xFFF3, 0xFFFC, 0xFFFF 36 | ]; 37 | 38 | pub const MULTI_EXP_TABLE: [u16; 256] = [ 39 | 0x0000, 0x0005, 0x000A, 0x000F, 0x0050, 0x0055, 0x005A, 0x005F, 40 | 0x00A0, 0x00A5, 0x00AA, 0x00AF, 0x00F0, 0x00F5, 0x00FA, 0x00FF, 41 | 0x0500, 0x0505, 0x050A, 0x050F, 0x0550, 0x0555, 0x055A, 0x055F, 42 | 0x05A0, 0x05A5, 0x05AA, 0x05AF, 0x05F0, 0x05F5, 0x05FA, 0x05FF, 43 | 0x0A00, 0x0A05, 0x0A0A, 0x0A0F, 0x0A50, 0x0A55, 0x0A5A, 0x0A5F, 44 | 0x0AA0, 0x0AA5, 0x0AAA, 0x0AAF, 0x0AF0, 0x0AF5, 0x0AFA, 0x0AFF, 45 | 0x0F00, 0x0F05, 0x0F0A, 0x0F0F, 0x0F50, 0x0F55, 0x0F5A, 0x0F5F, 46 | 0x0FA0, 0x0FA5, 0x0FAA, 0x0FAF, 0x0FF0, 0x0FF5, 0x0FFA, 0x0FFF, 47 | 0x5000, 0x5005, 0x500A, 0x500F, 0x5050, 0x5055, 0x505A, 0x505F, 48 | 0x50A0, 0x50A5, 0x50AA, 0x50AF, 0x50F0, 0x50F5, 0x50FA, 0x50FF, 49 | 0x5500, 0x5505, 0x550A, 0x550F, 0x5550, 0x5555, 0x555A, 0x555F, 50 | 0x55A0, 0x55A5, 0x55AA, 0x55AF, 0x55F0, 0x55F5, 0x55FA, 0x55FF, 51 | 0x5A00, 0x5A05, 0x5A0A, 0x5A0F, 0x5A50, 0x5A55, 0x5A5A, 0x5A5F, 52 | 0x5AA0, 0x5AA5, 0x5AAA, 0x5AAF, 0x5AF0, 0x5AF5, 0x5AFA, 0x5AFF, 53 | 0x5F00, 0x5F05, 0x5F0A, 0x5F0F, 0x5F50, 0x5F55, 0x5F5A, 0x5F5F, 54 | 0x5FA0, 0x5FA5, 0x5FAA, 0x5FAF, 0x5FF0, 0x5FF5, 0x5FFA, 0x5FFF, 55 | 0xA000, 0xA005, 0xA00A, 0xA00F, 0xA050, 0xA055, 0xA05A, 0xA05F, 56 | 0xA0A0, 0xA0A5, 0xA0AA, 0xA0AF, 0xA0F0, 0xA0F5, 0xA0FA, 0xA0FF, 57 | 0xA500, 0xA505, 0xA50A, 0xA50F, 0xA550, 0xA555, 0xA55A, 0xA55F, 58 | 0xA5A0, 0xA5A5, 0xA5AA, 0xA5AF, 0xA5F0, 0xA5F5, 0xA5FA, 0xA5FF, 59 | 0xAA00, 0xAA05, 0xAA0A, 0xAA0F, 0xAA50, 0xAA55, 0xAA5A, 0xAA5F, 60 | 0xAAA0, 0xAAA5, 0xAAAA, 0xAAAF, 0xAAF0, 0xAAF5, 0xAAFA, 0xAAFF, 61 | 0xAF00, 0xAF05, 0xAF0A, 0xAF0F, 0xAF50, 0xAF55, 0xAF5A, 0xAF5F, 62 | 0xAFA0, 0xAFA5, 0xAFAA, 0xAFAF, 0xAFF0, 0xAFF5, 0xAFFA, 0xAFFF, 63 | 0xF000, 0xF005, 0xF00A, 0xF00F, 0xF050, 0xF055, 0xF05A, 0xF05F, 64 | 0xF0A0, 0xF0A5, 0xF0AA, 0xF0AF, 0xF0F0, 0xF0F5, 0xF0FA, 0xF0FF, 65 | 0xF500, 0xF505, 0xF50A, 0xF50F, 0xF550, 0xF555, 0xF55A, 0xF55F, 66 | 0xF5A0, 0xF5A5, 0xF5AA, 0xF5AF, 0xF5F0, 0xF5F5, 0xF5FA, 0xF5FF, 67 | 0xFA00, 0xFA05, 0xFA0A, 0xFA0F, 0xFA50, 0xFA55, 0xFA5A, 0xFA5F, 68 | 0xFAA0, 0xFAA5, 0xFAAA, 0xFAAF, 0xFAF0, 0xFAF5, 0xFAFA, 0xFAFF, 69 | 0xFF00, 0xFF05, 0xFF0A, 0xFF0F, 0xFF50, 0xFF55, 0xFF5A, 0xFF5F, 70 | 0xFFA0, 0xFFA5, 0xFFAA, 0xFFAF, 0xFFF0, 0xFFF5, 0xFFFA, 0xFFFF 71 | ]; 72 | -------------------------------------------------------------------------------- /src/debugger/font.rs: -------------------------------------------------------------------------------- 1 | // bitmap font used in debugger window 2 | use utils; 3 | 4 | pub struct SysFont { 5 | data: Vec 6 | } 7 | 8 | 9 | impl SysFont { 10 | pub fn new() -> SysFont { 11 | let mut font = SysFont { 12 | data: Vec::::new(), 13 | }; 14 | 15 | // lazily skipping the BMP header to actual data 16 | let bmp_data = utils::open_file("res/font.bmp", 54); 17 | 18 | let mut j: i32 = 256*63*3; 19 | let mut i; 20 | 21 | while j >= 0 { 22 | i = 0; 23 | while i < 256 * 3 { 24 | let color = if bmp_data[i + j as usize] != 0 { 1 } else { 0 }; 25 | font.data.push(color); 26 | i+= 3; 27 | } 28 | 29 | j -= 256 * 3; 30 | } 31 | 32 | font 33 | } 34 | 35 | pub fn draw_text_rgb(&self, window_buffer: &mut Vec, window_w: usize, x: usize, y: usize, text: &str, color: u32) { 36 | let chars: Vec = text.chars().collect(); 37 | for i in 0..text.len() { 38 | self.draw_char_rgb(window_buffer, window_w, x*8 + 8*i as usize, y*8 as usize, self.ascii_to_petscii(chars[i]), color); 39 | } 40 | } 41 | 42 | pub fn draw_text(&self, window_buffer: &mut Vec, window_w: usize, x: usize, y: usize, text: &str, c64_color: u8) { 43 | let chars: Vec = text.chars().collect(); 44 | for i in 0..text.len() { 45 | self.draw_char(window_buffer, window_w, x*8 + 8*i as usize, y*8 as usize, self.ascii_to_petscii(chars[i]), c64_color); 46 | } 47 | } 48 | 49 | pub fn draw_char(&self, window_buffer: &mut Vec, window_w: usize, x: usize, y: usize, charcode: u8, c64_color: u8) { 50 | self.draw_char_rgb(window_buffer, window_w, x, y, charcode, utils::fetch_c64_color_rgba(c64_color)); 51 | } 52 | 53 | pub fn draw_char_rgb(&self, window_buffer: &mut Vec, window_w: usize, x: usize, y: usize, charcode: u8, color: u32) { 54 | let char_w: i32 = 8; 55 | let char_h: i32 = 8; 56 | let data_x = char_w * (charcode % 32) as i32; 57 | let data_y = char_h * (charcode / 32) as i32; 58 | let data_w = data_x + char_w; 59 | let data_h = data_y + char_h; 60 | 61 | let mut k = 0; 62 | let mut l = 0; 63 | for i in data_y..data_h { 64 | for j in data_x..data_w { 65 | window_buffer[x + l + window_w * (y + k)] = self.data[j as usize + (i * 256) as usize] as u32 * color; 66 | l += 1; 67 | } 68 | l = 0; 69 | k += 1; 70 | } 71 | } 72 | 73 | fn ascii_to_petscii(&self, c_ascii: char) -> u8 { 74 | match c_ascii { 75 | '@' => 0, 76 | 'A' | 'a' => 1, 77 | 'B' | 'b' => 2, 78 | 'C' | 'c' => 3, 79 | 'D' | 'd' => 4, 80 | 'E' | 'e' => 5, 81 | 'F' | 'f' => 6, 82 | 'G' | 'g' => 7, 83 | 'H' | 'h' => 8, 84 | 'I' | 'i' => 9, 85 | 'J' | 'j' => 10, 86 | 'K' | 'k' => 11, 87 | 'L' | 'l' => 12, 88 | 'M' | 'm' => 13, 89 | 'N' | 'n' => 14, 90 | 'O' | 'o' => 15, 91 | 'P' | 'p' => 16, 92 | 'Q' | 'q' => 17, 93 | 'R' | 'r' => 18, 94 | 'S' | 's' => 19, 95 | 'T' | 't' => 20, 96 | 'U' | 'u' => 21, 97 | 'V' | 'v' => 22, 98 | 'W' | 'w' => 23, 99 | 'X' | 'x' => 24, 100 | 'Y' | 'y' => 25, 101 | 'Z' | 'z'=> 26, 102 | '[' => 27, 103 | ']' => 29, 104 | ' ' => 32, 105 | '!' => 33, 106 | '"' => 34, 107 | '#' => 35, 108 | '$' => 36, 109 | '%' => 37, 110 | '&' => 38, 111 | '`' => 39, 112 | '(' => 40, 113 | ')' => 41, 114 | '*' => 42, 115 | '+' => 43, 116 | ',' => 44, 117 | '-' => 45, 118 | '.' => 46, 119 | '/' => 47, 120 | '0' => 48, 121 | '1' => 49, 122 | '2' => 50, 123 | '3' => 51, 124 | '4' => 52, 125 | '5' => 53, 126 | '6' => 54, 127 | '7' => 55, 128 | '8' => 56, 129 | '9' => 57, 130 | ':' => 58, 131 | ';' => 59, 132 | '<' => 60, 133 | '=' => 61, 134 | '>' => 62, 135 | '?' => 63, 136 | _ => 63 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/c64/crt.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | use std::fs::File; 3 | use std::io::{Read, Seek, SeekFrom}; 4 | use std::str; 5 | use std::fmt; 6 | use c64::memory; 7 | 8 | use byteorder::{BigEndian, ReadBytesExt}; 9 | use num::FromPrimitive; 10 | 11 | #[derive(Debug)] 12 | pub struct Crt { 13 | header: Header, 14 | chips: Vec, 15 | } 16 | 17 | impl Crt { 18 | pub fn from_filename(filename: &str) -> Result { 19 | let mut file = File::open(filename).map_err(|e| e.to_string())?; 20 | 21 | // Read Header 22 | let mut signature = [0u8; 16]; 23 | file.read(&mut signature).map_err(|e| e.to_string())?; 24 | if &signature != b"C64 CARTRIDGE " { 25 | return Err("Invalid cartridge signature".to_string()) 26 | } 27 | let header_len = file.read_u32::().map_err(|e| e.to_string())?; 28 | let mut version = [0u8;2]; 29 | file.read(&mut version).map_err(|e| e.to_string())?; 30 | let hw_type = file.read_u16::().map_err(|e| e.to_string())?; 31 | if hw_type != 0 { 32 | return Err("Unsupported cartridge type".to_string()) 33 | } 34 | let exrom = file.read_u8().map_err(|e| e.to_string())?; 35 | let game = file.read_u8().map_err(|e| e.to_string())?; 36 | file.seek(SeekFrom::Start(0x20)).map_err(|e| e.to_string())?; 37 | let mut name = [0u8; 32]; 38 | file.read(&mut name).map_err(|e| e.to_string())?; 39 | 40 | // Read Chips 41 | file.seek(SeekFrom::Start(header_len as u64)).map_err(|e| e.to_string())?; 42 | let mut chips: Vec = Vec::new(); 43 | loop { 44 | let mut chip_signature = [0u8;4]; 45 | file.read(&mut chip_signature).map_err(|e| e.to_string())?; 46 | if &chip_signature != b"CHIP" { 47 | break; 48 | } 49 | let length = file.read_u32::().map_err(|e| e.to_string())?; 50 | let chip_type = ChipType::from_u16(file.read_u16::() 51 | .map_err(|e| e.to_string())?).ok_or("Invalid chip type".to_string())?; 52 | let bank_number = file.read_u16::().map_err(|e| e.to_string())?; 53 | let load_addr = file.read_u16::().map_err(|e| e.to_string())?; 54 | let data_size = file.read_u16::().map_err(|e| e.to_string())?; 55 | let mut data: Vec = vec![0u8; data_size as usize]; 56 | file.read(&mut data).map_err(|e| e.to_string())?; 57 | 58 | chips.push(Chip { 59 | signature: chip_signature, 60 | length: length, 61 | chip_type: chip_type, 62 | bank_number: bank_number, 63 | load_addr: load_addr, 64 | data_size: data_size, 65 | data: data, 66 | }); 67 | } 68 | 69 | 70 | Ok(Crt { 71 | header: Header { 72 | signature: signature, 73 | header_len: header_len, 74 | version: version, 75 | hw_type: hw_type, 76 | exrom: exrom, 77 | game: game, 78 | name: name, 79 | }, 80 | chips: chips, 81 | }) 82 | } 83 | 84 | pub fn load_into_memory(&self, mut memory: std::cell::RefMut) { 85 | memory.exrom = self.header.exrom == 1; 86 | memory.game = self.header.game == 1; 87 | for chip in self.chips.iter() { 88 | let base_addr = chip.load_addr; 89 | for (offset, byte) in chip.data.iter().enumerate() { 90 | memory.write_byte(base_addr+offset as u16, *byte); 91 | } 92 | } 93 | } 94 | } 95 | 96 | struct Header { 97 | signature: [u8; 16], 98 | header_len: u32, 99 | version: [u8; 2], 100 | hw_type: u16, 101 | exrom: u8, 102 | game: u8, 103 | // 001A-001F RFU 104 | name: [u8; 32], 105 | } 106 | 107 | impl fmt::Debug for Header { 108 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 109 | write!(f, 110 | "Header {{ 111 | signature: {}, 112 | header_len: {} bytes, 113 | version: {:x}.{:02x} 114 | hw_type: {}, 115 | exrom: {}, 116 | game: {}, 117 | name: {} 118 | }}", 119 | str::from_utf8(&self.signature).unwrap(), 120 | self.header_len, 121 | self.version[0], 122 | self.version[1], 123 | self.hw_type, 124 | self.exrom, 125 | self.game, 126 | str::from_utf8(&self.name).unwrap() 127 | ) 128 | } 129 | } 130 | 131 | struct Chip { 132 | signature: [u8; 4], 133 | length: u32, // header and data combined 134 | chip_type: ChipType, 135 | bank_number: u16, 136 | load_addr: u16, 137 | data_size: u16, 138 | data: Vec, 139 | } 140 | 141 | impl fmt::Debug for Chip { 142 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 143 | write!(f, 144 | "Chip {{ 145 | signature: {}, 146 | length: {} bytes, 147 | chip_type: {:?}, 148 | bank_number: {}, 149 | load_addr: 0x{:04x}, 150 | data_size: {} bytes, 151 | data: (not shown) 152 | }}", 153 | str::from_utf8(&self.signature).unwrap(), 154 | self.length, 155 | self.chip_type, 156 | self.bank_number, 157 | self.load_addr, 158 | self.data_size 159 | ) 160 | } 161 | } 162 | 163 | enum_from_primitive! { 164 | #[derive(Debug, PartialEq)] 165 | enum ChipType { 166 | ROM, 167 | RAM, 168 | Flash, 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/c64/mod.rs: -------------------------------------------------------------------------------- 1 | // main module for C64 updates 2 | extern crate minifb; 3 | 4 | pub mod cpu; 5 | pub mod memory; 6 | pub mod opcodes; 7 | pub mod vic; 8 | pub mod crt; 9 | 10 | mod cia; 11 | mod clock; 12 | mod io; 13 | mod sid; 14 | mod sid_tables; 15 | mod vic_tables; 16 | 17 | use debugger; 18 | use minifb::*; 19 | use utils; 20 | 21 | 22 | pub const SCREEN_WIDTH: usize = 384; // extend 20 pixels left and right for the borders 23 | pub const SCREEN_HEIGHT: usize = 272; // extend 36 pixels top and down for the borders 24 | 25 | // PAL clock frequency in Hz 26 | const CLOCK_FREQ: f64 = 1.5 * 985248.0; 27 | 28 | 29 | pub struct C64 { 30 | pub main_window: minifb::Window, 31 | pub file_to_load: String, 32 | pub crt_to_load: String, 33 | memory: memory::MemShared, 34 | io: io::IO, 35 | clock: clock::Clock, 36 | cpu: cpu::CPUShared, 37 | cia1: cia::CIAShared, 38 | cia2: cia::CIAShared, 39 | vic: vic::VICShared, 40 | sid: sid::SIDShared, 41 | 42 | debugger: Option, 43 | powered_on: bool, 44 | boot_complete: bool, 45 | cycle_count: u32, 46 | } 47 | 48 | impl C64 { 49 | pub fn new(window_scale: Scale, debugger_on: bool, prg_to_load: &str, crt_to_load: &str) -> C64 { 50 | let memory = memory::Memory::new_shared(); 51 | let vic = vic::VIC::new_shared(); 52 | let cia1 = cia::CIA::new_shared(true); 53 | let cia2 = cia::CIA::new_shared(false); 54 | let cpu = cpu::CPU::new_shared(); 55 | let sid = sid::SID::new_shared(); 56 | 57 | let mut c64 = C64 { 58 | main_window: Window::new("Rust64", SCREEN_WIDTH, SCREEN_HEIGHT, WindowOptions { scale: window_scale, ..Default::default() }).unwrap(), 59 | file_to_load: String::from(prg_to_load), 60 | crt_to_load: String::from(crt_to_load), 61 | memory: memory.clone(), // shared system memory (RAM, ROM, IO registers) 62 | io: io::IO::new(), 63 | clock: clock::Clock::new(CLOCK_FREQ), 64 | cpu: cpu.clone(), 65 | cia1: cia1.clone(), 66 | cia2: cia2.clone(), 67 | vic: vic.clone(), 68 | sid: sid.clone(), 69 | debugger: if debugger_on { Some(debugger::Debugger::new()) } else { None }, 70 | powered_on: false, 71 | boot_complete: false, 72 | cycle_count: 0, 73 | }; 74 | 75 | c64.main_window.set_position(75, 20); 76 | 77 | // cyclic dependencies are not possible in Rust (yet?), so we have 78 | // to resort to setting references manually 79 | c64.cia1.borrow_mut().set_references(memory.clone(), cpu.clone(), vic.clone()); 80 | c64.cia2.borrow_mut().set_references(memory.clone(), cpu.clone(), vic.clone()); 81 | c64.vic.borrow_mut().set_references(memory.clone(), cpu.clone()); 82 | c64.sid.borrow_mut().set_references(memory.clone()); 83 | c64.cpu.borrow_mut().set_references(memory.clone(), vic.clone(), cia1.clone(), cia2.clone(), sid.clone()); 84 | 85 | drop(memory); 86 | drop(cia1); 87 | drop(cia2); 88 | drop(vic); 89 | drop(cpu); 90 | drop(sid); 91 | 92 | c64 93 | } 94 | 95 | 96 | pub fn reset(&mut self) { 97 | self.memory.borrow_mut().reset(); 98 | self.cpu.borrow_mut().reset(); 99 | self.cia1.borrow_mut().reset(); 100 | self.cia2.borrow_mut().reset(); 101 | self.sid.borrow_mut().reset(); 102 | } 103 | 104 | 105 | pub fn run(&mut self) { 106 | // attempt to load a program supplied with command line 107 | if !self.powered_on { 108 | // $FCE2 is the power-on reset routine, which searches for and starts 109 | // a cartridge amongst other things. The cartridge must be loaded here 110 | self.powered_on = self.cpu.borrow_mut().pc == 0xFCE2; 111 | if self.powered_on { 112 | let crt_file = &self.crt_to_load.to_owned()[..]; 113 | if crt_file.len() > 0 { 114 | let crt = crt::Crt::from_filename(crt_file).unwrap(); 115 | println!("{:?}", crt); 116 | crt.load_into_memory(self.memory.borrow_mut()); 117 | } 118 | } 119 | } 120 | 121 | if !self.boot_complete { 122 | // $A480 is the BASIC warm start sequence - safe to assume we can load a cmdline program now 123 | self.boot_complete = self.cpu.borrow_mut().pc == 0xA480; 124 | 125 | if self.boot_complete { 126 | let prg_file = &self.file_to_load.to_owned()[..]; 127 | 128 | if prg_file.len() > 0 { 129 | self.boot_complete = true; self.load_prg(prg_file); 130 | } 131 | } 132 | } 133 | 134 | // main C64 update - use the clock to time all the operations 135 | if self.clock.tick() { 136 | let mut should_trigger_vblank = false; 137 | 138 | if self.vic.borrow_mut().update(self.cycle_count, &mut should_trigger_vblank) { 139 | self.sid.borrow_mut().update(); 140 | } 141 | 142 | self.cia1.borrow_mut().process_irq(); 143 | self.cia2.borrow_mut().process_irq(); 144 | self.cia1.borrow_mut().update(); 145 | self.cia2.borrow_mut().update(); 146 | 147 | self.cpu.borrow_mut().update(self.cycle_count); 148 | 149 | // update the debugger window if it exists 150 | match self.debugger { 151 | Some(ref mut dbg) => { 152 | dbg.update_vic_window(&mut self.vic); 153 | if should_trigger_vblank { 154 | dbg.render(&mut self.cpu, &mut self.memory); 155 | } 156 | }, 157 | None => (), 158 | } 159 | 160 | // redraw the screen and process input on VBlank 161 | if should_trigger_vblank { 162 | let _ = self.main_window.update_with_buffer(&self.vic.borrow_mut().window_buffer, SCREEN_WIDTH, SCREEN_HEIGHT); 163 | self.io.update(&self.main_window, &mut self.cia1); 164 | self.cia1.borrow_mut().count_tod(); 165 | self.cia2.borrow_mut().count_tod(); 166 | 167 | if self.io.check_restore_key(&self.main_window) { 168 | self.cpu.borrow_mut().set_nmi(true); 169 | } 170 | } 171 | 172 | // process special keys: console ASM output and reset switch 173 | if self.main_window.is_key_pressed(Key::F11, KeyRepeat::No) { 174 | let di = self.cpu.borrow_mut().debug_instr; 175 | self.cpu.borrow_mut().debug_instr = !di; 176 | } 177 | 178 | if self.main_window.is_key_pressed(Key::F12, KeyRepeat::No) { 179 | self.reset(); 180 | } 181 | 182 | self.cycle_count += 1; 183 | } 184 | 185 | // update SDL2 audio buffers 186 | self.sid.borrow_mut().update_audio(); 187 | } 188 | 189 | 190 | // *** private functions *** // 191 | 192 | // load a *.prg file 193 | fn load_prg(&mut self, filename: &str) { 194 | let prg_data = utils::open_file(filename, 0); 195 | let start_address: u16 = ((prg_data[1] as u16) << 8) | (prg_data[0] as u16); 196 | println!("Loading {} to start location at ${:04x} ({})", filename, start_address, start_address); 197 | 198 | for i in 2..(prg_data.len()) { 199 | self.memory.borrow_mut().write_byte(start_address + (i as u16) - 2, prg_data[i]); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | // helper utility functions and macros 2 | use c64::cpu; 3 | use c64::opcodes; 4 | use std::io::prelude::*; 5 | use std::fs::File; 6 | use std::io::SeekFrom; 7 | use std::path::Path; 8 | 9 | 10 | // helper macros to easily extract references from Option> 11 | macro_rules! as_ref { 12 | ($x:expr) => ($x.as_ref().unwrap().borrow_mut()) 13 | } 14 | 15 | macro_rules! as_mut { 16 | ($x:expr) => ($x.as_mut().unwrap().borrow_mut()) 17 | } 18 | 19 | 20 | // common helper functions 21 | pub fn open_file(filename: &str, offset: u64) -> Vec { 22 | let path = Path::new(&filename); 23 | 24 | let mut file = match File::open(&path) { 25 | Err(why) => panic!("Couldn't open {}: {}", path.display(), why.to_string()), 26 | Ok(file) => file, 27 | }; 28 | 29 | let mut file_data = Vec::::new(); 30 | 31 | let _ = file.seek(SeekFrom::Start(offset)); 32 | let result = file.read_to_end(&mut file_data); 33 | 34 | match result { 35 | Err(why) => panic!("Error reading file: {}", why.to_string()), 36 | Ok(result) => println!("Read {}: {} bytes", path.display(), result), 37 | }; 38 | 39 | file_data 40 | } 41 | 42 | 43 | // set 8 consecutive buffer elements to single value for faster update of 44 | // a single 8-pixel screen chunk 45 | pub fn memset8(buffer: &mut [u32], start: usize, value: u32) { 46 | buffer[start] = value; 47 | buffer[start+1] = buffer[start]; 48 | buffer[start+2] = buffer[start]; 49 | buffer[start+3] = buffer[start]; 50 | buffer[start+4] = buffer[start]; 51 | buffer[start+5] = buffer[start]; 52 | buffer[start+6] = buffer[start]; 53 | buffer[start+7] = buffer[start]; 54 | } 55 | 56 | 57 | pub fn fetch_c64_color_rgba(idx: u8) -> u32 { 58 | // palette RGB values copied from WinVICE 59 | match idx & 0x0F { 60 | 0x00 => 0x00000000, 61 | 0x01 => 0x00FFFFFF, 62 | 0x02 => 0x00894036, 63 | 0x03 => 0x007ABFC7, 64 | 0x04 => 0x008A46AE, 65 | 0x05 => 0x0068A941, 66 | 0x06 => 0x003E31A2, 67 | 0x07 => 0x00D0DC71, 68 | 0x08 => 0x00905F25, 69 | 0x09 => 0x005C4700, 70 | 0x0A => 0x00BB776D, 71 | 0x0B => 0x00555555, 72 | 0x0C => 0x00808080, 73 | 0x0D => 0x00ACEA88, 74 | 0x0E => 0x007C70DA, 75 | 0x0F => 0x00ABABAB, 76 | _ => panic!("Unknown color!"), 77 | } 78 | } 79 | 80 | 81 | // instruction debugging 82 | pub struct OpDebugger { 83 | pub jump_queue: Vec 84 | } 85 | 86 | impl OpDebugger { 87 | pub fn new() -> OpDebugger { 88 | OpDebugger { 89 | jump_queue: Vec::::new() 90 | } 91 | } 92 | } 93 | 94 | 95 | // output current instruction and CPU register status in a neat, readable fashion 96 | pub fn debug_instruction(opcode: u8, cpu: &mut cpu::CPU) { 97 | cpu.prev_pc = cpu.pc; 98 | let prev_pc = cpu.prev_pc; 99 | 100 | let operand_hex: String; 101 | let operand: String; 102 | let mut extra_cycle = false; 103 | let debug_loops = true; // if true, every loop will be unrolled in the debug output 104 | 105 | // RTS? pop from queue to continue logging 106 | if !debug_loops { 107 | match cpu.instruction.opcode 108 | { 109 | opcodes::Op::RTS => { 110 | let _ = cpu.op_debugger.jump_queue.pop(); 111 | return; 112 | }, 113 | opcodes::Op::JSR => { 114 | if !cpu.op_debugger.jump_queue.is_empty() { 115 | cpu.op_debugger.jump_queue.push(opcode); 116 | return; 117 | } 118 | }, 119 | _ => { 120 | if !cpu.op_debugger.jump_queue.is_empty() { 121 | return; 122 | } 123 | } 124 | } 125 | } 126 | 127 | // instruction opcode and arglist formatting based on addressing mode 128 | match cpu.instruction.addr_mode { 129 | opcodes::AddrMode::Implied => { 130 | operand_hex = format!(" "); 131 | operand = format!(" "); 132 | }, 133 | opcodes::AddrMode::Accumulator => { 134 | operand_hex = format!(" "); 135 | operand = format!("A "); 136 | }, 137 | opcodes::AddrMode::Immediate => { 138 | operand_hex = format!(" {:02X} ", cpu.read_byte(prev_pc)); 139 | operand = format!("#${:02X} ", cpu.read_byte(prev_pc)); 140 | }, 141 | opcodes::AddrMode::Absolute => { 142 | operand_hex = format!(" {:02X} {:02X} ", cpu.read_byte(prev_pc), cpu.read_byte(prev_pc + 0x01)); 143 | operand = format!("${:04X} ", cpu.read_word_le(cpu.prev_pc)); 144 | }, 145 | opcodes::AddrMode::AbsoluteIndexedX(ec) => { 146 | extra_cycle = ec; 147 | operand_hex = format!(" {:02X} {:02X} ", cpu.read_byte(prev_pc), cpu.read_byte(prev_pc + 0x01)); 148 | operand = format!("${:04X},X", cpu.read_word_le(cpu.prev_pc)); 149 | }, 150 | opcodes::AddrMode::AbsoluteIndexedY(ec) => { 151 | extra_cycle = ec; 152 | operand_hex = format!(" {:02X} {:02X} ", cpu.read_byte(prev_pc), cpu.read_byte(prev_pc + 0x01)); 153 | operand = format!("${:04X},Y", cpu.read_word_le(cpu.prev_pc)); 154 | }, 155 | opcodes::AddrMode::Zeropage => { 156 | operand_hex = format!(" {:02X} ", cpu.read_byte(prev_pc)); 157 | operand = format!("${:02X} ", cpu.read_byte(prev_pc)); 158 | }, 159 | opcodes::AddrMode::ZeropageIndexedX => { 160 | operand_hex = format!(" {:02X} ", cpu.read_byte(prev_pc)); 161 | operand = format!("${:02X},X ", cpu.read_byte(prev_pc)); 162 | }, 163 | opcodes::AddrMode::ZeropageIndexedY => { 164 | operand_hex = format!(" {:02X} ", cpu.read_byte(prev_pc)); 165 | operand = format!("${:02X},Y ", cpu.read_byte(prev_pc)); 166 | }, 167 | opcodes::AddrMode::Relative => { 168 | operand_hex = format!(" {:02X} ", cpu.read_byte(prev_pc)); 169 | let b: i8 = cpu.read_byte(prev_pc) as i8; 170 | operand = format!("${:04X} ", ((cpu.prev_pc + 1) as i16 + b as i16) as u16); 171 | }, 172 | opcodes::AddrMode::Indirect => { 173 | operand_hex = format!(" {:02X} {:02X} ", cpu.read_byte(prev_pc), cpu.read_byte(prev_pc + 0x01)); 174 | operand = format!("(${:04X})", cpu.read_word_le(cpu.prev_pc)); 175 | }, 176 | opcodes::AddrMode::IndexedIndirectX => { 177 | operand_hex = format!(" {:02X} ", cpu.read_byte(prev_pc)); 178 | operand = format!("(${:02X},X)", cpu.read_byte(prev_pc)); 179 | }, 180 | opcodes::AddrMode::IndirectIndexedY(ec) => { 181 | extra_cycle = ec; 182 | operand_hex = format!(" {:02X} ", cpu.read_byte(prev_pc)); 183 | operand = format!("(${:02X}),Y", cpu.read_byte(prev_pc)); 184 | }, 185 | } 186 | 187 | // control latch bytes' status 188 | let byte0 = cpu.read_byte(0x0000); 189 | let byte1 = cpu.read_byte(0x0001); 190 | 191 | let mut total_cycles = cpu.instruction.cycles_to_fetch + cpu.instruction.cycles_to_run + cpu.instruction.cycles_to_rmw; 192 | let mut fetch_cycles = cpu.instruction.cycles_to_fetch; 193 | let mut extra_cycle_mark = "*"; 194 | 195 | if !extra_cycle { 196 | extra_cycle_mark = " "; 197 | total_cycles += 1; 198 | fetch_cycles += 1; 199 | } 200 | 201 | let rmw_mark = if cpu.instruction.cycles_to_rmw > 0 { "+" } else { " " }; 202 | 203 | println!("${:04X}: {:02X}{}{}{} {} {}<- A: {:02X} X: {:02X} Y: {:02X} SP: {:02X} 00: {:02X} 01: {:02X} NV-BDIZC: [{:08b}] ({} cls, f: {}, r: {})", cpu.prev_pc - 1, opcode, operand_hex, extra_cycle_mark, cpu.instruction, operand,rmw_mark, cpu.a, cpu.x, cpu.y, cpu.sp, byte0, byte1, cpu.p, total_cycles, fetch_cycles, cpu.instruction.cycles_to_run); 204 | 205 | // JSR? push on queue to supress logging 206 | if !debug_loops { 207 | match cpu.instruction.opcode { 208 | opcodes::Op::JSR => cpu.op_debugger.jump_queue.push(opcode), 209 | _ => () 210 | } 211 | } 212 | } 213 | 214 | -------------------------------------------------------------------------------- /src/c64/memory.rs: -------------------------------------------------------------------------------- 1 | // memory banks 2 | use std::cell::RefCell; 3 | use std::rc::Rc; 4 | use utils; 5 | 6 | pub type MemShared = Rc>; 7 | 8 | pub enum MemType { 9 | Ram, 10 | Basic, 11 | Chargen, 12 | Io, 13 | Kernal, 14 | } 15 | 16 | 17 | // specific memory bank - RAM, ROM, IO 18 | pub struct MemBank { 19 | bank_type: MemType, // what am I? 20 | read_only: bool, // RAM or ROM? 21 | offset: u16, // offset from start of address space 22 | data: Vec, 23 | } 24 | 25 | impl MemBank { 26 | pub fn new(mem_type: MemType) -> MemBank { 27 | let mut mem_bank = MemBank { 28 | bank_type: mem_type, 29 | read_only: true, 30 | offset: 0x0000, 31 | data: Vec::::new(), 32 | }; 33 | 34 | match mem_bank.bank_type { 35 | MemType::Basic => { 36 | mem_bank.data = utils::open_file("rom/basic.rom", 0); 37 | mem_bank.offset = 0xA000; 38 | }, 39 | MemType::Chargen => { 40 | mem_bank.data = utils::open_file("rom/chargen.rom", 0); 41 | mem_bank.offset = 0xD000; 42 | }, 43 | MemType::Kernal => { 44 | mem_bank.data = utils::open_file("rom/kernal.rom", 0); 45 | mem_bank.offset = 0xE000; 46 | }, 47 | MemType::Ram => { 48 | mem_bank.data = Vec::::with_capacity(0x10000); 49 | for _ in 0..0x10000 { 50 | mem_bank.data.push(0); 51 | } 52 | 53 | mem_bank.read_only = false; 54 | }, 55 | MemType::Io => { 56 | mem_bank.data = Vec::::with_capacity(0x1000); 57 | for _ in 0..0x1000 { 58 | mem_bank.data.push(0); 59 | } 60 | 61 | mem_bank.offset = 0xD000; 62 | mem_bank.read_only = false; 63 | } 64 | } 65 | 66 | mem_bank 67 | } 68 | 69 | 70 | pub fn write(&mut self, addr: u16, val: u8) { 71 | match self.bank_type { 72 | MemType::Ram => self.data[(addr - self.offset) as usize] = val, 73 | MemType::Io => { 74 | match addr { 75 | 0xD016 => self.data[(addr - self.offset) as usize] = 0xC0 | val, 76 | 0xD019 => self.data[(addr - self.offset) as usize] = 0x70 | val, 77 | 0xD01A => self.data[(addr - self.offset) as usize] = 0xF0 | val, 78 | //0xD01E..=0xD01F => (), // cannot be written on real C64 but allow the VIC to do it anyway 79 | 0xD020..=0xD02E => self.data[(addr - self.offset) as usize] = 0xF0 | val, 80 | 0xD02F..=0xD03F => (), // write ignored 81 | 0xD040..=0xD3FF => self.write(0xD000 + (addr % 0x0040), val), // same as 0xD000-0xD03F 82 | _ => self.data[(addr - self.offset) as usize] = val 83 | } 84 | 85 | }, 86 | _ => panic!("Can't write to ROM!") 87 | } 88 | } 89 | 90 | 91 | pub fn read(&mut self, addr: u16) -> u8 { 92 | match self.bank_type { 93 | MemType::Io => { 94 | match addr { 95 | 0xD016 => 0xC0 | self.data[(addr - self.offset) as usize], 96 | 0xD018 => 0x01 | self.data[(addr - self.offset) as usize], 97 | 0xD019 => 0x70 | self.data[(addr - self.offset) as usize], 98 | 0xD01A => 0xF0 | self.data[(addr - self.offset) as usize], 99 | 0xD01E..=0xD01F => { // cannot be written, cleared on read 100 | let value = self.data[(addr - self.offset) as usize]; 101 | self.data[(addr - self.offset) as usize] = 0; 102 | value 103 | }, 104 | 0xD020..=0xD02E => 0xF0 | self.data[(addr - self.offset) as usize], 105 | 0xD02F..=0xD03F => 0xFF, // always returns 0xFF 106 | 0xD040..=0xD3FF => self.read(0xD000 + (addr % 0x0040)), // same as 0xD000-0xD03F 107 | _ => self.data[(addr - self.offset) as usize] 108 | } 109 | }, 110 | _ => self.data[(addr - self.offset) as usize] 111 | } 112 | } 113 | } 114 | 115 | 116 | // collective memory storage with all the banks and bank switching support 117 | pub struct Memory { 118 | ram: MemBank, 119 | basic: MemBank, 120 | chargen: MemBank, 121 | io: MemBank, 122 | kernal: MemBank, 123 | 124 | // bank switching flags 125 | pub exrom: bool, 126 | pub game: bool, 127 | pub basic_on: bool, 128 | pub chargen_on: bool, 129 | pub io_on: bool, 130 | pub kernal_on: bool, 131 | } 132 | 133 | impl Memory { 134 | pub fn new_shared() -> MemShared { 135 | Rc::new(RefCell::new(Memory { 136 | ram: MemBank::new(MemType::Ram), // 64k 137 | basic: MemBank::new(MemType::Basic), // 8k 138 | chargen: MemBank::new(MemType::Chargen), // 4k 139 | io: MemBank::new(MemType::Io), // 4k (VIC, SID, CIA, Color RAM) 140 | kernal: MemBank::new(MemType::Kernal), // 8k 141 | exrom: true, 142 | game: true, 143 | basic_on: false, 144 | chargen_on: false, 145 | io_on: false, 146 | kernal_on: false, 147 | })) 148 | } 149 | 150 | 151 | // returns memory bank for current latch setting and address 152 | pub fn get_bank(&mut self, addr: u16) -> &mut MemBank { 153 | match addr { 154 | 0x0000..=0x9FFF => &mut self.ram, 155 | 0xA000..=0xBFFF => if self.basic_on { &mut self.basic } else { &mut self.ram }, 156 | 0xC000..=0xCFFF => &mut self.ram, 157 | 0xD000..=0xDFFF => { 158 | if self.chargen_on { return &mut self.chargen } 159 | if self.io_on { return &mut self.io; } 160 | return &mut self.ram; 161 | }, 162 | 0xE000..=0xFFFF => if self.kernal_on { &mut self.kernal } else { &mut self.ram } 163 | } 164 | } 165 | 166 | 167 | // returns specific modifiable memory bank 168 | pub fn get_ram_bank(&mut self, bank_type: MemType) -> &mut MemBank { 169 | match bank_type { 170 | MemType::Ram => &mut self.ram, 171 | MemType::Io => &mut self.io, 172 | _ => panic!("Unrecognized RAM bank"), 173 | } 174 | } 175 | 176 | 177 | // returns specific non-modifiable memory bank 178 | pub fn get_rom_bank(&mut self, bank_type: MemType) -> &mut MemBank { 179 | match bank_type { 180 | MemType::Basic => &mut self.basic, 181 | MemType::Chargen => &mut self.chargen, 182 | MemType::Kernal => &mut self.kernal, 183 | _ => panic!("Unrecognized ROM Abank"), 184 | } 185 | } 186 | 187 | 188 | pub fn reset(&mut self) { 189 | self.write_byte(0x0000, 0xFF); 190 | self.write_byte(0x0001, 0x07); // enable kernal, chargen and basic ROMs 191 | } 192 | 193 | 194 | // Write a byte to memory - returns whether RAM was written (true) or RAM under ROM (false) 195 | pub fn write_byte(&mut self, addr: u16, value: u8) -> bool { 196 | // RAM under ROM written? Return false to let us know about it 197 | if self.get_bank(addr).read_only { 198 | self.ram.write(addr, value); 199 | return false; 200 | } 201 | else { 202 | self.get_bank(addr).write(addr, value); 203 | } 204 | 205 | // update the bank switching flags here, since they can only change on memory write 206 | // latch byte changed - update bank switching flags 207 | if addr < 0x0002 { 208 | self.update_memory_latch(); 209 | } 210 | 211 | return true; 212 | } 213 | 214 | 215 | // Read a byte from memory 216 | pub fn read_byte(&mut self, addr: u16) -> u8 { 217 | // special location: current memory latch settings 218 | if addr == 0x0001 { 219 | let ddr = self.ram.read(0x0000); 220 | let pr = self.ram.read(0x0001); 221 | return (ddr & pr) | (!ddr & 0x17); 222 | } 223 | 224 | self.get_bank(addr).read(addr) 225 | } 226 | 227 | 228 | // Read a word from memory (stored in little endian) 229 | pub fn read_word_le(&mut self, addr: u16) -> u16 { 230 | let bank = self.get_bank(addr); 231 | let value_be: u16 = ((bank.read(addr) as u16) << 8 & 0xFF00) | 232 | ((bank.read(addr + 0x0001) as u16) & 0x00FF); 233 | 234 | let value_le: u16 = ((value_be << 8) & 0xFF00) | ((value_be >> 8) & 0x00FF); 235 | value_le 236 | } 237 | 238 | 239 | // *** private functions *** // 240 | 241 | // update status of memory bank latches 242 | fn update_memory_latch(&mut self) { 243 | let ddr = self.ram.read(0x0000); 244 | let pr = self.ram.read(0x0001); 245 | let latch = !ddr | pr; 246 | 247 | self.chargen_on = ((latch & 0x04) == 0) && ((latch & 0x03) != 0); // %0xx except %000 248 | self.io_on = ((latch & 0x04) != 0) && ((latch & 0x03) != 0); // %1xx except %100 249 | self.basic_on = (latch & 0x03) == 3; 250 | self.kernal_on = (latch & 0x02) != 0; 251 | 252 | // binary logic is hard 253 | if self.exrom && !self.game { 254 | self.basic_on = false; 255 | self.kernal_on = false; 256 | } 257 | if !self.exrom && !self.game { 258 | self.basic_on = false; 259 | } 260 | } 261 | } 262 | 263 | -------------------------------------------------------------------------------- /src/c64/sid_tables.rs: -------------------------------------------------------------------------------- 1 | // SID data and precalculated sample tables - as found in Frodo emulator 4.1b 2 | use c64::sid; 3 | 4 | pub static mut TRI_TABLE: [u16; 8192] = [0; 8192]; 5 | 6 | 7 | pub const TRI_SAW_TABLE: [u16; 256] = [ 8 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 9 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 10 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 11 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 12 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 13 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 14 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 15 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0808, 16 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 17 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 18 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 19 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 20 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 21 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 22 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 23 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1010, 0x3C3C, 24 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 25 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 26 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 27 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 28 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 29 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 30 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 31 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0808, 32 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 33 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 34 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 35 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 36 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 37 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 38 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 39 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1010, 0x3C3C 40 | ]; 41 | 42 | pub const TRI_RECT_TABLE: [u16; 256] = [ 43 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 44 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 45 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 46 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 47 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 48 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 49 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 50 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 51 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 52 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 53 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 54 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8080, 55 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 56 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8080, 57 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8080, 0xC0C0, 58 | 0x0000, 0x8080, 0x8080, 0xE0E0, 0x8080, 0xE0E0, 0xF0F0, 0xFCFC, 59 | 0xFFFF, 0xFCFC, 0xFAFA, 0xF0F0, 0xF6F6, 0xE0E0, 0xE0E0, 0x8080, 60 | 0xEEEE, 0xE0E0, 0xE0E0, 0x8080, 0xC0C0, 0x0000, 0x0000, 0x0000, 61 | 0xDEDE, 0xC0C0, 0xC0C0, 0x0000, 0x8080, 0x0000, 0x0000, 0x0000, 62 | 0x8080, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 63 | 0xBEBE, 0x8080, 0x8080, 0x0000, 0x8080, 0x0000, 0x0000, 0x0000, 64 | 0x8080, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 65 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 66 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 67 | 0x7E7E, 0x4040, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 68 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 69 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 70 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 71 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 72 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 73 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 74 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 75 | ]; 76 | 77 | pub const SAW_RECT_TABLE: [u16; 256] = [ 78 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 79 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 80 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 81 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 82 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 83 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 84 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 85 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 86 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 87 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 88 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 89 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 90 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 91 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 92 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 93 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7878, 94 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 95 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 96 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 97 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 98 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 99 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 100 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 101 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 102 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 103 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 104 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 105 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 106 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 107 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 108 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 109 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7878 110 | ]; 111 | 112 | pub const TRI_SAW_RECT_TABLE: [u16; 256] = [ 113 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 114 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 115 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 116 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 117 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 118 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 119 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 120 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 121 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 122 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 123 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 124 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 125 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 126 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 127 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 128 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 129 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 130 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 131 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 132 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 133 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 134 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 135 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 136 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 137 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 138 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 139 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 140 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 141 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 142 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 143 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 144 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 145 | ]; 146 | 147 | pub const EGDR_SHIFT: [u8; 256] = [ 148 | 5,5,5,5,5,5,5,5,4,4,4,4,4,4,4,4, 149 | 3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2, 150 | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 151 | 2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1, 152 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 153 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 154 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 155 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 156 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 157 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 158 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 159 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 160 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 161 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 162 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 163 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 164 | ]; 165 | 166 | pub const SAMPLE_TABLE: [u16; 16] = [ 167 | 0x8000, 0x9111, 0xa222, 0xb333, 0xc444, 0xd555, 0xe666, 0xf777, 168 | 0x0888, 0x1999, 0x2aaa, 0x3bbb, 0x4ccc, 0x5ddd, 0x6eee, 0x7fff, 169 | ]; 170 | 171 | pub const EG_TABLE: [u32; 16] = [ 172 | (sid::SID_CYCLES << 16) / 9, (sid::SID_CYCLES << 16) / 32, 173 | (sid::SID_CYCLES << 16) / 63, (sid::SID_CYCLES << 16) / 95, 174 | (sid::SID_CYCLES << 16) / 149, (sid::SID_CYCLES << 16) / 220, 175 | (sid::SID_CYCLES << 16) / 267, (sid::SID_CYCLES << 16) / 313, 176 | (sid::SID_CYCLES << 16) / 392, (sid::SID_CYCLES << 16) / 977, 177 | (sid::SID_CYCLES << 16) / 1954, (sid::SID_CYCLES << 16) / 3126, 178 | (sid::SID_CYCLES << 16) / 3906, (sid::SID_CYCLES << 16) / 11720, 179 | (sid::SID_CYCLES << 16) / 19531, (sid::SID_CYCLES << 16) / 31251 180 | ]; 181 | -------------------------------------------------------------------------------- /src/c64/io.rs: -------------------------------------------------------------------------------- 1 | // keyboard and joystick support 2 | extern crate minifb; 3 | 4 | use minifb::*; 5 | use c64::cia; 6 | 7 | /* 8 | C64 keyboard map: 9 | 10 | Bit | 7 6 5 4 3 2 1 0 11 | -----+------------------------------------------------------ 12 | 7 | RUNSTOP Q C= SPACE 2 CTRL <- 1 13 | 6 | / ^ = RSHIFT HOME ; * POUND 14 | 5 | , @ " . - L P + 15 | 4 | N O K M 0 J I 9 16 | 3 | V U H B 8 G Y 7 17 | 2 | X T F C 6 D R 5 18 | 1 | LSHIFT E S Z 4 A W 3 19 | 0 | CRSR-DN F5 F3 F1 F7 CRSR-RT RETURN DELETE 20 | */ 21 | 22 | pub struct IO { 23 | keyboard_state: [bool; 0xFF], // key states, including shift presses 24 | joystick_state: [bool; 0x0A], // 9 directions (num-pad) + 1 fire button 25 | joy_port1: bool, // is joystick plugged to port 1? 26 | } 27 | 28 | impl IO { 29 | pub fn new() -> IO { 30 | IO { 31 | keyboard_state: [false; 0xFF], 32 | joystick_state: [false; 0x0A], 33 | joy_port1: false 34 | } 35 | } 36 | 37 | 38 | pub fn update(&mut self, window: &Window, cia1: &mut cia::CIAShared) { 39 | // keyboard processing 40 | // iterating over all keys is crawling-slow, so check individual keys 41 | self.process_key(window.is_key_down(Key::Key0), Key::Key0, cia1); 42 | self.process_key(window.is_key_down(Key::Key1), Key::Key1, cia1); 43 | self.process_key(window.is_key_down(Key::Key2), Key::Key2, cia1); 44 | self.process_key(window.is_key_down(Key::Key3), Key::Key3, cia1); 45 | self.process_key(window.is_key_down(Key::Key4), Key::Key4, cia1); 46 | self.process_key(window.is_key_down(Key::Key5), Key::Key5, cia1); 47 | self.process_key(window.is_key_down(Key::Key6), Key::Key6, cia1); 48 | self.process_key(window.is_key_down(Key::Key7), Key::Key7, cia1); 49 | self.process_key(window.is_key_down(Key::Key8), Key::Key8, cia1); 50 | self.process_key(window.is_key_down(Key::Key9), Key::Key9, cia1); 51 | 52 | self.process_key(window.is_key_down(Key::A), Key::A, cia1); 53 | self.process_key(window.is_key_down(Key::B), Key::B, cia1); 54 | self.process_key(window.is_key_down(Key::C), Key::C, cia1); 55 | self.process_key(window.is_key_down(Key::D), Key::D, cia1); 56 | self.process_key(window.is_key_down(Key::E), Key::E, cia1); 57 | self.process_key(window.is_key_down(Key::F), Key::F, cia1); 58 | self.process_key(window.is_key_down(Key::G), Key::G, cia1); 59 | self.process_key(window.is_key_down(Key::H), Key::H, cia1); 60 | self.process_key(window.is_key_down(Key::I), Key::I, cia1); 61 | self.process_key(window.is_key_down(Key::J), Key::J, cia1); 62 | self.process_key(window.is_key_down(Key::K), Key::K, cia1); 63 | self.process_key(window.is_key_down(Key::L), Key::L, cia1); 64 | self.process_key(window.is_key_down(Key::M), Key::M, cia1); 65 | self.process_key(window.is_key_down(Key::N), Key::N, cia1); 66 | self.process_key(window.is_key_down(Key::O), Key::O, cia1); 67 | self.process_key(window.is_key_down(Key::P), Key::P, cia1); 68 | self.process_key(window.is_key_down(Key::Q), Key::Q, cia1); 69 | self.process_key(window.is_key_down(Key::R), Key::R, cia1); 70 | self.process_key(window.is_key_down(Key::S), Key::S, cia1); 71 | self.process_key(window.is_key_down(Key::T), Key::T, cia1); 72 | self.process_key(window.is_key_down(Key::U), Key::U, cia1); 73 | self.process_key(window.is_key_down(Key::V), Key::V, cia1); 74 | self.process_key(window.is_key_down(Key::W), Key::W, cia1); 75 | self.process_key(window.is_key_down(Key::X), Key::X, cia1); 76 | self.process_key(window.is_key_down(Key::Y), Key::Y, cia1); 77 | self.process_key(window.is_key_down(Key::Z), Key::Z, cia1); 78 | self.process_key(window.is_key_down(Key::F1), Key::F1, cia1); 79 | self.process_key(window.is_key_down(Key::F2), Key::F2, cia1); 80 | self.process_key(window.is_key_down(Key::F3), Key::F3, cia1); 81 | self.process_key(window.is_key_down(Key::F4), Key::F4, cia1); 82 | self.process_key(window.is_key_down(Key::F5), Key::F5, cia1); 83 | self.process_key(window.is_key_down(Key::F6), Key::F6, cia1); 84 | self.process_key(window.is_key_down(Key::F7), Key::F7, cia1); 85 | self.process_key(window.is_key_down(Key::F8), Key::F8, cia1); 86 | 87 | self.process_key(window.is_key_down(Key::Down), Key::Down, cia1); 88 | self.process_key(window.is_key_down(Key::Up), Key::Up, cia1); 89 | self.process_key(window.is_key_down(Key::Right), Key::Right, cia1); 90 | self.process_key(window.is_key_down(Key::Left), Key::Left, cia1); 91 | self.process_key(window.is_key_down(Key::Space), Key::Space, cia1); 92 | self.process_key(window.is_key_down(Key::Comma), Key::Comma, cia1); 93 | self.process_key(window.is_key_down(Key::Period), Key::Period, cia1); 94 | self.process_key(window.is_key_down(Key::Slash), Key::Slash, cia1); 95 | self.process_key(window.is_key_down(Key::Enter), Key::Enter, cia1); 96 | self.process_key(window.is_key_down(Key::Backspace), Key::Backspace, cia1); 97 | self.process_key(window.is_key_down(Key::Backquote), Key::Backquote, cia1); 98 | self.process_key(window.is_key_down(Key::LeftShift), Key::LeftShift, cia1); 99 | self.process_key(window.is_key_down(Key::RightShift), Key::RightShift, cia1); 100 | self.process_key(window.is_key_down(Key::Escape), Key::Escape, cia1); 101 | self.process_key(window.is_key_down(Key::Minus), Key::Minus, cia1); 102 | self.process_key(window.is_key_down(Key::Equal), Key::Equal, cia1); 103 | self.process_key(window.is_key_down(Key::Insert), Key::Insert, cia1); 104 | self.process_key(window.is_key_down(Key::Home), Key::Home, cia1); 105 | self.process_key(window.is_key_down(Key::LeftBracket), Key::LeftBracket, cia1); 106 | self.process_key(window.is_key_down(Key::RightBracket), Key::RightBracket, cia1); 107 | self.process_key(window.is_key_down(Key::Delete), Key::Delete, cia1); 108 | self.process_key(window.is_key_down(Key::Semicolon), Key::Semicolon, cia1); 109 | self.process_key(window.is_key_down(Key::Apostrophe), Key::Apostrophe, cia1); 110 | self.process_key(window.is_key_down(Key::Backslash), Key::Backslash, cia1); 111 | self.process_key(window.is_key_down(Key::Tab), Key::Tab, cia1); 112 | self.process_key(window.is_key_down(Key::LeftCtrl), Key::LeftCtrl, cia1); 113 | 114 | // joystick processing 115 | self.process_joystick(window.is_key_down(Key::NumPad1), Key::NumPad1, cia1); 116 | self.process_joystick(window.is_key_down(Key::NumPad2), Key::NumPad2, cia1); 117 | self.process_joystick(window.is_key_down(Key::NumPad3), Key::NumPad3, cia1); 118 | self.process_joystick(window.is_key_down(Key::NumPad4), Key::NumPad4, cia1); 119 | self.process_joystick(window.is_key_down(Key::NumPad5), Key::NumPad5, cia1); 120 | self.process_joystick(window.is_key_down(Key::NumPad6), Key::NumPad6, cia1); 121 | self.process_joystick(window.is_key_down(Key::NumPad7), Key::NumPad7, cia1); 122 | self.process_joystick(window.is_key_down(Key::NumPad8), Key::NumPad8, cia1); 123 | self.process_joystick(window.is_key_down(Key::NumPad9), Key::NumPad9, cia1); 124 | self.process_joystick(window.is_key_down(Key::RightCtrl), Key::RightCtrl, cia1); 125 | 126 | // helper keys 127 | // toggle between joystick ports 1 and 2 128 | if window.is_key_pressed(Key::NumLock, KeyRepeat::No) { 129 | self.joy_port1 = !self.joy_port1; 130 | 131 | if self.joy_port1 { 132 | cia1.borrow_mut().joystick_2 = 0xFF; 133 | } 134 | else { 135 | cia1.borrow_mut().joystick_1 = 0xFF; 136 | } 137 | 138 | println!("Using joystick in port {}", if self.joy_port1 { "1" } else { "2" }); 139 | } 140 | } 141 | 142 | 143 | pub fn check_restore_key(&self, window: &Window) -> bool { 144 | // End will serve as the Restore key 145 | window.is_key_pressed(Key::End, KeyRepeat::No) 146 | } 147 | 148 | 149 | // *** private functions *** // 150 | 151 | fn process_key(&mut self, key_pressed: bool, keycode: Key, cia1: &mut cia::CIAShared) { 152 | if key_pressed { 153 | self.on_key_press(keycode, cia1); 154 | } 155 | else { 156 | self.on_key_release(keycode, cia1); 157 | } 158 | } 159 | 160 | 161 | fn on_key_press(&mut self, keycode: Key, cia1: &mut cia::CIAShared) { 162 | let c64_keycode = self.keycode_to_c64(keycode); 163 | 164 | if self.keyboard_state[c64_keycode as usize] || c64_keycode == 0xFF 165 | { 166 | return 167 | } 168 | 169 | self.keyboard_state[c64_keycode as usize] = true; 170 | 171 | let c64_bit = c64_keycode & 7; 172 | let c64_byte = (c64_keycode >> 3) & 7; 173 | 174 | // key is shifted? 175 | if (c64_keycode & 0x80) != 0 176 | { 177 | cia1.borrow_mut().key_matrix[6] &= 0xEF; 178 | cia1.borrow_mut().rev_matrix[4] &= 0xBF; 179 | } 180 | 181 | cia1.borrow_mut().key_matrix[c64_byte as usize] &= !(1 << c64_bit); 182 | cia1.borrow_mut().rev_matrix[c64_bit as usize] &= !(1 << c64_byte); 183 | } 184 | 185 | 186 | fn on_key_release(&mut self, keycode: Key, cia1: &mut cia::CIAShared) { 187 | let c64_keycode = self.keycode_to_c64(keycode); 188 | 189 | if !self.keyboard_state[c64_keycode as usize] || c64_keycode == 0xFF { 190 | return; 191 | } 192 | 193 | self.keyboard_state[c64_keycode as usize] = false; 194 | 195 | let c64_bit = c64_keycode & 7; 196 | let c64_byte = (c64_keycode >> 3) & 7; 197 | 198 | // key is shifted? 199 | if (c64_keycode & 0x80) != 0 { 200 | cia1.borrow_mut().key_matrix[6] |= 0x10; 201 | cia1.borrow_mut().rev_matrix[4] |= 0x40; 202 | } 203 | 204 | cia1.borrow_mut().key_matrix[c64_byte as usize] |= 1 << c64_bit; 205 | cia1.borrow_mut().rev_matrix[c64_bit as usize] |= 1 << c64_byte; 206 | } 207 | 208 | 209 | fn process_joystick(&mut self, key_pressed: bool, keycode: Key, cia1: &mut cia::CIAShared) { 210 | if key_pressed { 211 | self.on_joy_press(keycode, cia1); 212 | } 213 | else { 214 | self.on_joy_release(keycode, cia1); 215 | } 216 | } 217 | 218 | 219 | fn on_joy_press(&mut self, keycode: Key, cia1: &mut cia::CIAShared) { 220 | let mut joystate = if self.joy_port1 { cia1.borrow_mut().joystick_1 } else { cia1.borrow_mut().joystick_2 }; 221 | 222 | match keycode { 223 | // down-left 224 | Key::NumPad1 => { self.joystick_state[0] = true; joystate = (joystate | 0x09) & !0x06; }, 225 | // down 226 | Key::NumPad2 => { self.joystick_state[1] = true; joystate = (joystate | 0x01) & !0x02; }, 227 | // down-right 228 | Key::NumPad3 => { self.joystick_state[2] = true; joystate = (joystate | 0x05) & !0x0A; }, 229 | // left 230 | Key::NumPad4 => { self.joystick_state[3] = true; joystate = (joystate | 0x08) & !0x04; }, 231 | // center 232 | Key::NumPad5 => { self.joystick_state[4] = true; joystate |= 0x0F; }, 233 | // right 234 | Key::NumPad6 => { self.joystick_state[5] = true; joystate = (joystate | 0x04) & !0x08; }, 235 | // up-left 236 | Key::NumPad7 => { self.joystick_state[6] = true; joystate = (joystate | 0x0A) & !0x05; }, 237 | // up 238 | Key::NumPad8 => { self.joystick_state[7] = true; joystate = (joystate | 0x02) & !0x01; }, 239 | // up-right 240 | Key::NumPad9 => { self.joystick_state[8] = true; joystate = (joystate | 0x06) & !0x09; }, 241 | // fire button 242 | Key::RightCtrl => { self.joystick_state[9] = true; joystate &= !0x10; }, 243 | _ => (), 244 | } 245 | 246 | if self.joy_port1 { 247 | cia1.borrow_mut().joystick_1 = joystate; 248 | } 249 | else { 250 | cia1.borrow_mut().joystick_2 = joystate; 251 | } 252 | } 253 | 254 | 255 | fn on_joy_release(&mut self, keycode: Key, cia1: &mut cia::CIAShared) { 256 | let mut joystate = if self.joy_port1 { cia1.borrow_mut().joystick_1 } else { cia1.borrow_mut().joystick_2 }; 257 | 258 | if joystate == 0xFF { 259 | return; 260 | } 261 | 262 | match keycode { 263 | // down-left 264 | Key::NumPad1 => if self.joystick_state[0] { joystate |= 0x06; self.joystick_state[0] = false; }, 265 | // down 266 | Key::NumPad2 => if self.joystick_state[1] { joystate |= 0x02; self.joystick_state[1] = false; }, 267 | // down-right 268 | Key::NumPad3 => if self.joystick_state[2] { joystate |= 0x0A; self.joystick_state[2] = false; }, 269 | // left 270 | Key::NumPad4 => if self.joystick_state[3] { joystate |= 0x04; self.joystick_state[3] = false; }, 271 | // center 272 | Key::NumPad5 => self.joystick_state[4] = false, 273 | // right 274 | Key::NumPad6 => if self.joystick_state[5] { joystate |= 0x08; self.joystick_state[5] = false; }, 275 | // up-left 276 | Key::NumPad7 => if self.joystick_state[6] { joystate |= 0x05; self.joystick_state[6] = false; }, 277 | // up 278 | Key::NumPad8 => if self.joystick_state[7] { joystate |= 0x01; self.joystick_state[7] = false; }, 279 | // up-right 280 | Key::NumPad9 => if self.joystick_state[8] { joystate |= 0x09; self.joystick_state[8] = false; }, 281 | // fire button 282 | Key::RightCtrl => if self.joystick_state[9] { joystate |= 0x10; self.joystick_state[9] = false; }, 283 | _ => (), 284 | } 285 | 286 | if self.joy_port1 { 287 | cia1.borrow_mut().joystick_1 = joystate; 288 | cia1.borrow_mut().joystick_2 = 0xFF; 289 | } 290 | else { 291 | cia1.borrow_mut().joystick_1 = 0xFF; 292 | cia1.borrow_mut().joystick_2 = joystate; 293 | } 294 | } 295 | 296 | 297 | fn keycode_to_c64(&self, keycode: Key) -> u8 { 298 | // fetch key's bit combination as represented in C64 keyboard matrix 299 | let to_c64 = |row_bit: u8, col_bit: u8| (row_bit << 3) | col_bit; 300 | 301 | match keycode { 302 | Key::Key0 => to_c64(4, 3), 303 | Key::Key1 => to_c64(7, 0), 304 | Key::Key2 => to_c64(7, 3), 305 | Key::Key3 => to_c64(1, 0), 306 | Key::Key4 => to_c64(1, 3), 307 | Key::Key5 => to_c64(2, 0), 308 | Key::Key6 => to_c64(2, 3), 309 | Key::Key7 => to_c64(3, 0), 310 | Key::Key8 => to_c64(3, 3), 311 | Key::Key9 => to_c64(4, 0), 312 | Key::A => to_c64(1, 2), 313 | Key::B => to_c64(3, 4), 314 | Key::C => to_c64(2, 4), 315 | Key::D => to_c64(2, 2), 316 | Key::E => to_c64(1, 6), 317 | Key::F => to_c64(2, 5), 318 | Key::G => to_c64(3, 2), 319 | Key::H => to_c64(3, 5), 320 | Key::I => to_c64(4, 1), 321 | Key::J => to_c64(4, 2), 322 | Key::K => to_c64(4, 5), 323 | Key::L => to_c64(5, 2), 324 | Key::M => to_c64(4, 4), 325 | Key::N => to_c64(4, 7), 326 | Key::O => to_c64(4, 6), 327 | Key::P => to_c64(5, 1), 328 | Key::Q => to_c64(7, 6), 329 | Key::R => to_c64(2, 1), 330 | Key::S => to_c64(1, 5), 331 | Key::T => to_c64(2, 6), 332 | Key::U => to_c64(3, 6), 333 | Key::V => to_c64(3, 7), 334 | Key::W => to_c64(1, 1), 335 | Key::X => to_c64(2, 7), 336 | Key::Y => to_c64(3, 1), 337 | Key::Z => to_c64(1, 4), 338 | Key::F1 => to_c64(0, 4), 339 | Key::F2 => to_c64(0, 4) | 0x80, 340 | Key::F3 => to_c64(0, 5), 341 | Key::F4 => to_c64(0, 5) | 0x80, 342 | Key::F5 => to_c64(0, 6), 343 | Key::F6 => to_c64(0, 6) | 0x80, 344 | Key::F7 => to_c64(0, 3), 345 | Key::F8 => to_c64(0, 3) | 0x80, 346 | Key::Down => to_c64(0, 7), 347 | Key::Up => to_c64(0, 7) | 0x80, 348 | Key::Right => to_c64(0, 2), 349 | Key::Left => to_c64(0, 2) | 0x80, 350 | Key::Space => to_c64(7, 4), 351 | Key::Comma => to_c64(5, 7), 352 | Key::Period => to_c64(5, 4), 353 | Key::Slash => to_c64(6, 7), 354 | Key::Enter => to_c64(0, 1), 355 | Key::Backspace => to_c64(0, 0), 356 | // Left arrow key 357 | Key::Backquote => to_c64(7, 1), 358 | Key::LeftShift => to_c64(1, 7), 359 | Key::RightShift => to_c64(6, 4), 360 | // Run Stop key 361 | Key::Escape => to_c64(7, 7), 362 | // Plus key 363 | Key::Minus => to_c64(5, 0), 364 | // Minus key 365 | Key::Equal => to_c64(5, 3), 366 | // Pound key 367 | Key::Insert => to_c64(6, 0), 368 | // CLR/Home key 369 | Key::Home => to_c64(6, 3), 370 | // Home key 371 | Key::Delete => to_c64(6, 6), 372 | // @ key 373 | Key::LeftBracket => to_c64(5, 6), 374 | // * key 375 | Key::RightBracket => to_c64(6, 1), 376 | // Colon key 377 | Key::Semicolon => to_c64(5, 5), 378 | // Semicolon key 379 | Key::Apostrophe => to_c64(6, 2), 380 | // Equals key 381 | Key::Backslash => to_c64(6, 5), 382 | // Control key 383 | Key::Tab => to_c64(7, 2), 384 | // Commodore key 385 | Key::LeftCtrl => to_c64(7, 5), 386 | // unknown 387 | _ => 0xFF 388 | } 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /src/debugger/mod.rs: -------------------------------------------------------------------------------- 1 | // memory debug window 2 | extern crate minifb; 3 | 4 | mod font; 5 | 6 | use c64; 7 | use minifb::*; 8 | use std::io::Write; 9 | use utils; 10 | 11 | const DEBUG_W: usize = 640; 12 | const DEBUG_H: usize = 432; 13 | const RASTER_DEBUG_W: usize = 3*63; 14 | const RASTER_DEBUG_H: usize = 312; 15 | 16 | // color constants for VIC raster window 17 | const BORDER_COLOR: u32 = 0x00404040; 18 | const BG_COLOR: u32 = 0x00000000; 19 | const VIC_WRITE_COLOR: u32 = 0x00FF0000; 20 | const RASTER_COLOR: u32 = 0x000000FF; 21 | const BADLINE_COLOR: u32 = 0x0000FF00; 22 | 23 | 24 | pub struct Debugger { 25 | debug_window: minifb::Window, 26 | vic_window: minifb::Window, 27 | font: font::SysFont, 28 | window_buffer: Vec, // main debugger window data buffer 29 | vic_buffer: Vec, // VIC window data buffer 30 | mempage_offset: u32, // RAM preview memory page offset 31 | draw_mode: u8, 32 | } 33 | 34 | impl Debugger { 35 | pub fn new() -> Debugger { 36 | let mut dbg = Debugger { 37 | debug_window: Window::new("Debug window", DEBUG_W, DEBUG_H, WindowOptions { scale: Scale::X2, ..Default::default() }).unwrap(), 38 | vic_window: Window::new("VIC", RASTER_DEBUG_W, RASTER_DEBUG_H, WindowOptions::default()).unwrap(), 39 | font: font::SysFont::new(), 40 | window_buffer: vec![0; DEBUG_W * DEBUG_H], 41 | vic_buffer: vec![0; RASTER_DEBUG_W * RASTER_DEBUG_H], 42 | mempage_offset: 0, 43 | draw_mode: 0, 44 | }; 45 | 46 | dbg.debug_window.set_position(480, 20); 47 | dbg.vic_window.set_position(270, 340); 48 | 49 | for y in 1..26 { 50 | for x in 0..40 { 51 | dbg.font.draw_char_rgb(&mut dbg.window_buffer, DEBUG_W, 8*x as usize, 8 + 8*y as usize, 102, 0x00101010); 52 | } 53 | } 54 | 55 | dbg.draw_vic_window_txt(); 56 | dbg 57 | } 58 | 59 | 60 | pub fn render(&mut self, cpu: &mut c64::cpu::CPUShared, memory: &mut c64::memory::MemShared) { 61 | if self.debug_window.is_open() { 62 | self.draw_border(); 63 | 64 | let home_pressed = self.debug_window.is_key_pressed(Key::Home, KeyRepeat::No); 65 | let end_pressed = self.debug_window.is_key_pressed(Key::End, KeyRepeat::No); 66 | 67 | if home_pressed || end_pressed { 68 | if home_pressed { 69 | if self.draw_mode == 0 { 70 | self.draw_mode = 4; 71 | } 72 | else { 73 | self.draw_mode -= 1; 74 | } 75 | } 76 | if end_pressed { 77 | if self.draw_mode == 4 { 78 | self.draw_mode = 0; 79 | } 80 | else { 81 | self.draw_mode += 1; 82 | } 83 | } 84 | // clear memdump 85 | for y in 0..26 { 86 | for x in 0..40 { 87 | self.font.draw_char_rgb(&mut self.window_buffer, DEBUG_W, 8*x as usize, 8 + 8*y as usize, 102, 0x00101010); 88 | } 89 | } 90 | // clear hex region 91 | for y in 28..54 { 92 | for x in 0..80 { 93 | self.clear_char(x, y); 94 | } 95 | } 96 | } 97 | 98 | match self.draw_mode { 99 | 0 => self.draw_ram(memory), 100 | 1 => self.draw_vic(memory), 101 | 2 => self.draw_cia(cpu), 102 | 3 => self.draw_color_ram(memory), 103 | 4 => self.draw_sid(memory), 104 | _ => () 105 | } 106 | 107 | self.draw_gfx_mode(memory); 108 | self.draw_latch_status(memory); 109 | self.draw_data(memory); 110 | self.draw_cpu(cpu); 111 | 112 | let _ = self.debug_window.update_with_buffer(&self.window_buffer, DEBUG_W, DEBUG_H); 113 | } 114 | if self.vic_window.is_open() { 115 | let _ = self.vic_window.update_with_buffer(&self.vic_buffer, DEBUG_W, DEBUG_H); 116 | } 117 | } 118 | 119 | 120 | pub fn update_vic_window(&mut self, vic: &mut c64::vic::VICShared) { 121 | if !self.vic_window.is_open() { 122 | return; 123 | } 124 | 125 | let x = vic.borrow_mut().curr_cycle; 126 | let y = vic.borrow_mut().raster_cnt; 127 | let is_bad_line = vic.borrow_mut().is_bad_line; 128 | let is_raster_irq = vic.borrow_mut().raster_irq == y; 129 | let is_border = vic.borrow_mut().border_on; 130 | let is_state_changed = vic.borrow_mut().dbg_reg_changed; 131 | 132 | let mut dst_color = if is_border { BORDER_COLOR } else { BG_COLOR }; 133 | dst_color = if is_state_changed { self.mix_colors(VIC_WRITE_COLOR, dst_color, 0.8) } else { dst_color }; 134 | dst_color = if is_raster_irq { self.mix_colors(RASTER_COLOR, dst_color, 0.8) } else { dst_color }; 135 | dst_color = if is_bad_line { self.mix_colors(BADLINE_COLOR, dst_color, 0.5) } else { dst_color }; 136 | 137 | self.vic_buffer[((x as u32) - 1 + (RASTER_DEBUG_W as u32)*(y as u32)) as usize] = dst_color; 138 | } 139 | 140 | 141 | // *** private functions *** // 142 | 143 | // dump RAM page to screen 144 | fn draw_ram(&mut self, memory: &mut c64::memory::MemShared) { 145 | if self.debug_window.is_key_pressed(Key::PageUp, KeyRepeat::Yes) { 146 | self.mempage_offset += 0x400; 147 | 148 | if self.mempage_offset > 0xFC00 { 149 | self.mempage_offset = 0; 150 | } 151 | } 152 | if self.debug_window.is_key_pressed(Key::PageDown, KeyRepeat::Yes) { 153 | if self.mempage_offset == 0x0000 { 154 | self.mempage_offset = 0x10000; 155 | } 156 | self.mempage_offset -= 0x400; 157 | } 158 | 159 | let mut start = 0x0000 + self.mempage_offset as u16; 160 | let mut title = Vec::new(); 161 | let mut hex_offset_x = 0; 162 | let _ = write!(&mut title, "Memory page ${:04x}-${:04x}", start, start + 0x3FF); 163 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 0, 0, &String::from_utf8(title).unwrap().to_owned()[..], 0x0A); 164 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 34, 0, "*RAM*", 0x0E); 165 | 166 | for y in 0..26 { 167 | for x in 0..40 { 168 | let byte = memory.borrow_mut().get_ram_bank(c64::memory::MemType::Ram).read(start); 169 | self.font.draw_char(&mut self.window_buffer, DEBUG_W, 8*x as usize, 8 + 8*y as usize, byte, 0x05); 170 | 171 | self.draw_hex(hex_offset_x + x as usize, 28 + y as usize, byte); 172 | hex_offset_x += 1; 173 | start += 1; 174 | 175 | if start == (self.mempage_offset as u16 + 0x0400) { return; } 176 | } 177 | hex_offset_x = 0; 178 | } 179 | } 180 | 181 | 182 | // VIC registers 183 | fn draw_vic(&mut self, memory: &mut c64::memory::MemShared) { 184 | let mut start = 0xD000; 185 | let mut title = Vec::new(); 186 | let mut hex_offset_x = 0; 187 | let _ = write!(&mut title, "VIC ${:04x}-${:04x}", start, start + 0x03F); 188 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 0, 0, &String::from_utf8(title).unwrap().to_owned()[..], 0x0A); 189 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 34, 0, "*VIC*", 0x0E); 190 | 191 | for y in 0..25 { 192 | for x in 0..40 { 193 | let byte = memory.borrow_mut().get_ram_bank(c64::memory::MemType::Io).read(start); 194 | self.font.draw_char(&mut self.window_buffer, DEBUG_W, 8*x as usize, 8 + 8*y as usize, byte, 0x05); 195 | self.draw_hex(hex_offset_x + x as usize, 28 + y as usize, byte); 196 | hex_offset_x += 1; 197 | start += 1; 198 | 199 | if start == 0xD040 { 200 | return; 201 | } 202 | } 203 | hex_offset_x = 0; 204 | } 205 | } 206 | 207 | 208 | // CIA registers 209 | fn draw_cia(&mut self, cpu: &mut c64::cpu::CPUShared) { 210 | let mut start = 0xDC00; 211 | let mut title = Vec::new(); 212 | let mut hex_offset_x = 0; 213 | let _ = write!(&mut title, "CIA ${:04x}-${:04x}", start, start + 0x1FF); 214 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 0, 0, &String::from_utf8(title).unwrap().to_owned()[..], 0x0A); 215 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 34, 0, "*CIA*", 0x0E); 216 | 217 | for y in 0..25 { 218 | for x in 0..40 { 219 | if start >= 0xDC10 && start < 0xDD00 { 220 | hex_offset_x += 1; 221 | start += 1; 222 | continue; 223 | } 224 | let byte = cpu.borrow_mut().read_byte(start); 225 | self.font.draw_char(&mut self.window_buffer, DEBUG_W, 8*x as usize, 8 + 8*y as usize, byte, 0x05); 226 | self.draw_hex(hex_offset_x + x as usize, 28 + y as usize, byte); 227 | hex_offset_x += 1; 228 | start += 1; 229 | 230 | if start == 0xDD10 { 231 | return; 232 | } 233 | } 234 | hex_offset_x = 0; 235 | } 236 | } 237 | 238 | 239 | // SID registers 240 | fn draw_sid(&mut self, memory: &mut c64::memory::MemShared) { 241 | let mut start = 0xD400; 242 | let mut title = Vec::new(); 243 | let mut hex_offset_x = 0; 244 | let _ = write!(&mut title, "SID ${:04x}-${:04x}", start, start + 0x03F); 245 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 0, 0, &String::from_utf8(title).unwrap().to_owned()[..], 0x0A); 246 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 34, 0, "*SID*", 0x0E); 247 | 248 | for y in 0..25 { 249 | for x in 0..40 { 250 | let byte = memory.borrow_mut().get_ram_bank(c64::memory::MemType::Io).read(start); 251 | self.font.draw_char(&mut self.window_buffer, DEBUG_W, 8*x as usize, 8 + 8*y as usize, byte, 0x05); 252 | 253 | self.draw_hex(hex_offset_x + x as usize, 28 + y as usize, byte); 254 | hex_offset_x += 1; 255 | start += 1; 256 | 257 | if start == 0xD420 { 258 | return; 259 | } 260 | } 261 | hex_offset_x = 0; 262 | } 263 | } 264 | 265 | 266 | // Color RAM 267 | fn draw_color_ram(&mut self, memory: &mut c64::memory::MemShared) { 268 | let mut start = 0xD800; 269 | 270 | let mut title = Vec::new(); 271 | let _ = write!(&mut title, "COLOR ${:04x}-${:04x}", start, start + 0x3FF); 272 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 0, 0, &String::from_utf8(title).unwrap().to_owned()[..], 0x0A); 273 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 28, 0, "*COLOR RAM*", 0x0E); 274 | 275 | let mut hex_offset_x = 0; 276 | 277 | for y in 0..25 { 278 | for x in 0..40 { 279 | let byte = memory.borrow_mut().get_ram_bank(c64::memory::MemType::Io).read(start); 280 | self.font.draw_char(&mut self.window_buffer, DEBUG_W, 8*x as usize, 8 + 8*y as usize, byte, 0x05); 281 | 282 | self.draw_hex(hex_offset_x + x as usize, 28 + y as usize, byte); 283 | hex_offset_x += 1; 284 | start += 1; 285 | 286 | if start == 0xDC00 { 287 | return; 288 | } 289 | } 290 | 291 | hex_offset_x = 0; 292 | } 293 | } 294 | 295 | 296 | // draw colored hex value of memory cell 297 | fn draw_hex(&mut self, x_pos: usize, y_pos: usize, byte: u8 ) { 298 | let mut hex_value = Vec::new(); 299 | let _ = write!(&mut hex_value, "{:02X}", byte); 300 | 301 | let mut base_color = utils::fetch_c64_color_rgba(byte >> 4); 302 | if base_color == 0 { 303 | base_color = 0x00333333; 304 | } 305 | 306 | // all black? make it at least somewhat visible 307 | if byte == 0 { 308 | base_color = 0x00101010; 309 | } 310 | 311 | self.font.draw_text_rgb(&mut self.window_buffer, DEBUG_W, x_pos, y_pos, &String::from_utf8(hex_value).unwrap().to_owned()[..], base_color); 312 | } 313 | 314 | 315 | // basic C64 settings 316 | fn draw_data(&mut self, memory: &mut c64::memory::MemShared) { 317 | let d018 = memory.borrow_mut().get_ram_bank(c64::memory::MemType::Io).read(0xD018); 318 | let dd00 = memory.borrow_mut().get_ram_bank(c64::memory::MemType::Io).read(0xDD00); 319 | 320 | let mut vmatrix_txt = Vec::new(); 321 | let mut char_txt = Vec::new(); 322 | let mut bmp_txt = Vec::new(); 323 | let mut bank_txt = Vec::new(); 324 | let _ = write!(&mut vmatrix_txt, "${:04X}", 0x400 * ((d018 >> 4) & 0xF) as u16); 325 | let _ = write!(&mut char_txt, "${:04X}", 0x800 * ((d018 >> 1) & 0x07) as u16); 326 | let _ = write!(&mut bmp_txt, "${:04X}", 0x2000 * ((d018 >> 3) & 0x01) as u16); 327 | let _ = write!(&mut bank_txt, "${:04X}", 0xC000 - 0x4000 * (dd00 & 0x03) as u16); 328 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 43, 3, "Screen: ", 0x0F); 329 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 51, 3, &String::from_utf8(vmatrix_txt).unwrap().to_owned()[..], 0x0E); 330 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 45, 4, "Char: ", 0x0F); 331 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 51, 4, &String::from_utf8(char_txt).unwrap().to_owned()[..], 0x0E); 332 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 43, 5, "Bitmap: ", 0x0F); 333 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 51, 5, &String::from_utf8(bmp_txt).unwrap().to_owned()[..], 0x0E); 334 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 41, 6, "VIC Bank: ", 0x0F); 335 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 51, 6, &String::from_utf8(bank_txt).unwrap().to_owned()[..], 0x0E); 336 | } 337 | 338 | 339 | // current graphics mode tags 340 | fn draw_gfx_mode(&mut self, memory: &mut c64::memory::MemShared) { 341 | let d011 = memory.borrow_mut().get_ram_bank(c64::memory::MemType::Io).read(0xD011); 342 | let d016 = memory.borrow_mut().get_ram_bank(c64::memory::MemType::Io).read(0xD016); 343 | let ecm_on = (d011 & 0x40) != 0; 344 | let mcm_on = (d016 & 0x10) != 0; 345 | let bmp_on = (d011 & 0x20) != 0; 346 | 347 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 52, 1, "ECM", if ecm_on { 0x0A } else { 0x0B }); 348 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 57, 1, "CHR", if !bmp_on & !ecm_on { 0x0A } else { 0x0B }); 349 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 62, 1, "BMP", if bmp_on { 0x0A } else { 0x0B }); 350 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 67, 1, "MCM", if mcm_on { 0x0A } else { 0x0B }); 351 | } 352 | 353 | 354 | // active memory banks 355 | fn draw_latch_status(&mut self, memory: &mut c64::memory::MemShared) { 356 | let basic_on = memory.borrow_mut().basic_on; 357 | let chargen_on = memory.borrow_mut().chargen_on; 358 | let io_on = memory.borrow_mut().io_on; 359 | let kernal_on = memory.borrow_mut().kernal_on; 360 | 361 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 48, 25, "BASIC", if basic_on { 0x0A } else { 0x0B }); 362 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 55, 25, "CHARGEN", if chargen_on { 0x0A } else { 0x0B }); 363 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 64, 25, "IO", if io_on { 0x0A } else { 0x0B }); 364 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 68, 25, "KERNAL", if kernal_on { 0x0A } else { 0x0B }); 365 | } 366 | 367 | 368 | // draw CPU flags and registers 369 | fn draw_cpu(&mut self, cpu: &mut c64::cpu::CPUShared) { 370 | let mut pc_txt = Vec::new(); 371 | let mut a_txt = Vec::new(); 372 | let mut x_txt = Vec::new(); 373 | let mut y_txt = Vec::new(); 374 | let mut sp_txt = Vec::new(); 375 | let mut p_txt = Vec::new(); 376 | let _ = write!(&mut pc_txt, "${:04X}", cpu.borrow_mut().pc); 377 | let _ = write!(&mut a_txt, "${:02X}", cpu.borrow_mut().a); 378 | let _ = write!(&mut x_txt, "${:02X}", cpu.borrow_mut().x); 379 | let _ = write!(&mut y_txt, "${:02X}", cpu.borrow_mut().y); 380 | let _ = write!(&mut sp_txt, "${:02X}", cpu.borrow_mut().sp); 381 | let _ = write!(&mut p_txt, "[{:08b}]", cpu.borrow_mut().p); 382 | 383 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 44, 22, "PC:", 0x0F); 384 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 47, 22, &String::from_utf8(pc_txt).unwrap().to_owned()[..], 0x0E); 385 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 53, 22, "A:", 0x0F); 386 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 55, 22, &String::from_utf8(a_txt).unwrap().to_owned()[..], 0x0E); 387 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 59, 22, "X:", 0x0F); 388 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 61, 22, &String::from_utf8(x_txt).unwrap().to_owned()[..], 0x0E); 389 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 65, 22, "Y:", 0x0F); 390 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 67, 22, &String::from_utf8(y_txt).unwrap().to_owned()[..], 0x0E); 391 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 71, 22, "SP:", 0x0F); 392 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 74, 22, &String::from_utf8(sp_txt).unwrap().to_owned()[..], 0x0E); 393 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 51, 23, "NV-BDIZC:", 0x0F); 394 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, 61, 23, &String::from_utf8(p_txt).unwrap().to_owned()[..], 0x0E); 395 | } 396 | 397 | 398 | // draw window border 399 | fn draw_border(&mut self) { 400 | for x in 0..80 { 401 | self.font.draw_char(&mut self.window_buffer, DEBUG_W, 8*x as usize, 0, 64, 0x0B); 402 | self.font.draw_char(&mut self.window_buffer, DEBUG_W, 8*x as usize, 8*27, 64, 0x0B); 403 | } 404 | 405 | for y in 1..27 { 406 | self.font.draw_char(&mut self.window_buffer, DEBUG_W, 8*40, 8*y as usize, 66, 0x0B); 407 | } 408 | 409 | self.font.draw_char(&mut self.window_buffer, DEBUG_W, 8*40, 0, 114, 0x0B); 410 | self.font.draw_char(&mut self.window_buffer, DEBUG_W, 8*40, 8*27, 113, 0x0B); 411 | } 412 | 413 | 414 | // one-time text draw for VIC raster window 415 | fn draw_vic_window_txt(&mut self) { 416 | self.font.draw_text_rgb(&mut self.vic_buffer, RASTER_DEBUG_W, 10, 3, "*VIC events*", utils::fetch_c64_color_rgba(0x0F)); 417 | self.font.draw_text_rgb(&mut self.vic_buffer, RASTER_DEBUG_W, 11, 5, "Border on", BORDER_COLOR); 418 | self.font.draw_text_rgb(&mut self.vic_buffer, RASTER_DEBUG_W, 11, 6, "Bad line", BADLINE_COLOR); 419 | self.font.draw_text_rgb(&mut self.vic_buffer, RASTER_DEBUG_W, 11, 7, "Reg. change", VIC_WRITE_COLOR); 420 | self.font.draw_text_rgb(&mut self.vic_buffer, RASTER_DEBUG_W, 11, 8, "Raster IRQ", RASTER_COLOR); 421 | } 422 | 423 | 424 | fn clear_char(&mut self, x_pos: usize, y_pos: usize) { 425 | self.font.draw_text(&mut self.window_buffer, DEBUG_W, x_pos, y_pos, " ", 0x00); 426 | } 427 | 428 | 429 | fn mix_colors(&self, new: u32, old: u32, alpha: f32) -> u32 { 430 | let rn = ((new >> 16) & 0xFF) as f32; 431 | let gn = ((new >> 8) & 0xFF) as f32; 432 | let bn = (new & 0xFF) as f32; 433 | 434 | let ro = ((old >> 16) & 0xFF) as f32; 435 | let go = ((old >> 8) & 0xFF) as f32; 436 | let bo = (old & 0xFF) as f32; 437 | 438 | let rd = alpha * rn + (1.0 - alpha) * ro; 439 | let gd = alpha * gn + (1.0 - alpha) * go; 440 | let bd = alpha * bn + (1.0 - alpha) * bo; 441 | 442 | let mut dst_color = (rd as u32) << 16; 443 | dst_color |= (gd as u32) << 8; 444 | dst_color |= bd as u32; 445 | 446 | dst_color 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /src/c64/cpu.rs: -------------------------------------------------------------------------------- 1 | // The CPU 2 | use c64::cia; 3 | use c64::memory; 4 | use c64::opcodes; 5 | use c64::sid; 6 | use c64::vic; 7 | use std::cell::RefCell; 8 | use std::rc::Rc; 9 | use utils; 10 | 11 | pub type CPUShared = Rc>; 12 | 13 | pub const NMI_VECTOR: u16 = 0xFFFA; 14 | pub const RESET_VECTOR: u16 = 0xFFFC; 15 | pub const IRQ_VECTOR: u16 = 0xFFFE; 16 | 17 | 18 | // status flags for P register 19 | pub enum StatusFlag { 20 | Carry = 1 << 0, 21 | Zero = 1 << 1, 22 | InterruptDisable = 1 << 2, 23 | DecimalMode = 1 << 3, 24 | Break = 1 << 4, 25 | Unused = 1 << 5, 26 | Overflow = 1 << 6, 27 | Negative = 1 << 7, 28 | } 29 | 30 | // action to perform on specific CIA and VIC events 31 | pub enum Callback { 32 | None, 33 | TriggerVICIrq, 34 | ClearVICIrq, 35 | TriggerCIAIrq, 36 | ClearCIAIrq, 37 | TriggerNMI, 38 | ClearNMI 39 | } 40 | 41 | pub enum CPUState { 42 | FetchOp, 43 | FetchOperandAddr, 44 | PerformRMW, 45 | ProcessIRQ, 46 | ProcessNMI, 47 | ExecuteOp 48 | } 49 | 50 | 51 | pub struct CPU { 52 | pub pc: u16, // program counter 53 | pub sp: u8, // stack pointer 54 | pub p: u8, // processor status 55 | pub a: u8, // accumulator 56 | pub x: u8, // index register 57 | pub y: u8, // index register 58 | pub mem_ref: Option, // reference to shared system memory 59 | pub vic_ref: Option, 60 | pub cia1_ref: Option, 61 | pub cia2_ref: Option, 62 | pub sid_ref: Option, 63 | pub instruction: opcodes::Instruction, 64 | pub ba_low: bool, // is BA low? 65 | pub cia_irq: bool, 66 | pub vic_irq: bool, 67 | pub irq_cycles_left: u8, 68 | pub nmi_cycles_left: u8, 69 | pub first_nmi_cycle: u32, 70 | pub first_irq_cycle: u32, 71 | pub state: CPUState, 72 | pub nmi: bool, 73 | pub debug_instr: bool, 74 | pub prev_pc: u16, // previous program counter - used for debugging 75 | pub op_debugger: utils::OpDebugger, 76 | dfff_byte: u8 77 | } 78 | 79 | impl CPU { 80 | pub fn new_shared() -> CPUShared { 81 | Rc::new(RefCell::new(CPU { 82 | pc: 0, 83 | sp: 0xFF, 84 | p: 0, 85 | a: 0, 86 | x: 0, 87 | y: 0, 88 | mem_ref: None, 89 | vic_ref: None, 90 | cia1_ref: None, 91 | cia2_ref: None, 92 | sid_ref: None, 93 | ba_low: false, 94 | cia_irq: false, 95 | vic_irq: false, 96 | irq_cycles_left: 0, 97 | nmi_cycles_left: 0, 98 | first_nmi_cycle: 0, 99 | first_irq_cycle: 0, 100 | state: CPUState::FetchOp, 101 | instruction: opcodes::Instruction::new(), 102 | nmi: false, 103 | debug_instr: false, 104 | prev_pc: 0, 105 | op_debugger: utils::OpDebugger::new(), 106 | dfff_byte: 0x55 107 | })) 108 | } 109 | 110 | 111 | pub fn set_references(&mut self, memref: memory::MemShared, vicref: vic::VICShared, cia1ref: cia::CIAShared, cia2ref: cia::CIAShared, sidref: sid::SIDShared) { 112 | self.mem_ref = Some(memref); 113 | self.vic_ref = Some(vicref); 114 | self.cia1_ref = Some(cia1ref); 115 | self.cia2_ref = Some(cia2ref); 116 | self.sid_ref = Some(sidref); 117 | } 118 | 119 | 120 | pub fn set_status_flag(&mut self, flag: StatusFlag, value: bool) { 121 | if value { self.p |= flag as u8; } 122 | else { self.p &= !(flag as u8); } 123 | } 124 | 125 | 126 | pub fn get_status_flag(&mut self, flag: StatusFlag) -> bool { 127 | self.p & flag as u8 != 0x00 128 | } 129 | 130 | 131 | // these flags will be set in tandem quite often 132 | pub fn set_zn_flags(&mut self, value: u8) { 133 | self.set_status_flag(StatusFlag::Zero, value == 0x00); 134 | self.set_status_flag(StatusFlag::Negative, (value as i8) < 0); 135 | } 136 | 137 | 138 | pub fn reset(&mut self) { 139 | let pc = self.read_word_le(RESET_VECTOR); 140 | self.pc = pc; 141 | 142 | // I'm only doing this to avoid dead code warning :) 143 | self.set_status_flag(StatusFlag::Unused, false); 144 | } 145 | 146 | 147 | pub fn update(&mut self, c64_cycle_cnt: u32) { 148 | // check for irq and nmi 149 | match self.state { 150 | CPUState::FetchOp => { 151 | if self.nmi && self.nmi_cycles_left == 0 && (c64_cycle_cnt - (self.first_nmi_cycle as u32) >= 2) { 152 | self.nmi_cycles_left = 7; 153 | self.state = CPUState::ProcessNMI; 154 | } 155 | else if !self.get_status_flag(StatusFlag::InterruptDisable) { 156 | let irq_ready = (self.cia_irq || self.vic_irq) && self.irq_cycles_left == 0; 157 | 158 | if irq_ready && (c64_cycle_cnt - (self.first_irq_cycle as u32) >= 2) { 159 | self.irq_cycles_left = 7; 160 | self.state = CPUState::ProcessIRQ; 161 | } 162 | } 163 | }, 164 | _ => {} 165 | } 166 | 167 | match self.state { 168 | CPUState::FetchOp => { 169 | if self.ba_low { return; } 170 | let next_op = self.next_byte(); 171 | match opcodes::get_instruction(next_op) { 172 | Some((opcode, total_cycles, is_rmw, addr_mode)) => { 173 | self.instruction.opcode = opcode; 174 | self.instruction.addr_mode = addr_mode; 175 | self.instruction.is_rmw = is_rmw; 176 | self.instruction.calculate_cycles(total_cycles, is_rmw); 177 | if self.debug_instr { utils::debug_instruction(next_op, self); } 178 | } 179 | None => panic!("Can't fetch instruction") 180 | } 181 | 182 | // jump straight to op execution unless operand address needs to be fetched 183 | match self.instruction.addr_mode { 184 | opcodes::AddrMode::Implied => self.state = CPUState::ExecuteOp, 185 | opcodes::AddrMode::Accumulator => self.state = CPUState::ExecuteOp, 186 | opcodes::AddrMode::Immediate => self.state = CPUState::ExecuteOp, 187 | opcodes::AddrMode::Relative => { 188 | // TODO: inc PC only during op execution? 189 | let base = (self.pc + 1) as i16; 190 | let offset = self.next_byte() as i8; 191 | self.instruction.operand_addr = (base + offset as i16) as u16; 192 | self.state = CPUState::ExecuteOp; 193 | }, 194 | _ => self.state = CPUState::FetchOperandAddr, 195 | }; 196 | }, 197 | CPUState::FetchOperandAddr => { 198 | if self.ba_low { return; } 199 | if opcodes::fetch_operand_addr(self) { 200 | if self.instruction.is_rmw { 201 | self.state = CPUState::PerformRMW; 202 | } 203 | else { 204 | self.state = CPUState::ExecuteOp; 205 | } 206 | } 207 | 208 | // TODO: odd case? Some instructions can be executed immediately after operand fetch 209 | if self.instruction.cycles_to_run == 0 && self.instruction.cycles_to_fetch == 0 { 210 | //panic!("Not sure if this should happen - reinvestigate"); 211 | opcodes::run(self); 212 | self.state = CPUState::FetchOp; 213 | } 214 | } 215 | CPUState::ProcessIRQ => { 216 | if self.process_irq(false) { 217 | self.cia_irq = false; 218 | self.vic_irq = false; 219 | self.state = CPUState::FetchOp; 220 | } 221 | }, 222 | CPUState::ProcessNMI => { 223 | if self.process_irq(true) { 224 | self.nmi = false; 225 | self.state = CPUState::FetchOp; 226 | } 227 | }, 228 | CPUState::PerformRMW => { 229 | match self.instruction.cycles_to_rmw { 230 | 2 => { 231 | if self.ba_low { return; } 232 | let addr = self.instruction.operand_addr; 233 | self.instruction.rmw_buffer = self.read_byte(addr); 234 | }, 235 | 1 => { 236 | let addr = self.instruction.operand_addr; 237 | let val = self.instruction.rmw_buffer; 238 | self.write_byte(addr, val); 239 | self.state = CPUState::ExecuteOp; 240 | }, 241 | _ => panic!("Too many cycles in RMW stage! ({}) ", self.instruction.cycles_to_rmw) 242 | } 243 | 244 | self.instruction.cycles_to_rmw -= 1; 245 | }, 246 | CPUState::ExecuteOp => { 247 | if opcodes::run(self) { 248 | self.state = CPUState::FetchOp; 249 | } 250 | } 251 | } 252 | } 253 | 254 | 255 | pub fn next_byte(&mut self) -> u8 { 256 | let pc = self.pc; 257 | let op = self.read_byte(pc); 258 | self.pc += 1; 259 | op 260 | } 261 | 262 | 263 | // stack memory: $0100 - $01FF (256 byes) 264 | pub fn push_byte(&mut self, value: u8) { 265 | self.sp -= 0x01; 266 | let new_sp = (self.sp + 0x01) as u16; 267 | self.write_byte(0x0100 + new_sp, value); 268 | } 269 | 270 | 271 | pub fn pop_byte(&mut self) -> u8 { 272 | let addr = 0x0100 + (self.sp + 0x01) as u16; 273 | let value = self.read_byte(addr); 274 | self.sp += 0x01; 275 | value 276 | } 277 | 278 | 279 | pub fn push_word(&mut self, value: u16) { 280 | self.push_byte(((value >> 8) & 0xFF) as u8); 281 | self.push_byte((value & 0xFF) as u8); 282 | } 283 | 284 | 285 | pub fn write_byte(&mut self, addr: u16, value: u8) -> bool { 286 | let mut on_write = Callback::None; 287 | let mut mem_write_ok = true; 288 | let io_enabled = as_ref!(self.mem_ref).io_on; 289 | 290 | if io_enabled { 291 | match addr { 292 | /* VIC-II */ 0xD000..=0xD3FF => as_mut!(self.vic_ref).write_register(addr, value, &mut on_write), 293 | /* SID */ 0xD400..=0xD7FF => as_mut!(self.sid_ref).write_register(addr, value), 294 | /* color RAM */ 0xD800..=0xDBFF => mem_write_ok = as_mut!(self.mem_ref).write_byte(addr, value & 0x0F), 295 | /* CIA1 */ 0xDC00..=0xDCFF => as_mut!(self.cia1_ref).write_register(addr, value, &mut on_write), 296 | /* CIA2 */ 0xDD00..=0xDDFF => as_mut!(self.cia2_ref).write_register(addr, value, &mut on_write), 297 | _ => mem_write_ok = as_mut!(self.mem_ref).write_byte(addr, value), 298 | } 299 | } 300 | else { 301 | mem_write_ok = as_mut!(self.mem_ref).write_byte(addr, value); 302 | } 303 | 304 | // on VIC/CIA register write perform necessary action on the CPU 305 | match on_write { 306 | Callback::TriggerVICIrq => self.set_vic_irq(true), 307 | Callback::ClearVICIrq => self.set_vic_irq(false), 308 | Callback::TriggerCIAIrq => self.set_cia_irq(true), 309 | Callback::ClearCIAIrq => self.set_cia_irq(false), 310 | Callback::TriggerNMI => self.set_nmi(true), 311 | Callback::ClearNMI => self.set_nmi(false), 312 | _ => (), 313 | } 314 | 315 | mem_write_ok 316 | } 317 | 318 | 319 | pub fn read_byte(&mut self, addr: u16) -> u8 { 320 | let byte: u8; 321 | let mut on_read = Callback::None; 322 | let io_enabled = as_ref!(self.mem_ref).io_on; 323 | 324 | if io_enabled { 325 | match addr { 326 | /* VIC-II */ 0xD000..=0xD3FF => byte = as_mut!(self.vic_ref).read_register(addr), 327 | /* SID */ 0xD400..=0xD7FF => byte = as_mut!(self.sid_ref).read_register(addr), 328 | /* color RAM */ 0xD800..=0xDBFF => byte = (as_ref!(self.mem_ref).read_byte(addr) & 0x0F) | (as_ref!(self.vic_ref).last_byte & 0xF0), 329 | /* CIA1 */ 0xDC00..=0xDCFF => byte = as_mut!(self.cia1_ref).read_register(addr, &mut on_read), 330 | /* CIA2 */ 0xDD00..=0xDDFF => byte = as_mut!(self.cia2_ref).read_register(addr, &mut on_read), 331 | 0xDF00..=0xDF9F => byte = as_ref!(self.vic_ref).last_byte, 332 | 0xDFFF => { 333 | self.dfff_byte = !self.dfff_byte; 334 | byte = self.dfff_byte; 335 | }, 336 | _ => byte = as_mut!(self.mem_ref).read_byte(addr) 337 | } 338 | } 339 | else { 340 | byte = as_mut!(self.mem_ref).read_byte(addr); 341 | } 342 | 343 | match on_read { 344 | Callback::TriggerCIAIrq => self.set_cia_irq(true), 345 | Callback::ClearCIAIrq => self.set_cia_irq(false), 346 | Callback::TriggerNMI => self.set_nmi(true), 347 | Callback::ClearNMI => self.set_nmi(false), 348 | _ => (), 349 | } 350 | 351 | byte 352 | } 353 | 354 | 355 | pub fn read_word_le(&self, addr: u16) -> u16 { 356 | as_ref!(self.mem_ref).read_word_le(addr) 357 | } 358 | 359 | 360 | pub fn set_vic_irq(&mut self, val: bool) { 361 | self.vic_irq = val; 362 | } 363 | 364 | 365 | pub fn set_nmi(&mut self, val: bool) { 366 | self.nmi = val; 367 | } 368 | 369 | 370 | pub fn set_cia_irq(&mut self, val: bool) { 371 | self.cia_irq = val; 372 | } 373 | 374 | 375 | pub fn get_operand(&mut self) -> u8 { 376 | // RMW instruction store pre-fetched operand value in internal buffer 377 | if self.instruction.is_rmw { 378 | return self.instruction.rmw_buffer; 379 | } 380 | 381 | let val = match self.instruction.addr_mode { 382 | opcodes::AddrMode::Implied => panic!("Can't get operand value!"), 383 | opcodes::AddrMode::Accumulator => self.a, 384 | opcodes::AddrMode::Immediate => self.next_byte(), 385 | _ => { 386 | let addr = self.instruction.operand_addr; 387 | self.read_byte(addr) 388 | } 389 | }; 390 | 391 | val 392 | } 393 | 394 | 395 | pub fn set_operand(&mut self, val: u8) { 396 | match self.instruction.addr_mode { 397 | opcodes::AddrMode::Implied => panic!("Can't set implied operand value!"), 398 | opcodes::AddrMode::Accumulator => self.a = val, 399 | opcodes::AddrMode::Immediate => panic!("Can't set immediate operand value!"), 400 | opcodes::AddrMode::Relative => panic!("Can't set relative operand value!"), 401 | _ => { 402 | let addr = self.instruction.operand_addr; 403 | let _ = self.write_byte(addr, val); 404 | } 405 | } 406 | } 407 | 408 | 409 | // perform add with carry 410 | pub fn adc(&mut self, value: u8) { 411 | let c = self.get_status_flag(StatusFlag::Carry); 412 | let a = self.a as u16; 413 | let v = value as u16; 414 | 415 | if self.get_status_flag(StatusFlag::DecimalMode) { 416 | let mut lo = (a & 0x0F).wrapping_add(v & 0x0F); 417 | 418 | if c { 419 | lo = lo.wrapping_add(0x01); 420 | } 421 | 422 | if lo > 9 { 423 | lo = lo.wrapping_add(0x06); 424 | } 425 | 426 | let mut hi = (a >> 4).wrapping_add(v >> 4); 427 | if lo > 0x0F { 428 | hi = hi.wrapping_add(0x01); 429 | } 430 | 431 | let is_overflow = ((((hi << 4) ^ a) & 0x80) != 0) && (((a ^ v) & 0x80) == 0); 432 | let mut is_zero = a.wrapping_add(v); 433 | 434 | if c { 435 | is_zero = is_zero.wrapping_add(0x01); 436 | } 437 | 438 | self.set_status_flag(StatusFlag::Negative, (hi << 4) != 0); // TODO: is this ok? 439 | self.set_status_flag(StatusFlag::Overflow, is_overflow); 440 | self.set_status_flag(StatusFlag::Zero, is_zero == 0); 441 | 442 | if hi > 9 { 443 | hi = hi.wrapping_add(0x06); 444 | } 445 | 446 | self.set_status_flag(StatusFlag::Carry, hi > 0xF); 447 | self.a = ((hi << 4) | (lo & 0xF)) as u8; 448 | } 449 | else { 450 | // TODO: should operation wrap automatically here? 451 | let mut res = a.wrapping_add(v); 452 | 453 | if c 454 | { 455 | res = res.wrapping_add(0x1); 456 | } 457 | 458 | self.set_status_flag(StatusFlag::Carry, (res & 0x0100) != 0); 459 | let is_overflow = (a ^ v) & 0x80 == 0 && (a ^ res) & 0x80 == 0x80; 460 | self.set_status_flag(StatusFlag::Overflow, is_overflow); 461 | self.a = res as u8; 462 | self.set_zn_flags(res as u8); 463 | } 464 | } 465 | 466 | 467 | // perform substraction with carry 468 | pub fn sbc(&mut self, value: u8) { 469 | let a = self.a as u16; 470 | let v = value as u16; 471 | let mut res: u16 = a.wrapping_sub(v); 472 | 473 | if !self.get_status_flag(StatusFlag::Carry) { 474 | res = res.wrapping_sub(0x0001); 475 | } 476 | 477 | if self.get_status_flag(StatusFlag::DecimalMode) { 478 | let mut lo = (a & 0x0F).wrapping_sub(v & 0x0F); 479 | let mut hi = (a >> 4).wrapping_sub(v >> 4); 480 | 481 | if !self.get_status_flag(StatusFlag::Carry) { 482 | lo = lo.wrapping_sub(0x01); 483 | } 484 | 485 | if (lo & 0x10) != 0 { 486 | lo = lo.wrapping_sub(0x06); 487 | hi = hi.wrapping_sub(0x01); 488 | } 489 | 490 | if (hi & 0x10) != 0 { 491 | hi = hi.wrapping_sub(0x06); 492 | } 493 | 494 | let is_overflow = (a ^ res) & 0x80 != 0 && (a ^ v) & 0x80 == 0x80; 495 | 496 | self.set_status_flag(StatusFlag::Carry, (res & 0x0100) == 0); 497 | self.set_status_flag(StatusFlag::Overflow, is_overflow); 498 | self.set_zn_flags(res as u8); 499 | 500 | self.a = ((hi << 4) | (lo & 0xF)) as u8; 501 | } 502 | else { 503 | // TODO: should operation wrap automatically here? 504 | self.set_status_flag(StatusFlag::Carry, (res & 0x0100) == 0); 505 | let is_overflow = (a ^ res) & 0x80 != 0 && (a ^ v) & 0x80 == 0x80; 506 | self.set_status_flag(StatusFlag::Overflow, is_overflow); 507 | self.a = res as u8; 508 | self.set_zn_flags(res as u8); 509 | } 510 | } 511 | 512 | 513 | // perform a branch 514 | pub fn branch(&mut self, flag_condition: bool, cycle: u8) -> bool { 515 | match cycle { 516 | 3 => { 517 | if self.ba_low { return false; } 518 | if flag_condition { 519 | let addr = self.instruction.operand_addr; 520 | let pc = self.pc; 521 | self.instruction.zp_crossed = (addr >> 8) != (pc >> 8); 522 | } 523 | else { 524 | // no branching - finish instruction after only 2 cycles 525 | self.instruction.cycles_to_run = 1; 526 | } 527 | }, 528 | 2 => { 529 | if !self.instruction.zp_crossed { 530 | self.first_irq_cycle += 1; 531 | self.first_nmi_cycle += 1; 532 | } 533 | if self.ba_low { return false; } 534 | 535 | let addr = self.instruction.operand_addr; 536 | self.pc = addr; 537 | 538 | if !self.instruction.zp_crossed { 539 | // no page crossing - finish instruction after only 3 cycle 540 | self.instruction.cycles_to_run = 1; 541 | } 542 | }, 543 | 1 => { 544 | if self.ba_low { return false; } 545 | }, 546 | _ => panic!("Wrong number of branching cycles"), 547 | } 548 | 549 | true 550 | } 551 | 552 | 553 | // *** private functions *** // 554 | 555 | fn process_irq(&mut self, is_nmi: bool) -> bool { 556 | let new_pc = if is_nmi { NMI_VECTOR } else { IRQ_VECTOR }; 557 | let cycle_cnt = if is_nmi { self.nmi_cycles_left } else { self.irq_cycles_left }; 558 | 559 | match cycle_cnt { 560 | 7 | 6 => { 561 | if self.ba_low { return false; } 562 | }, 563 | 5 => { 564 | let pc_hi = (self.pc >> 8) as u8; 565 | self.push_byte(pc_hi); 566 | }, 567 | 4 => { 568 | let pc_lo = self.pc as u8; 569 | self.push_byte(pc_lo); 570 | }, 571 | 3 => { 572 | self.set_status_flag(StatusFlag::Break, false); 573 | let curr_p = self.p; 574 | self.push_byte(curr_p); 575 | self.set_status_flag(StatusFlag::InterruptDisable, true); 576 | }, 577 | 2 => { 578 | if self.ba_low { return false; } // TODO: is reading whole word ok in cycle 1? 579 | }, 580 | 1 => { 581 | if self.ba_low { return false; } 582 | self.pc = as_ref!(self.mem_ref).read_word_le(new_pc); 583 | } 584 | _ => panic!("Invalid IRQ/NMI cycle") 585 | } 586 | 587 | if is_nmi { 588 | self.nmi_cycles_left -= 1; 589 | self.nmi_cycles_left == 0 590 | } 591 | else { 592 | self.irq_cycles_left -= 1; 593 | self.irq_cycles_left == 0 594 | } 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /src/c64/cia.rs: -------------------------------------------------------------------------------- 1 | // CIA chip 2 | use c64::cpu; 3 | use c64::memory; 4 | use c64::vic; 5 | use std::rc::Rc; 6 | use std::cell::RefCell; 7 | 8 | pub type CIAShared = Rc>; 9 | 10 | enum TimerState { 11 | Stop, 12 | WaitCount, 13 | LoadStop, 14 | LoadCount, 15 | LoadWaitCount, 16 | Count, 17 | CountStop 18 | } 19 | 20 | 21 | // Struct for CIA timer A/B 22 | struct CIATimer { 23 | state: TimerState, // current state of the timer 24 | is_ta: bool, // is this timer A? 25 | value: u16, // timer value (TA/TB) 26 | latch: u16, // timer latch 27 | ctrl: u8, // control timer (CRA/CRB) 28 | new_ctrl: u8, 29 | has_new_ctrl: bool, 30 | is_cnt_phi2: bool, // timer is counting phi2 31 | irq_next_cycle: bool, // perform timer interrupt next cycle 32 | underflow: bool, // timer underflowed 33 | cnt_ta_underflow: bool, // timer is counting underflows of Timer A 34 | } 35 | 36 | impl CIATimer { 37 | pub fn new(is_ta: bool) -> CIATimer { 38 | CIATimer { 39 | state: TimerState::Stop, 40 | is_ta: is_ta, 41 | value: 0xFFFF, 42 | latch: 1, 43 | ctrl: 0, 44 | new_ctrl: 0, 45 | has_new_ctrl: false, 46 | is_cnt_phi2: false, 47 | irq_next_cycle: false, 48 | underflow: false, 49 | cnt_ta_underflow: false, 50 | } 51 | } 52 | 53 | 54 | pub fn reset(&mut self) { 55 | self.state = TimerState::Stop; 56 | self.value = 0xFFFF; 57 | self.latch = 1; 58 | self.ctrl = 0; 59 | self.new_ctrl = 0; 60 | self.has_new_ctrl = false; 61 | self.is_cnt_phi2 = false; 62 | self.irq_next_cycle = false; 63 | self.underflow = false; 64 | self.cnt_ta_underflow = false; 65 | } 66 | 67 | 68 | pub fn update(&mut self, cia_icr: &mut u8, ta_underflow: bool) { 69 | match self.state { 70 | TimerState::Stop => (), 71 | TimerState::WaitCount => { 72 | self.state = TimerState::Count; 73 | }, 74 | TimerState::LoadStop => { 75 | self.state = TimerState::Stop; 76 | self.value = self.latch; 77 | }, 78 | TimerState::LoadCount => { 79 | self.state = TimerState::Count; 80 | self.value = self.latch; 81 | }, 82 | TimerState::LoadWaitCount => { 83 | self.state = TimerState::WaitCount; 84 | 85 | if self.value == 1 { 86 | self.irq(cia_icr); 87 | } 88 | else { 89 | self.value = self.latch; 90 | } 91 | } 92 | TimerState::Count => { 93 | self.count(cia_icr, ta_underflow); 94 | }, 95 | TimerState::CountStop => { 96 | self.state = TimerState::Stop; 97 | self.count(cia_icr, ta_underflow); 98 | } 99 | } 100 | 101 | self.idle(); 102 | } 103 | 104 | 105 | pub fn idle(&mut self) { 106 | if self.has_new_ctrl { 107 | match self.state { 108 | TimerState::Stop | TimerState::LoadStop => { 109 | if (self.new_ctrl & 1) != 0 { 110 | if (self.new_ctrl & 0x10) != 0 { 111 | self.state = TimerState::LoadWaitCount; 112 | } 113 | else { 114 | self.state = TimerState::WaitCount; 115 | } 116 | } 117 | else { 118 | if (self.new_ctrl & 0x10) != 0 { 119 | self.state = TimerState::LoadStop; 120 | } 121 | } 122 | }, 123 | TimerState::WaitCount | TimerState::LoadCount => { 124 | if (self.new_ctrl & 1) != 0 { 125 | if (self.new_ctrl & 8) != 0 { 126 | self.new_ctrl &= 0xFE; 127 | self.state = TimerState::Stop; 128 | } 129 | else { 130 | if (self.new_ctrl & 0x10) != 0 { 131 | self.state = TimerState::LoadWaitCount; 132 | } 133 | } 134 | } 135 | else { 136 | self.state = TimerState::Stop; 137 | } 138 | }, 139 | TimerState::Count => { 140 | if (self.new_ctrl & 1) != 0 { 141 | if (self.new_ctrl & 0x10) != 0 { 142 | self.state = TimerState::LoadWaitCount; 143 | } 144 | } 145 | else { 146 | if (self.new_ctrl & 0x10) != 0 { 147 | self.state = TimerState::LoadStop; 148 | } 149 | else { 150 | self.state = TimerState::CountStop; 151 | } 152 | } 153 | }, 154 | _ => (), 155 | } 156 | 157 | self.ctrl = self.new_ctrl & 0xEF; 158 | self.has_new_ctrl = false; 159 | } 160 | } 161 | 162 | 163 | pub fn irq(&mut self, cia_icr: &mut u8) { 164 | self.value = self.latch; 165 | self.irq_next_cycle = true; 166 | *cia_icr |= if self.is_ta { 1 } else { 2 }; 167 | 168 | if (self.ctrl & 8) != 0 { 169 | self.ctrl &= 0xFE; 170 | self.new_ctrl &= 0xFE; 171 | self.state = TimerState::LoadStop; 172 | } 173 | else { 174 | self.state = TimerState::LoadCount; 175 | } 176 | } 177 | 178 | 179 | pub fn count(&mut self, cia_icr: &mut u8, ta_underflow: bool) { 180 | if self.is_cnt_phi2 || (self.cnt_ta_underflow && ta_underflow) { 181 | let curr_val = self.value; 182 | self.value -= 1; 183 | if (curr_val == 0) || (self.value == 0) { 184 | match self.state { 185 | TimerState::Stop => (), 186 | _ => self.irq(cia_icr), 187 | } 188 | 189 | self.underflow = true; 190 | } 191 | } 192 | } 193 | } 194 | 195 | 196 | // the actual CIA chip including both timers 197 | pub struct CIA { 198 | mem_ref: Option, 199 | cpu_ref: Option, 200 | vic_ref: Option, 201 | 202 | is_cia1: bool, // is this CIA1 or CIA2 chip? 203 | 204 | timer_a: CIATimer, 205 | timer_b: CIATimer, 206 | irq_mask: u8, 207 | icr: u8, 208 | pra: u8, 209 | prb: u8, 210 | ddra: u8, 211 | ddrb: u8, 212 | sdr: u8, 213 | 214 | // TOD timer 215 | tod_halt: bool, 216 | tod_freq_div: u16, 217 | tod_hour: u8, 218 | tod_min: u8, 219 | tod_sec: u8, 220 | tod_dsec: u8, // deciseconds 221 | 222 | // alarm time 223 | alarm_hour: u8, 224 | alarm_min: u8, 225 | alarm_sec: u8, 226 | alarm_dsec: u8, 227 | 228 | // CIA1 only 229 | pub key_matrix: [u8; 8], 230 | pub rev_matrix: [u8; 8], 231 | pub joystick_1: u8, 232 | pub joystick_2: u8, 233 | prev_lp: u8, 234 | 235 | // CIA2 only 236 | iec_lines: u8, 237 | } 238 | 239 | impl CIA { 240 | pub fn new_shared(is_cia1: bool) -> CIAShared { 241 | Rc::new(RefCell::new(CIA { 242 | mem_ref: None, 243 | cpu_ref: None, 244 | vic_ref: None, 245 | 246 | is_cia1: is_cia1, 247 | timer_a: CIATimer::new(true), 248 | timer_b: CIATimer::new(false), 249 | irq_mask: 0, 250 | icr: 0, 251 | pra: 0, 252 | prb: 0, 253 | ddra: 0, 254 | ddrb: 0, 255 | sdr: 0, 256 | 257 | tod_halt: false, 258 | tod_freq_div: 0, 259 | tod_hour: 0, 260 | tod_min: 0, 261 | tod_sec: 0, 262 | tod_dsec: 0, 263 | alarm_hour: 0, 264 | alarm_min: 0, 265 | alarm_sec: 0, 266 | alarm_dsec: 0, 267 | 268 | // CIA1 only 269 | key_matrix: [0xFF; 8], 270 | rev_matrix: [0xFF; 8], 271 | joystick_1: 0xFF, 272 | joystick_2: 0xFF, 273 | prev_lp: 0x10, 274 | 275 | // CIA2 only 276 | iec_lines: 0xD0 277 | })) 278 | } 279 | 280 | 281 | pub fn set_references(&mut self, memref: memory::MemShared, cpuref: cpu::CPUShared, vicref: vic::VICShared) { 282 | self.mem_ref = Some(memref); 283 | self.cpu_ref = Some(cpuref); 284 | self.vic_ref = Some(vicref); 285 | } 286 | 287 | 288 | pub fn reset(&mut self) { 289 | self.timer_a.reset(); 290 | self.timer_b.reset(); 291 | self.irq_mask = 0; 292 | self.icr = 0; 293 | self.pra = 0; 294 | self.prb = 0; 295 | self.ddra = 0; 296 | self.ddrb = 0; 297 | self.sdr = 0; 298 | self.tod_halt = false; 299 | self.tod_freq_div = 0; 300 | self.tod_hour = 0; 301 | self.tod_min = 0; 302 | self.tod_sec = 0; 303 | self.tod_dsec = 0; 304 | self.alarm_hour = 0; 305 | self.alarm_min = 0; 306 | self.alarm_sec = 0; 307 | self.alarm_dsec = 0; 308 | 309 | // CIA1 only 310 | for i in 0..8 { 311 | self.key_matrix[i] = 0xFF; 312 | self.rev_matrix[i] = 0xFF; 313 | } 314 | 315 | self.joystick_1 = 0xFF; 316 | self.joystick_2 = 0xFF; 317 | self.prev_lp = 0x10; 318 | 319 | // CIA2 only 320 | self.iec_lines = 0xD0; 321 | } 322 | 323 | 324 | pub fn update(&mut self) { 325 | self.timer_a.update(&mut self.icr, false); 326 | let ta_underflow = self.timer_a.underflow; 327 | self.timer_b.update(&mut self.icr, ta_underflow); 328 | } 329 | 330 | 331 | pub fn read_register(&mut self, addr: u16, on_cia_read: &mut cpu::Callback) -> u8 { 332 | // CIA1 and CIA2 share behavior for certain addresses 333 | match addr & 0x00FF { 334 | 0x02 => self.ddra, 335 | 0x03 => self.ddrb, 336 | 0x04 => self.timer_a.value as u8, 337 | 0x05 => (self.timer_a.value >> 8) as u8, 338 | 0x06 => self.timer_b.value as u8, 339 | 0x07 => (self.timer_b.value >> 8) as u8, 340 | 0x08 => { 341 | self.tod_halt = false; 342 | self.tod_dsec 343 | }, 344 | 0x09 => self.tod_sec, 345 | 0x0A => self.tod_min, 346 | 0x0B => { 347 | self.tod_halt = true; 348 | self.tod_hour 349 | }, 350 | 0x0C => self.sdr, 351 | 0x0D => { 352 | let curr_icr = self.icr; 353 | self.icr = 0; 354 | *on_cia_read = if self.is_cia1 { cpu::Callback::ClearCIAIrq } else { cpu::Callback::ClearNMI }; 355 | curr_icr 356 | }, 357 | 0x0E => self.timer_a.ctrl, 358 | 0x0F => self.timer_b.ctrl, 359 | 0x10..=0xFF => self.read_register((addr & 0xFF00) + (addr % 0x0010), on_cia_read), 360 | _ => { 361 | // CIA1/2 specific read-values for 0x00 and 0x01 362 | if self.is_cia1 { 363 | self.read_cia1_register(addr) 364 | } 365 | else { 366 | self.read_cia2_register(addr) 367 | } 368 | } 369 | } 370 | } 371 | 372 | 373 | pub fn write_register(&mut self, addr: u16, value: u8, on_cia_write: &mut cpu::Callback) { 374 | match addr & 0x00FF { 375 | 0x04 => { 376 | self.timer_a.latch = (self.timer_a.latch & 0xFF00) | value as u16; 377 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 378 | }, 379 | 0x05 => { 380 | self.timer_a.latch = (self.timer_a.latch & 0x00FF) | ((value as u16) << 8); 381 | if (self.timer_a.ctrl & 1) == 0 { 382 | self.timer_a.value = self.timer_a.latch; 383 | } 384 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 385 | }, 386 | 0x06 => { 387 | self.timer_b.latch = (self.timer_b.latch & 0xFF00) | value as u16; 388 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 389 | }, 390 | 0x07 => { 391 | self.timer_b.latch = (self.timer_b.latch & 0x00FF) | ((value as u16) << 8); 392 | if (self.timer_b.ctrl & 1) == 0 { 393 | self.timer_b.value = self.timer_b.latch; 394 | } 395 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 396 | }, 397 | 0x08 => { 398 | if (self.timer_b.ctrl & 0x80) != 0 { 399 | self.alarm_dsec = value & 0x0F; 400 | } 401 | else { 402 | self.tod_dsec = value & 0x0F; 403 | } 404 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 405 | }, 406 | 0x09 => { 407 | if (self.timer_b.ctrl & 0x80) != 0 { 408 | self.alarm_sec = value & 0x7F; 409 | } 410 | else { 411 | self.tod_sec = value & 0x7F; 412 | } 413 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 414 | }, 415 | 0x0A => { 416 | if (self.timer_b.ctrl & 0x80) != 0 { 417 | self.alarm_min = value & 0x7F; 418 | } 419 | else { 420 | self.tod_min = value & 0x7F; 421 | } 422 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 423 | }, 424 | 0x0B => { 425 | if (self.timer_b.ctrl & 0x80) != 0 { 426 | self.alarm_hour = value & 0x9F; 427 | } 428 | else { 429 | self.tod_hour = value & 0x9F; 430 | } 431 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 432 | }, 433 | 0x0C => { 434 | self.sdr = value; 435 | let irq_triggered = self.trigger_irq(8); 436 | if irq_triggered { 437 | *on_cia_write = if self.is_cia1 { cpu::Callback::TriggerCIAIrq } else { cpu::Callback::TriggerNMI }; 438 | } 439 | }, 440 | 0x0D => { 441 | if (value & 0x80) != 0 { 442 | self.irq_mask |= value & 0x7F; 443 | } 444 | else { 445 | self.irq_mask &= !value; 446 | } 447 | 448 | if (self.icr & self.irq_mask & 0x1F) != 0 { 449 | self.icr |= 0x80; 450 | *on_cia_write = if self.is_cia1 { cpu::Callback::TriggerCIAIrq } else { cpu::Callback::TriggerNMI }; 451 | } 452 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 453 | }, 454 | 0x0E => { 455 | self.timer_a.has_new_ctrl = true; 456 | self.timer_a.new_ctrl = value; 457 | self.timer_a.is_cnt_phi2 = (value & 0x20) == 0; 458 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 459 | }, 460 | 0x0F => { 461 | self.timer_b.has_new_ctrl = true; 462 | self.timer_b.new_ctrl = value; 463 | self.timer_b.is_cnt_phi2 = (value & 0x60) == 0; 464 | self.timer_b.cnt_ta_underflow = (value & 0x60) == 0x40; 465 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 466 | }, 467 | _ => { 468 | if self.is_cia1 { 469 | self.write_cia1_register(addr, value, on_cia_write); 470 | } 471 | else { 472 | self.write_cia2_register(addr, value, on_cia_write); 473 | } 474 | } 475 | } 476 | } 477 | 478 | 479 | pub fn process_irq(&mut self) { 480 | if self.timer_a.irq_next_cycle { 481 | if self.trigger_irq(1) { 482 | if self.is_cia1 { 483 | as_mut!(self.cpu_ref).set_cia_irq(true); 484 | } 485 | else { 486 | as_mut!(self.cpu_ref).set_nmi(true); 487 | } 488 | } 489 | 490 | self.timer_a.irq_next_cycle = false 491 | } 492 | if self.timer_a.irq_next_cycle { 493 | if self.trigger_irq(2) { 494 | if self.is_cia1 { 495 | as_mut!(self.cpu_ref).set_cia_irq(true); 496 | } 497 | else { 498 | as_mut!(self.cpu_ref).set_nmi(true); 499 | } 500 | } 501 | 502 | self.timer_a.irq_next_cycle = false 503 | } 504 | } 505 | 506 | 507 | pub fn count_tod(&mut self) { 508 | let mut lo: u8; 509 | let mut hi: u8; 510 | 511 | if self.tod_freq_div != 0 { 512 | self.tod_freq_div -= 1; 513 | } 514 | else { 515 | // adjust frequency according to 50/60Hz flag 516 | if (self.timer_a.ctrl & 0x80) != 0 { 517 | self.tod_freq_div = 4; 518 | } 519 | else { 520 | self.tod_freq_div = 5; 521 | } 522 | 523 | self.tod_dsec += 1; 524 | if self.tod_dsec > 9 { 525 | self.tod_dsec = 0; 526 | 527 | lo = (self.tod_sec & 0x0F) + 1; 528 | hi = self.tod_sec >> 4; 529 | 530 | if lo > 9 { 531 | lo = 0; 532 | hi += 1; 533 | } 534 | 535 | if hi > 5 { 536 | self.tod_sec = 0; 537 | 538 | lo = (self.tod_min & 0x0F) + 1; 539 | hi = self.tod_min >> 4; 540 | 541 | if lo > 9 { 542 | lo = 0; 543 | hi += 1; 544 | } 545 | 546 | if hi > 5 { 547 | self.tod_min = 0; 548 | 549 | lo = (self.tod_hour & 0x0F) + 1; 550 | hi = self.tod_hour >> 4; 551 | 552 | if lo > 9 { 553 | lo = 0; 554 | hi += 1; 555 | } 556 | 557 | self.tod_hour |= (hi << 4) | lo; 558 | if (self.tod_hour & 0x1F) > 0x11 { 559 | self.tod_hour = self.tod_hour & 0x80 ^ 0x80; 560 | } 561 | } 562 | else { 563 | self.tod_min = (hi << 4) | lo; 564 | } 565 | } 566 | else { 567 | self.tod_sec = (hi << 4) | lo; 568 | } 569 | } 570 | 571 | // TODO: update memory registers 572 | // trigger irq if alarm time reached 573 | if (self.tod_dsec == self.alarm_dsec) && 574 | (self.tod_sec == self.alarm_sec) && 575 | (self.tod_min == self.alarm_min) && 576 | (self.tod_hour == self.alarm_hour) { 577 | if self.trigger_irq(4) { 578 | if self.is_cia1 { 579 | as_mut!(self.cpu_ref).set_cia_irq(true); 580 | } 581 | else { 582 | as_mut!(self.cpu_ref).set_nmi(true); 583 | }; 584 | } 585 | } 586 | } 587 | } 588 | 589 | 590 | // true - irq triggered; false - not 591 | pub fn trigger_irq(&mut self, mask: u8) -> bool { 592 | self.icr |= mask; 593 | 594 | if (self.irq_mask & mask) != 0 { 595 | self.icr |= 0x80; 596 | true 597 | } 598 | else { 599 | false 600 | } 601 | } 602 | 603 | 604 | // *** private functions *** // 605 | 606 | fn read_cia1_register(&mut self, addr: u16) -> u8 { 607 | match addr { 608 | 0xDC00 => { 609 | let mut retval = self.pra | !self.ddra; 610 | let tst = (self.prb | !self.ddrb) & self.joystick_1; 611 | 612 | if tst & 0x01 == 0 { retval &= self.rev_matrix[0]; } 613 | if tst & 0x02 == 0 { retval &= self.rev_matrix[1]; } 614 | if tst & 0x04 == 0 { retval &= self.rev_matrix[2]; } 615 | if tst & 0x08 == 0 { retval &= self.rev_matrix[3]; } 616 | if tst & 0x10 == 0 { retval &= self.rev_matrix[4]; } 617 | if tst & 0x20 == 0 { retval &= self.rev_matrix[5]; } 618 | if tst & 0x40 == 0 { retval &= self.rev_matrix[6]; } 619 | if tst & 0x80 == 0 { retval &= self.rev_matrix[7]; } 620 | 621 | retval & self.joystick_2 622 | }, 623 | 0xDC01 => { 624 | let mut retval = !self.ddrb; 625 | let tst = (self.pra | !self.ddra) & self.joystick_2; 626 | 627 | if tst & 0x01 == 0 { retval &= self.key_matrix[0]; } 628 | if tst & 0x02 == 0 { retval &= self.key_matrix[1]; } 629 | if tst & 0x04 == 0 { retval &= self.key_matrix[2]; } 630 | if tst & 0x08 == 0 { retval &= self.key_matrix[3]; } 631 | if tst & 0x10 == 0 { retval &= self.key_matrix[4]; } 632 | if tst & 0x20 == 0 { retval &= self.key_matrix[5]; } 633 | if tst & 0x40 == 0 { retval &= self.key_matrix[6]; } 634 | if tst & 0x80 == 0 { retval &= self.key_matrix[7]; } 635 | 636 | (retval | (self.prb & self.ddrb)) & self.joystick_1 637 | }, 638 | 0xDC10..=0xDCFF => self.read_cia1_register(0xDC00 + (addr % 0x0010)), 639 | _ => panic!("Address out of CIA1 memory range: ${:04X}", addr), 640 | } 641 | } 642 | 643 | 644 | fn write_cia1_register(&mut self, addr: u16, value: u8, on_cia_write: &mut cpu::Callback) { 645 | match addr { 646 | 0xDC00 => { 647 | self.pra = value; 648 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 649 | }, 650 | 0xDC01 => { 651 | self.prb = value; 652 | self.check_lp(); 653 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 654 | }, 655 | 0xDC02 => { 656 | self.ddra = value; 657 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 658 | }, 659 | 0xDC03 => { 660 | self.ddrb = value; 661 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 662 | self.check_lp(); 663 | }, 664 | 0xDC10..=0xDCFF => self.write_cia1_register(0xDC00 + (addr % 0x0010), value, on_cia_write), 665 | _ => panic!("Address out of CIA1 memory range"), 666 | } 667 | } 668 | 669 | 670 | fn read_cia2_register(&mut self, addr: u16) -> u8 { 671 | match addr { 672 | 0xDD00 => { 673 | // TODO 674 | (self.pra | !self.ddra) & 0x3f | self.iec_lines 675 | }, 676 | 0xDD01 => self.prb | !self.ddrb, 677 | 0xDD10..=0xDDFF => self.read_cia2_register(0xDD00 + (addr % 0x0010)), 678 | _ => panic!("Address out of CIA2 memory range ${:04X}", addr), 679 | } 680 | } 681 | 682 | 683 | fn write_cia2_register(&mut self, addr: u16, value: u8, on_cia_write: &mut cpu::Callback) { 684 | match addr { 685 | 0xDD00 => { 686 | // TODO 687 | self.pra = value; 688 | as_mut!(self.vic_ref).on_va_change(!(self.pra | !self.ddra) & 3); 689 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 690 | }, 691 | 0xDD01 => { 692 | self.prb = value; 693 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 694 | }, 695 | 0xDD02 => { 696 | self.ddra = value; 697 | as_mut!(self.vic_ref).on_va_change(!(self.pra | !self.ddra) & 3); 698 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 699 | }, 700 | 0xDD03 => { self.ddrb = value; as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); }, 701 | 0xDD10..=0xDDFF => self.write_cia2_register(0xDD00 + (addr % 0x0010), value, on_cia_write), 702 | _ => panic!("Address out of CIA2 memory range"), 703 | } 704 | } 705 | 706 | 707 | fn check_lp(&mut self) { 708 | if ((self.prb | !self.ddrb) & 0x10) != self.prev_lp { 709 | as_mut!(self.vic_ref).trigger_lp_irq(); 710 | } 711 | 712 | self.prev_lp = (self.prb | !self.ddrb) & 0x10; 713 | } 714 | } 715 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi 0.3.9", 21 | ] 22 | 23 | [[package]] 24 | name = "atty" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 28 | dependencies = [ 29 | "hermit-abi", 30 | "libc", 31 | "winapi 0.3.9", 32 | ] 33 | 34 | [[package]] 35 | name = "autocfg" 36 | version = "1.0.1" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 39 | 40 | [[package]] 41 | name = "bindgen" 42 | version = "0.56.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239" 45 | dependencies = [ 46 | "bitflags 1.3.2", 47 | "cexpr", 48 | "clang-sys", 49 | "clap", 50 | "env_logger", 51 | "lazy_static 1.4.0", 52 | "lazycell", 53 | "log", 54 | "peeking_take_while", 55 | "proc-macro2", 56 | "quote", 57 | "regex", 58 | "rustc-hash", 59 | "shlex", 60 | "which", 61 | ] 62 | 63 | [[package]] 64 | name = "bitflags" 65 | version = "0.7.0" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 68 | 69 | [[package]] 70 | name = "bitflags" 71 | version = "1.3.2" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 74 | 75 | [[package]] 76 | name = "byteorder" 77 | version = "1.4.3" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 80 | 81 | [[package]] 82 | name = "cc" 83 | version = "1.0.72" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 86 | 87 | [[package]] 88 | name = "cexpr" 89 | version = "0.4.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" 92 | dependencies = [ 93 | "nom 5.1.2", 94 | ] 95 | 96 | [[package]] 97 | name = "cfg-if" 98 | version = "0.1.10" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 101 | 102 | [[package]] 103 | name = "cfg-if" 104 | version = "1.0.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 107 | 108 | [[package]] 109 | name = "clang-sys" 110 | version = "1.3.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" 113 | dependencies = [ 114 | "glob", 115 | "libc", 116 | "libloading", 117 | ] 118 | 119 | [[package]] 120 | name = "clap" 121 | version = "2.34.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 124 | dependencies = [ 125 | "ansi_term", 126 | "atty", 127 | "bitflags 1.3.2", 128 | "strsim", 129 | "textwrap", 130 | "unicode-width", 131 | "vec_map", 132 | ] 133 | 134 | [[package]] 135 | name = "cty" 136 | version = "0.2.2" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" 139 | 140 | [[package]] 141 | name = "downcast-rs" 142 | version = "1.2.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" 145 | 146 | [[package]] 147 | name = "enum_primitive" 148 | version = "0.1.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" 151 | dependencies = [ 152 | "num-traits 0.1.40", 153 | ] 154 | 155 | [[package]] 156 | name = "env_logger" 157 | version = "0.8.4" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" 160 | dependencies = [ 161 | "atty", 162 | "humantime", 163 | "log", 164 | "regex", 165 | "termcolor", 166 | ] 167 | 168 | [[package]] 169 | name = "fastrand" 170 | version = "1.6.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" 173 | dependencies = [ 174 | "instant", 175 | ] 176 | 177 | [[package]] 178 | name = "fuchsia-zircon" 179 | version = "0.2.1" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "f6c0581a4e363262e52b87f59ee2afe3415361c6ec35e665924eb08afe8ff159" 182 | dependencies = [ 183 | "fuchsia-zircon-sys", 184 | ] 185 | 186 | [[package]] 187 | name = "fuchsia-zircon-sys" 188 | version = "0.2.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "43f3795b4bae048dc6123a6b972cadde2e676f9ded08aef6bb77f5f157684a82" 191 | dependencies = [ 192 | "bitflags 0.7.0", 193 | ] 194 | 195 | [[package]] 196 | name = "glob" 197 | version = "0.3.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 200 | 201 | [[package]] 202 | name = "hermit-abi" 203 | version = "0.1.19" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 206 | dependencies = [ 207 | "libc", 208 | ] 209 | 210 | [[package]] 211 | name = "humantime" 212 | version = "2.1.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 215 | 216 | [[package]] 217 | name = "instant" 218 | version = "0.1.12" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 221 | dependencies = [ 222 | "cfg-if 1.0.0", 223 | ] 224 | 225 | [[package]] 226 | name = "kernel32-sys" 227 | version = "0.2.2" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 230 | dependencies = [ 231 | "winapi 0.2.8", 232 | "winapi-build", 233 | ] 234 | 235 | [[package]] 236 | name = "lazy_static" 237 | version = "0.2.9" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "c9e5e58fa1a4c3b915a561a78a22ee0cac6ab97dca2504428bc1cb074375f8d5" 240 | 241 | [[package]] 242 | name = "lazy_static" 243 | version = "1.4.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 246 | 247 | [[package]] 248 | name = "lazycell" 249 | version = "1.3.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 252 | 253 | [[package]] 254 | name = "libc" 255 | version = "0.2.112" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" 258 | 259 | [[package]] 260 | name = "libloading" 261 | version = "0.7.3" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" 264 | dependencies = [ 265 | "cfg-if 1.0.0", 266 | "winapi 0.3.9", 267 | ] 268 | 269 | [[package]] 270 | name = "log" 271 | version = "0.4.14" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 274 | dependencies = [ 275 | "cfg-if 1.0.0", 276 | ] 277 | 278 | [[package]] 279 | name = "memchr" 280 | version = "2.4.1" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 283 | 284 | [[package]] 285 | name = "minifb" 286 | version = "0.20.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "f669be3941549a8291969021bf155ffb7bb337d4d2832d24ad7ab91c426c51a7" 289 | dependencies = [ 290 | "cc", 291 | "libc", 292 | "orbclient", 293 | "raw-window-handle 0.3.4", 294 | "tempfile", 295 | "wayland-client", 296 | "wayland-cursor", 297 | "wayland-protocols", 298 | "winapi 0.3.9", 299 | "x11-dl", 300 | "xkb", 301 | "xkbcommon-sys", 302 | ] 303 | 304 | [[package]] 305 | name = "minimal-lexical" 306 | version = "0.2.1" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 309 | 310 | [[package]] 311 | name = "nix" 312 | version = "0.20.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" 315 | dependencies = [ 316 | "bitflags 1.3.2", 317 | "cc", 318 | "cfg-if 1.0.0", 319 | "libc", 320 | ] 321 | 322 | [[package]] 323 | name = "nom" 324 | version = "5.1.2" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 327 | dependencies = [ 328 | "memchr", 329 | "version_check", 330 | ] 331 | 332 | [[package]] 333 | name = "nom" 334 | version = "7.1.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" 337 | dependencies = [ 338 | "memchr", 339 | "minimal-lexical", 340 | "version_check", 341 | ] 342 | 343 | [[package]] 344 | name = "num" 345 | version = "0.1.40" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "a311b77ebdc5dd4cf6449d81e4135d9f0e3b153839ac90e648a8ef538f923525" 348 | dependencies = [ 349 | "num-bigint", 350 | "num-complex", 351 | "num-integer", 352 | "num-iter", 353 | "num-rational", 354 | "num-traits 0.1.40", 355 | ] 356 | 357 | [[package]] 358 | name = "num-bigint" 359 | version = "0.1.44" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" 362 | dependencies = [ 363 | "num-integer", 364 | "num-traits 0.2.14", 365 | "rand", 366 | "rustc-serialize", 367 | ] 368 | 369 | [[package]] 370 | name = "num-complex" 371 | version = "0.1.43" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656" 374 | dependencies = [ 375 | "num-traits 0.2.14", 376 | "rustc-serialize", 377 | ] 378 | 379 | [[package]] 380 | name = "num-integer" 381 | version = "0.1.44" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 384 | dependencies = [ 385 | "autocfg", 386 | "num-traits 0.2.14", 387 | ] 388 | 389 | [[package]] 390 | name = "num-iter" 391 | version = "0.1.34" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" 394 | dependencies = [ 395 | "num-integer", 396 | "num-traits 0.1.40", 397 | ] 398 | 399 | [[package]] 400 | name = "num-rational" 401 | version = "0.1.42" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" 404 | dependencies = [ 405 | "num-bigint", 406 | "num-integer", 407 | "num-traits 0.2.14", 408 | "rustc-serialize", 409 | ] 410 | 411 | [[package]] 412 | name = "num-traits" 413 | version = "0.1.40" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0" 416 | 417 | [[package]] 418 | name = "num-traits" 419 | version = "0.2.14" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 422 | dependencies = [ 423 | "autocfg", 424 | ] 425 | 426 | [[package]] 427 | name = "once_cell" 428 | version = "1.9.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" 431 | 432 | [[package]] 433 | name = "orbclient" 434 | version = "0.3.23" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "135507db238b8326a429e2c9f79cda29167a63223734f3c52956cc54e16f7d46" 437 | dependencies = [ 438 | "redox_syscall 0.1.57", 439 | "sdl2 0.31.0", 440 | ] 441 | 442 | [[package]] 443 | name = "peeking_take_while" 444 | version = "0.1.2" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 447 | 448 | [[package]] 449 | name = "pkg-config" 450 | version = "0.3.9" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 453 | 454 | [[package]] 455 | name = "proc-macro2" 456 | version = "1.0.36" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 459 | dependencies = [ 460 | "unicode-xid", 461 | ] 462 | 463 | [[package]] 464 | name = "quote" 465 | version = "1.0.14" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" 468 | dependencies = [ 469 | "proc-macro2", 470 | ] 471 | 472 | [[package]] 473 | name = "rand" 474 | version = "0.3.17" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "61efcbcd9fa8d8fbb07c84e34a8af18a1ff177b449689ad38a6e9457ecc7b2ae" 477 | dependencies = [ 478 | "fuchsia-zircon", 479 | "libc", 480 | ] 481 | 482 | [[package]] 483 | name = "raw-window-handle" 484 | version = "0.3.4" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "e28f55143d0548dad60bb4fbdc835a3d7ac6acc3324506450c5fdd6e42903a76" 487 | dependencies = [ 488 | "libc", 489 | "raw-window-handle 0.4.2", 490 | ] 491 | 492 | [[package]] 493 | name = "raw-window-handle" 494 | version = "0.4.2" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "fba75eee94a9d5273a68c9e1e105d9cffe1ef700532325788389e5a83e2522b7" 497 | dependencies = [ 498 | "cty", 499 | ] 500 | 501 | [[package]] 502 | name = "redox_syscall" 503 | version = "0.1.57" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 506 | 507 | [[package]] 508 | name = "redox_syscall" 509 | version = "0.2.10" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 512 | dependencies = [ 513 | "bitflags 1.3.2", 514 | ] 515 | 516 | [[package]] 517 | name = "regex" 518 | version = "1.5.4" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 521 | dependencies = [ 522 | "aho-corasick", 523 | "memchr", 524 | "regex-syntax", 525 | ] 526 | 527 | [[package]] 528 | name = "regex-syntax" 529 | version = "0.6.25" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 532 | 533 | [[package]] 534 | name = "remove_dir_all" 535 | version = "0.5.3" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 538 | dependencies = [ 539 | "winapi 0.3.9", 540 | ] 541 | 542 | [[package]] 543 | name = "rust64" 544 | version = "0.6.1" 545 | dependencies = [ 546 | "byteorder", 547 | "enum_primitive", 548 | "minifb", 549 | "num", 550 | "rand", 551 | "sdl2 0.35.1", 552 | "time", 553 | ] 554 | 555 | [[package]] 556 | name = "rustc-hash" 557 | version = "1.1.0" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 560 | 561 | [[package]] 562 | name = "rustc-serialize" 563 | version = "0.3.24" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 566 | 567 | [[package]] 568 | name = "sdl2" 569 | version = "0.31.0" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "a74c2a98a354b20713b90cce70aef9e927e46110d1bc4ef728fd74e0d53eba60" 572 | dependencies = [ 573 | "bitflags 0.7.0", 574 | "lazy_static 0.2.9", 575 | "libc", 576 | "num", 577 | "rand", 578 | "sdl2-sys 0.31.0", 579 | ] 580 | 581 | [[package]] 582 | name = "sdl2" 583 | version = "0.35.1" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "f035f8e87735fa3a8437292be49fe6056450f7cbb13c230b4bcd1bdd7279421f" 586 | dependencies = [ 587 | "bitflags 1.3.2", 588 | "lazy_static 1.4.0", 589 | "libc", 590 | "sdl2-sys 0.35.1", 591 | ] 592 | 593 | [[package]] 594 | name = "sdl2-sys" 595 | version = "0.31.0" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "5c543ce8a6e33a30cb909612eeeb22e693848211a84558d5a00bb11e791b7ab7" 598 | dependencies = [ 599 | "cfg-if 0.1.10", 600 | ] 601 | 602 | [[package]] 603 | name = "sdl2-sys" 604 | version = "0.35.1" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "94cb479353c0603785c834e2307440d83d196bf255f204f7f6741358de8d6a2f" 607 | dependencies = [ 608 | "cfg-if 1.0.0", 609 | "libc", 610 | "version-compare", 611 | ] 612 | 613 | [[package]] 614 | name = "shlex" 615 | version = "0.1.1" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" 618 | 619 | [[package]] 620 | name = "smallvec" 621 | version = "1.8.0" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 624 | 625 | [[package]] 626 | name = "strsim" 627 | version = "0.8.0" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 630 | 631 | [[package]] 632 | name = "tempfile" 633 | version = "3.3.0" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 636 | dependencies = [ 637 | "cfg-if 1.0.0", 638 | "fastrand", 639 | "libc", 640 | "redox_syscall 0.2.10", 641 | "remove_dir_all", 642 | "winapi 0.3.9", 643 | ] 644 | 645 | [[package]] 646 | name = "termcolor" 647 | version = "1.1.2" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 650 | dependencies = [ 651 | "winapi-util", 652 | ] 653 | 654 | [[package]] 655 | name = "textwrap" 656 | version = "0.11.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 659 | dependencies = [ 660 | "unicode-width", 661 | ] 662 | 663 | [[package]] 664 | name = "time" 665 | version = "0.1.38" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" 668 | dependencies = [ 669 | "kernel32-sys", 670 | "libc", 671 | "redox_syscall 0.1.57", 672 | "winapi 0.2.8", 673 | ] 674 | 675 | [[package]] 676 | name = "unicode-width" 677 | version = "0.1.9" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 680 | 681 | [[package]] 682 | name = "unicode-xid" 683 | version = "0.2.2" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 686 | 687 | [[package]] 688 | name = "vec_map" 689 | version = "0.8.2" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 692 | 693 | [[package]] 694 | name = "version-compare" 695 | version = "0.1.0" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" 698 | 699 | [[package]] 700 | name = "version_check" 701 | version = "0.9.4" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 704 | 705 | [[package]] 706 | name = "wayland-client" 707 | version = "0.28.6" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "e3ab332350e502f159382201394a78e3cc12d0f04db863429260164ea40e0355" 710 | dependencies = [ 711 | "bitflags 1.3.2", 712 | "downcast-rs", 713 | "libc", 714 | "nix", 715 | "wayland-commons", 716 | "wayland-scanner", 717 | "wayland-sys", 718 | ] 719 | 720 | [[package]] 721 | name = "wayland-commons" 722 | version = "0.28.6" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "a21817947c7011bbd0a27e11b17b337bfd022e8544b071a2641232047966fbda" 725 | dependencies = [ 726 | "nix", 727 | "once_cell", 728 | "smallvec", 729 | "wayland-sys", 730 | ] 731 | 732 | [[package]] 733 | name = "wayland-cursor" 734 | version = "0.28.6" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "be610084edd1586d45e7bdd275fe345c7c1873598caa464c4fb835dee70fa65a" 737 | dependencies = [ 738 | "nix", 739 | "wayland-client", 740 | "xcursor", 741 | ] 742 | 743 | [[package]] 744 | name = "wayland-protocols" 745 | version = "0.28.6" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "286620ea4d803bacf61fa087a4242ee316693099ee5a140796aaba02b29f861f" 748 | dependencies = [ 749 | "bitflags 1.3.2", 750 | "wayland-client", 751 | "wayland-commons", 752 | "wayland-scanner", 753 | ] 754 | 755 | [[package]] 756 | name = "wayland-scanner" 757 | version = "0.28.6" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "ce923eb2deb61de332d1f356ec7b6bf37094dc5573952e1c8936db03b54c03f1" 760 | dependencies = [ 761 | "proc-macro2", 762 | "quote", 763 | "xml-rs", 764 | ] 765 | 766 | [[package]] 767 | name = "wayland-sys" 768 | version = "0.28.6" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "d841fca9aed7febf9bed2e9796c49bf58d4152ceda8ac949ebe00868d8f0feb8" 771 | dependencies = [ 772 | "pkg-config", 773 | ] 774 | 775 | [[package]] 776 | name = "which" 777 | version = "3.1.1" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" 780 | dependencies = [ 781 | "libc", 782 | ] 783 | 784 | [[package]] 785 | name = "winapi" 786 | version = "0.2.8" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 789 | 790 | [[package]] 791 | name = "winapi" 792 | version = "0.3.9" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 795 | dependencies = [ 796 | "winapi-i686-pc-windows-gnu", 797 | "winapi-x86_64-pc-windows-gnu", 798 | ] 799 | 800 | [[package]] 801 | name = "winapi-build" 802 | version = "0.1.1" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 805 | 806 | [[package]] 807 | name = "winapi-i686-pc-windows-gnu" 808 | version = "0.4.0" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 811 | 812 | [[package]] 813 | name = "winapi-util" 814 | version = "0.1.5" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 817 | dependencies = [ 818 | "winapi 0.3.9", 819 | ] 820 | 821 | [[package]] 822 | name = "winapi-x86_64-pc-windows-gnu" 823 | version = "0.4.0" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 826 | 827 | [[package]] 828 | name = "x11-dl" 829 | version = "2.19.1" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" 832 | dependencies = [ 833 | "lazy_static 1.4.0", 834 | "libc", 835 | "pkg-config", 836 | ] 837 | 838 | [[package]] 839 | name = "xcursor" 840 | version = "0.3.4" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" 843 | dependencies = [ 844 | "nom 7.1.0", 845 | ] 846 | 847 | [[package]] 848 | name = "xkb" 849 | version = "0.2.1" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "aec02bc5de902aa579f3d2f2c522edaf40fa42963cbaffe645b058ddcc68fdb2" 852 | dependencies = [ 853 | "bitflags 1.3.2", 854 | "libc", 855 | "xkbcommon-sys", 856 | ] 857 | 858 | [[package]] 859 | name = "xkbcommon-sys" 860 | version = "0.7.5" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "59a001b79d45b0b4541c228a501177f2b35db976bf7ee3f7fce8fa2381554ab5" 863 | dependencies = [ 864 | "bindgen", 865 | "libc", 866 | "pkg-config", 867 | ] 868 | 869 | [[package]] 870 | name = "xml-rs" 871 | version = "0.8.4" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" 874 | -------------------------------------------------------------------------------- /src/c64/sid.rs: -------------------------------------------------------------------------------- 1 | // SID chip 2 | extern crate rand; 3 | #[cfg(not(target_os = "redox"))] 4 | extern crate sdl2; 5 | 6 | #[cfg(not(target_os = "redox"))] 7 | use self::sdl2::audio::{ AudioCallback, AudioSpecDesired }; 8 | use c64::memory; 9 | use c64::sid_tables::*; 10 | use std::cell::RefCell; 11 | use std::f32; 12 | use std::rc::Rc; 13 | 14 | pub type SIDShared = Rc>; 15 | 16 | const SAMPLE_FREQ: u32 = 44100; // output frequency 17 | const SID_FREQ: u32 = 985248; // SID frequency in Hz 18 | pub const SID_CYCLES: u32 = SID_FREQ / SAMPLE_FREQ; // SID clocks/sample frame 19 | const NUM_SAMPLES: usize = 624; // size of buffer for sampled voice 20 | 21 | 22 | enum WaveForm { 23 | None, 24 | Triangle, 25 | Saw, 26 | TriSaw, 27 | Pulse, 28 | TriPulse, 29 | SawPulse, 30 | TriSawPulse, 31 | Noise 32 | } 33 | 34 | enum VoiceState { 35 | Idle, 36 | Attack, 37 | Decay, 38 | Release 39 | } 40 | 41 | #[derive(PartialEq)] 42 | enum FilterType { 43 | None, 44 | Lowpass, 45 | Bandpass, 46 | LowBandpass, 47 | Highpass, 48 | Notch, 49 | HighBandpass, 50 | All 51 | } 52 | 53 | 54 | // single SID voice 55 | struct SIDVoice { 56 | wave: WaveForm, 57 | state: VoiceState, 58 | modulator: usize, // number of voice that modulates this voice 59 | modulatee: usize, // number of voice that this voice modulates 60 | wf_cnt: u32, // waveform counter 61 | wf_add: u32, // value to add to wf_cnt each frame 62 | freq: u16, 63 | pw_val: u16, // pulse-width value 64 | attack_add: u32, 65 | decay_sub: u32, 66 | release_sub: u32, 67 | sustain_level: u32, 68 | level: u32, 69 | noise: u32, 70 | gate: bool, 71 | ring: bool, 72 | test: bool, 73 | filter: bool, 74 | sync: bool, 75 | mute: bool // only voice 3 can be muted 76 | } 77 | 78 | impl SIDVoice { 79 | fn new() -> SIDVoice { 80 | SIDVoice { 81 | wave: WaveForm::None, 82 | state: VoiceState::Idle, 83 | modulator: 0, 84 | modulatee: 0, 85 | wf_cnt: 0, 86 | wf_add: 0, 87 | freq: 0, 88 | pw_val: 0, 89 | attack_add: EG_TABLE[0], 90 | decay_sub: EG_TABLE[0], 91 | release_sub: EG_TABLE[0], 92 | sustain_level: 0, 93 | level: 0, 94 | noise: 0, 95 | gate: false, 96 | ring: false, 97 | test: false, 98 | filter: false, 99 | sync: false, 100 | mute: false 101 | } 102 | } 103 | 104 | 105 | fn reset(&mut self) { 106 | self.wave = WaveForm::None; 107 | self.state = VoiceState::Idle; 108 | self.wf_cnt = 0; 109 | self.wf_add = 0; 110 | self.freq = 0; 111 | self.pw_val = 0; 112 | self.attack_add = EG_TABLE[0]; 113 | self.decay_sub = EG_TABLE[0]; 114 | self.release_sub = EG_TABLE[0]; 115 | self.sustain_level = 0; 116 | self.level = 0; 117 | self.noise = 0; 118 | self.gate = false; 119 | self.ring = false; 120 | self.test = false; 121 | self.filter = false; 122 | self.sync = false; 123 | self.mute = false; 124 | } 125 | } 126 | 127 | 128 | // the SID chip with associated SDL2 audio device 129 | pub struct SID { 130 | mem_ref: Option, 131 | #[cfg(not(target_os = "redox"))] 132 | audio_device: sdl2::audio::AudioDevice, 133 | } 134 | 135 | #[cfg(not(target_os = "redox"))] 136 | impl SID { 137 | pub fn new_shared() -> SIDShared { 138 | let sdl_context = sdl2::init().unwrap(); 139 | let audio_subsystem = sdl_context.audio().unwrap(); 140 | 141 | let desired_spec = AudioSpecDesired { 142 | freq: Some(44100), 143 | channels: Some(1), // mono 144 | samples: Some(512), // default sample size 145 | }; 146 | 147 | Rc::new(RefCell::new(SID { 148 | mem_ref: None, 149 | audio_device: audio_subsystem.open_playback(None, &desired_spec, |spec| { 150 | println!("{:?}", spec); 151 | SIDAudioDevice::new() 152 | }).unwrap() 153 | })) 154 | } 155 | 156 | 157 | pub fn set_references(&mut self, memref: memory::MemShared) { 158 | self.mem_ref = Some(memref); 159 | } 160 | 161 | 162 | pub fn reset(&mut self) { 163 | let mut lock = self.audio_device.lock(); 164 | (*lock).reset(); 165 | } 166 | 167 | 168 | pub fn update(&mut self) { 169 | let mut lock = self.audio_device.lock(); 170 | (*lock).update(); 171 | } 172 | 173 | 174 | pub fn read_register(&mut self, addr: u16) -> u8 { 175 | let mut rval = 0; 176 | 177 | match addr { 178 | 0xD419..=0xD41A => { 179 | let mut lock = self.audio_device.lock(); 180 | rval = (*lock).read_register(addr); 181 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, rval); 182 | }, 183 | 0xD41B..=0xD41C => { 184 | let mut lock = self.audio_device.lock(); 185 | rval = (*lock).read_register(addr); 186 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, rval); 187 | }, 188 | 0xD420..=0xD7FF => { rval = self.read_register(0xD400 + (addr % 0x0020)); }, 189 | _ => { 190 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, rval); 191 | } 192 | } 193 | 194 | rval 195 | } 196 | 197 | 198 | pub fn write_register(&mut self, addr: u16, value: u8) { 199 | let mut lock = self.audio_device.lock(); 200 | (*lock).write_register(addr, value); 201 | as_ref!(self.mem_ref).get_ram_bank(memory::MemType::Io).write(addr, value); 202 | } 203 | 204 | 205 | pub fn update_audio(&mut self) { 206 | self.audio_device.resume(); 207 | } 208 | } 209 | 210 | 211 | #[cfg(target_os = "redox")] 212 | impl SID { 213 | pub fn new_shared() -> SIDShared { 214 | Rc::new(RefCell::new(SID { 215 | mem_ref: None, 216 | })) 217 | } 218 | 219 | 220 | pub fn set_references(&mut self, memref: memory::MemShared) { 221 | self.mem_ref = Some(memref); 222 | } 223 | 224 | 225 | pub fn reset(&mut self) {} 226 | 227 | 228 | pub fn update(&mut self) {} 229 | 230 | 231 | pub fn read_register(&mut self, addr: u16) -> u8 { 232 | 0 233 | } 234 | 235 | 236 | pub fn write_register(&mut self, addr: u16, value: u8) {} 237 | 238 | 239 | pub fn update_audio(&mut self) {} 240 | } 241 | 242 | 243 | // SDL2 audio device along with necessary SID parameters 244 | // this is where the actual SID calculations are being performed 245 | struct SIDAudioDevice { 246 | last_sid_byte: u8, // last byte read by the SID 247 | volume: u8, 248 | filter_type: FilterType, 249 | filter_freq: u8, 250 | filter_resonance: u8, 251 | 252 | // IIR filter 253 | iir_att: f32, 254 | d1: f32, 255 | d2: f32, 256 | g1: f32, 257 | g2: f32, 258 | xn1: f32, 259 | xn2: f32, 260 | yn1: f32, 261 | yn2: f32, 262 | 263 | voices: Vec, 264 | sample_buffer: [u8; NUM_SAMPLES], 265 | sample_idx: usize 266 | } 267 | 268 | impl SIDAudioDevice { 269 | pub fn new() -> SIDAudioDevice { 270 | let mut sid_audio_device = SIDAudioDevice { 271 | last_sid_byte: 0, 272 | voices: vec![SIDVoice::new(), SIDVoice::new(), SIDVoice::new()], 273 | volume: 0, 274 | filter_type: FilterType::None, 275 | filter_freq: 0, 276 | filter_resonance: 0, 277 | iir_att: 1.0, 278 | d1: 0.0, 279 | d2: 0.0, 280 | g1: 0.0, 281 | g2: 0.0, 282 | xn1: 0.0, 283 | xn2: 0.0, 284 | yn1: 0.0, 285 | yn2: 0.0, 286 | sample_buffer: [0; NUM_SAMPLES], 287 | sample_idx: 0 288 | }; 289 | 290 | // calculate triangle table values 291 | unsafe { 292 | for i in 0..0x1000 { 293 | let val = ((i << 4) | (i >> 8)) as u16; 294 | TRI_TABLE[i] = val; 295 | TRI_TABLE[0x1FFF - i] = val; 296 | } 297 | } 298 | 299 | sid_audio_device.voices[0].modulator = 2; 300 | sid_audio_device.voices[0].modulatee = 1; 301 | sid_audio_device.voices[1].modulator = 0; 302 | sid_audio_device.voices[1].modulatee = 2; 303 | sid_audio_device.voices[2].modulator = 1; 304 | sid_audio_device.voices[2].modulatee = 0; 305 | 306 | sid_audio_device 307 | } 308 | 309 | 310 | pub fn reset(&mut self) { 311 | self.last_sid_byte = 0; 312 | 313 | for i in 0..self.voices.len() { 314 | self.voices[i].reset(); 315 | } 316 | 317 | self.volume = 0; 318 | self.filter_type = FilterType::None; 319 | self.filter_freq = 0; 320 | self.filter_resonance = 0; 321 | self.iir_att = 1.0; 322 | self.d1 = 0.0; 323 | self.d2 = 0.0; 324 | self.g1 = 0.0; 325 | self.g2 = 0.0; 326 | self.xn1 = 0.0; 327 | self.xn2 = 0.0; 328 | self.yn1 = 0.0; 329 | self.yn2 = 0.0; 330 | self.sample_idx = 0; 331 | self.calculate_filter(); 332 | 333 | for i in 0..NUM_SAMPLES { 334 | self.sample_buffer[i] = 0; 335 | } 336 | } 337 | 338 | 339 | pub fn update(&mut self) { 340 | let idx = self.sample_idx; 341 | self.sample_buffer[idx] = self.volume; 342 | self.sample_idx = (self.sample_idx + 1) % NUM_SAMPLES; 343 | } 344 | 345 | 346 | pub fn read_register(&mut self, addr: u16) -> u8 { 347 | // most SID registers are write-only. The write to IO RAM is performed 348 | // so that the debugger can print out the value fetched by the CPU 349 | match addr { 350 | 0xD419..=0xD41A => { 351 | self.last_sid_byte = 0; 352 | let rval = 0xFF; 353 | rval 354 | }, 355 | 0xD41B..=0xD41C => { 356 | self.last_sid_byte = 0; 357 | let rval = rand::random::(); 358 | rval 359 | }, 360 | 0xD420..=0xD7FF => self.read_register(0xD400 + (addr % 0x0020)), 361 | _ => { 362 | let rval = self.last_sid_byte; 363 | rval 364 | } 365 | } 366 | } 367 | 368 | 369 | pub fn write_register(&mut self, addr: u16, value: u8) { 370 | self.last_sid_byte = value; 371 | 372 | match addr { 373 | 0xD400 => { 374 | self.voices[0].freq = (self.voices[0].freq & 0xFF00) | value as u16; 375 | self.voices[0].wf_add = SID_CYCLES * self.voices[0].freq as u32; 376 | }, 377 | 0xD401 => { 378 | self.voices[0].freq = (self.voices[0].freq & 0x00FF) | ((value as u16) << 8); 379 | self.voices[0].wf_add = SID_CYCLES * self.voices[0].freq as u32; 380 | }, 381 | 0xD402 => { 382 | self.voices[0].pw_val = (self.voices[0].pw_val & 0x0F00) | value as u16; 383 | }, 384 | 0xD403 => { 385 | self.voices[0].pw_val = (self.voices[0].pw_val & 0x00FF) | (((value as u16) & 0x000F) << 8); 386 | }, 387 | 0xD404 => { 388 | self.set_control_register(0, value); 389 | }, 390 | 0xD405 => { 391 | self.voices[0].attack_add = EG_TABLE[ (value >> 4) as usize ]; 392 | self.voices[0].decay_sub = EG_TABLE[ (value & 0x0F) as usize ]; 393 | }, 394 | 0xD406 => { 395 | self.voices[0].sustain_level = 0x111111 * (value >> 4) as u32; 396 | self.voices[0].release_sub = EG_TABLE[ (value & 0x0F) as usize ]; 397 | }, 398 | 0xD407 => { 399 | self.voices[1].freq = (self.voices[1].freq & 0xFF00) | value as u16; 400 | self.voices[1].wf_add = SID_CYCLES * self.voices[1].freq as u32; 401 | }, 402 | 0xD408 => { 403 | self.voices[1].freq = (self.voices[1].freq & 0x00FF) | ((value as u16) << 8); 404 | self.voices[1].wf_add = SID_CYCLES * self.voices[1].freq as u32; 405 | }, 406 | 0xD409 => { 407 | self.voices[1].pw_val = (self.voices[1].pw_val & 0x0F00) | value as u16; 408 | }, 409 | 0xD40A => { 410 | self.voices[1].pw_val = (self.voices[1].pw_val & 0x00FF) | (((value as u16) & 0x000F) << 8); 411 | }, 412 | 0xD40B => { 413 | self.set_control_register(1, value); 414 | }, 415 | 0xD40C => { 416 | self.voices[1].attack_add = EG_TABLE[ (value >> 4) as usize ]; 417 | self.voices[1].decay_sub = EG_TABLE[ (value & 0x0F) as usize ]; 418 | }, 419 | 0xD40D => { 420 | self.voices[1].sustain_level = 0x111111 * (value >> 4) as u32; 421 | self.voices[1].release_sub = EG_TABLE[ (value & 0x0F) as usize ]; 422 | }, 423 | 0xD40E => { 424 | self.voices[2].freq = (self.voices[2].freq & 0xFF00) | value as u16; 425 | self.voices[2].wf_add = SID_CYCLES * self.voices[2].freq as u32; 426 | }, 427 | 0xD40F => { 428 | self.voices[2].freq = (self.voices[2].freq & 0x00FF) | ((value as u16) << 8); 429 | self.voices[2].wf_add = SID_CYCLES * self.voices[2].freq as u32; 430 | }, 431 | 0xD410 => { 432 | self.voices[2].pw_val = (self.voices[2].pw_val & 0x0F00) | value as u16; 433 | }, 434 | 0xD411 => { 435 | self.voices[2].pw_val = (self.voices[2].pw_val & 0x00FF) | (((value as u16) & 0x000F) << 8); 436 | }, 437 | 0xD412 => { 438 | self.set_control_register(2, value); 439 | }, 440 | 0xD413 => { 441 | self.voices[2].attack_add = EG_TABLE[ (value >> 4) as usize ]; 442 | self.voices[2].decay_sub = EG_TABLE[ (value & 0x0F) as usize ]; 443 | }, 444 | 0xD414 => { 445 | self.voices[2].sustain_level = 0x111111 * (value >> 4) as u32; 446 | self.voices[2].release_sub = EG_TABLE[ (value & 0x0F) as usize ]; 447 | }, 448 | 0xD416 => { 449 | if self.filter_freq != value { 450 | self.filter_freq = value; 451 | self.calculate_filter(); 452 | } 453 | }, 454 | 0xD417 => { 455 | self.voices[0].filter = (value & 1) != 0; 456 | self.voices[1].filter = (value & 2) != 0; 457 | self.voices[2].filter = (value & 4) != 0; 458 | 459 | if self.filter_resonance != (value >> 4) { 460 | self.filter_resonance = value >> 4; 461 | self.calculate_filter(); 462 | } 463 | }, 464 | 0xD418 => { 465 | self.volume = value & 0x0F; 466 | self.voices[2].mute = (value & 0x80) != 0; 467 | let f_type = match (value >> 4) & 7 { 468 | 0 => FilterType::None, 469 | 1 => FilterType::Lowpass, 470 | 2 => FilterType::Bandpass, 471 | 3 => FilterType::LowBandpass, 472 | 4 => FilterType::Highpass, 473 | 5 => FilterType::Notch, 474 | 6 => FilterType::HighBandpass, 475 | 7 => FilterType::All, 476 | _ => panic!("Impossible filter combination!"), 477 | }; 478 | 479 | if self.filter_type != f_type { 480 | self.filter_type = f_type; 481 | self.xn1 = 0.0; 482 | self.xn2 = 0.0; 483 | self.yn1 = 0.0; 484 | self.yn2 = 0.0; 485 | self.calculate_filter(); 486 | } 487 | }, 488 | // $D41D-$D41F are unusable, so just ignore it 489 | 0xD420..=0xD7FF => self.write_register(0xD400 + (addr % 0x0020), value), 490 | _ => (), 491 | } 492 | } 493 | 494 | // *** private functions *** // 495 | 496 | fn lowpass_resonance(&self, f: f32) -> f32 { 497 | let f2 = f * f; 498 | let f3 = f2 * f; 499 | let f4 = f3 * f; 500 | 227.755 - 1.7635 * f - 0.0176385 * f2 + 0.00333484 * f3 - 9.05683E-6 * f4 501 | } 502 | 503 | 504 | fn highpass_resonance(&self, f: f32) -> f32 { 505 | let f2 = f * f; 506 | let f3 = f2 * f; 507 | 366.374 - 14.0052 * f + 0.603212 * f2 - 0.000880196 * f3 508 | } 509 | 510 | 511 | fn calculate_filter(&mut self) { 512 | let f = self.filter_freq as f32; 513 | let resonance: f32; 514 | let mut arg: f32; 515 | 516 | match self.filter_type { 517 | FilterType::None => { 518 | self.d1 = 0.0; 519 | self.d2 = 0.0; 520 | self.g1 = 0.0; 521 | self.g2 = 0.0; 522 | self.iir_att = 0.0; 523 | return; 524 | }, 525 | FilterType::All => { 526 | self.d1 = 0.0; 527 | self.d2 = 0.0; 528 | self.g1 = 0.0; 529 | self.g2 = 0.0; 530 | self.iir_att = 1.0; 531 | return; 532 | } 533 | FilterType::Lowpass | FilterType::LowBandpass => { 534 | resonance = self.lowpass_resonance(f); 535 | }, 536 | _ => { 537 | resonance = self.highpass_resonance(f); 538 | } 539 | } 540 | 541 | arg = resonance / ((SAMPLE_FREQ >> 1) as f32); 542 | if arg > 0.99 { arg = 0.99; } 543 | if arg < 0.01 { arg = 0.01; } 544 | 545 | self.g2 = 0.55 + 1.2 * arg * arg - 1.2 * arg + 0.0133333333 * self.filter_resonance as f32; 546 | self.g1 = -2.0 * self.g2.sqrt() * (f32::consts::PI * arg).cos(); 547 | 548 | match self.filter_type { 549 | FilterType::LowBandpass | FilterType::HighBandpass => self.g2 += 0.1, 550 | _ => () 551 | } 552 | 553 | if self.g1.abs() >= (self.g2 + 1.0) { 554 | if self.g1 > 0.0 { self.g1 = self.g2 + 0.99; } 555 | else { self.g1 = -(self.g2 + 0.99); } 556 | } 557 | 558 | match self.filter_type { 559 | FilterType::LowBandpass | FilterType::Lowpass => { 560 | self.d1 = 2.0; 561 | self.d2 = 1.0; 562 | self.iir_att = 0.25 * (1.0 + self.g1 + self.g2); 563 | }, 564 | FilterType::HighBandpass | FilterType::Highpass => { 565 | self.d1 = -2.0; 566 | self.d2 = 1.0; 567 | self.iir_att = 0.25 * (1.0 - self.g1 + self.g2); 568 | }, 569 | FilterType::Bandpass => { 570 | self.d1 = 0.0; 571 | self.d2 = -1.0; 572 | self.iir_att = 0.25 * (1.0 + self.g1 + self.g2) * (1.0 + (f32::consts::PI * arg).cos()) / (f32::consts::PI * arg).sin(); 573 | }, 574 | FilterType::Notch => { 575 | self.d1 = -2.0 * (f32::consts::PI * arg).cos(); 576 | self.d2 = 1.0; 577 | self.iir_att = 0.25 * (1.0 + self.g1 + self.g2) * (1.0 + (f32::consts::PI * arg).cos()) / (f32::consts::PI * arg).sin(); 578 | }, 579 | _ => () 580 | } 581 | } 582 | 583 | 584 | fn set_control_register(&mut self, v_num: usize, value: u8) { 585 | self.voices[v_num].wave = match (value >> 4) & 0x0F { 586 | 0 => WaveForm::None, 587 | 1 => WaveForm::Triangle, 588 | 2 => WaveForm::Saw, 589 | 3 => WaveForm::TriSaw, 590 | 4 => WaveForm::Pulse, 591 | 5 => WaveForm::TriPulse, 592 | 6 => WaveForm::SawPulse, 593 | 7 => WaveForm::TriSawPulse, 594 | 8 => WaveForm::Noise, 595 | _ => panic!("Impossible waveform value!"), 596 | }; 597 | 598 | let gate_on = (value & 1) != 0; 599 | let sync_on = (value & 2) != 0; 600 | let ring_on = (value & 4) != 0; 601 | let test_on = (value & 8) != 0; 602 | 603 | if gate_on != self.voices[v_num].gate { 604 | if gate_on { 605 | self.voices[v_num].state = VoiceState::Attack; 606 | } 607 | else { 608 | match self.voices[v_num].state { 609 | VoiceState::Idle => (), 610 | _ => self.voices[v_num].state = VoiceState::Release, 611 | } 612 | } 613 | 614 | let modulator = self.voices[v_num].modulator; 615 | self.voices[v_num].gate = gate_on; 616 | self.voices[modulator].sync = sync_on; 617 | self.voices[v_num].ring = ring_on; 618 | self.voices[v_num].test = test_on; 619 | 620 | if test_on { 621 | self.voices[v_num].wf_cnt = 0; 622 | } 623 | } 624 | } 625 | } 626 | 627 | // SDL2 audio callback implementation - this is where the samples are being converted to output sound 628 | #[cfg(not(target_os = "redox"))] 629 | impl AudioCallback for SIDAudioDevice { 630 | type Channel = i16; 631 | 632 | fn callback(&mut self, out: &mut [i16]) { 633 | let iir_att = self.iir_att; 634 | let d1 = self.d1; 635 | let d2 = self.d2; 636 | let g1 = self.g1; 637 | let g2 = self.g2; 638 | 639 | let mut sample_count = (self.sample_idx + NUM_SAMPLES/2) << 16; 640 | 641 | for x in out.iter_mut() { 642 | let master_volume: u8 = self.sample_buffer[(sample_count >> 16) % NUM_SAMPLES]; 643 | 644 | sample_count += ((50 * NUM_SAMPLES/2) << 16) / SAMPLE_FREQ as usize; 645 | let mut total_output: i32 = (SAMPLE_TABLE[master_volume as usize] as i32) << 8; 646 | let mut total_output_filter: i32 = 0; 647 | 648 | for i in 0..3 { 649 | let envelope: f32; 650 | 651 | match self.voices[i].state 652 | { 653 | VoiceState::Attack => { 654 | self.voices[i].level = self.voices[i].level.wrapping_add(self.voices[i].attack_add); 655 | if self.voices[i].level > 0xFFFFFF { 656 | self.voices[i].level = 0xFFFFFF; 657 | self.voices[i].state = VoiceState::Decay; 658 | } 659 | }, 660 | VoiceState::Decay => { 661 | if (self.voices[i].level <= self.voices[i].sustain_level) || (self.voices[i].level > 0xFFFFFF) { 662 | self.voices[i].level = self.voices[i].sustain_level; 663 | } 664 | else { 665 | self.voices[i].level = self.voices[i].level.wrapping_sub(self.voices[i].decay_sub >> EGDR_SHIFT[ (self.voices[i].level >> 16) as usize ]); 666 | if (self.voices[i].level <= self.voices[i].sustain_level) || (self.voices[i].level > 0xFFFFFF) { 667 | self.voices[i].level = self.voices[i].sustain_level; 668 | } 669 | } 670 | }, 671 | VoiceState::Release => { 672 | self.voices[i].level = self.voices[i].level.wrapping_sub(self.voices[i].release_sub >> EGDR_SHIFT[ (self.voices[i].level >> 16) as usize ]); 673 | if self.voices[i].level > 0xFFFFFF { 674 | self.voices[i].level = 0; 675 | self.voices[i].state = VoiceState::Idle; 676 | } 677 | }, 678 | VoiceState::Idle => { 679 | self.voices[i].level = 0; 680 | }, 681 | } 682 | 683 | envelope = ((self.voices[i].level as f32) * master_volume as f32) / (0xFFFFFF * 0xF) as f32; 684 | let modulatee = self.voices[i].modulatee; 685 | let modulator = self.voices[i].modulator; 686 | 687 | if self.voices[i].mute { 688 | continue; 689 | } 690 | 691 | if !self.voices[i].test { 692 | self.voices[i].wf_cnt += self.voices[i].wf_add; 693 | } 694 | 695 | if self.voices[i].sync && (self.voices[i].wf_cnt > 0x1000000) { 696 | self.voices[modulatee].wf_cnt = 0; 697 | } 698 | 699 | self.voices[i].wf_cnt &= 0xFFFFFF; 700 | 701 | let mut output: u16 = 0; 702 | match self.voices[i].wave { 703 | WaveForm::Triangle => { 704 | unsafe { 705 | if self.voices[i].ring { 706 | output = TRI_TABLE[((self.voices[i].wf_cnt ^ (self.voices[modulator].wf_cnt & 0x800000)) >> 11) as usize]; 707 | } 708 | else { 709 | output = TRI_TABLE[ (self.voices[i].wf_cnt >> 11) as usize ]; 710 | } 711 | } 712 | }, 713 | WaveForm::Saw => { 714 | output = (self.voices[i].wf_cnt >> 8) as u16; 715 | }, 716 | WaveForm::Pulse => { 717 | if self.voices[i].wf_cnt > (self.voices[i].pw_val << 12) as u32 { 718 | output = 0xFFFF; 719 | } 720 | }, 721 | WaveForm::TriSaw => { 722 | output = TRI_SAW_TABLE[ (self.voices[i].wf_cnt >> 16) as usize ]; 723 | }, 724 | WaveForm::TriPulse => { 725 | if self.voices[i].wf_cnt > (self.voices[i].pw_val << 12) as u32 { 726 | output = TRI_RECT_TABLE[ (self.voices[i].wf_cnt >> 16) as usize ]; 727 | } 728 | }, 729 | WaveForm::SawPulse => { 730 | if self.voices[i].wf_cnt > (self.voices[i].pw_val << 12) as u32 { 731 | output = SAW_RECT_TABLE[ (self.voices[i].wf_cnt >> 16) as usize ]; 732 | } 733 | }, 734 | WaveForm::TriSawPulse => { 735 | if self.voices[i].wf_cnt > (self.voices[i].pw_val << 12) as u32 { 736 | output = TRI_SAW_RECT_TABLE[ (self.voices[i].wf_cnt >> 16) as usize ]; 737 | } 738 | }, 739 | WaveForm::Noise => { 740 | if self.voices[i].wf_cnt > 0x100000 { 741 | let rnd_noise = rand::random::() << 8; 742 | self.voices[i].noise = rnd_noise as u32; 743 | output = rnd_noise; 744 | self.voices[i].wf_cnt &= 0xFFFFF; 745 | } 746 | else { 747 | output = self.voices[i].noise as u16; 748 | } 749 | }, 750 | WaveForm::None => { 751 | output = 0x8000; 752 | } 753 | } 754 | 755 | if self.voices[i].filter { 756 | total_output_filter += (envelope * ((output >> 3) ^ 0x8000) as f32) as i32; 757 | } 758 | else { 759 | total_output += (envelope * ((output >> 3) ^ 0x8000) as f32) as i32; 760 | } 761 | } 762 | 763 | // take filters into account 764 | let xn = (total_output_filter * iir_att as i32) as f32; 765 | let yn = xn + d1 * self.xn1 + d2 * self.xn2 - g1 * self.yn1 - g2 * self.yn2; 766 | self.yn2 = self.yn1; 767 | self.yn1 = yn; 768 | self.xn2 = self.xn1; 769 | self.xn1 = xn; 770 | total_output_filter = yn as i32; 771 | 772 | let sample_value = (((total_output + total_output_filter)) >> 2) as i16; 773 | 774 | // output the sample! 775 | *x = sample_value; 776 | } 777 | } 778 | } 779 | --------------------------------------------------------------------------------