Adds the simple test virtual machine and a few hard-coded test programs
parent
48bfaf500a
commit
6efdfecf54
@ -0,0 +1,3 @@
|
||||
{
|
||||
"rust-analyzer.showUnlinkedFileNotification": false
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "simple_test_machine"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
re6502 = { path = "../" }
|
||||
@ -0,0 +1,156 @@
|
||||
|
||||
|
||||
use std::str;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// BUS
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
use re6502::r6502::{R6502, Bus, Flags};
|
||||
struct TBus
|
||||
{
|
||||
memory: [u8; 64 * 1024]
|
||||
}
|
||||
|
||||
impl TBus
|
||||
{
|
||||
pub fn new() -> TBus
|
||||
{
|
||||
TBus { memory: [0; 64 * 1024] }
|
||||
}
|
||||
|
||||
pub fn clear_memory(&mut self)
|
||||
{
|
||||
for i in 0..self.memory.len()
|
||||
{
|
||||
self.memory[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_into_memory(&mut self, data: &[u8], at: u16)
|
||||
{
|
||||
for i in 0..data.len()
|
||||
{
|
||||
self.write(at + i as u16, data[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bus for TBus
|
||||
{
|
||||
fn read(&self, addr: u16) -> u8
|
||||
{
|
||||
self.memory[addr as usize]
|
||||
}
|
||||
|
||||
fn write(&mut self, addr: u16, value: u8)
|
||||
{
|
||||
self.memory[addr as usize] = value;
|
||||
}
|
||||
}
|
||||
|
||||
//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||
// CONSOLE
|
||||
//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||
|
||||
pub const OUTPUT_ADDR: u16 = 0x00A0;
|
||||
pub const PRINT_BYTE_FLAG: u16 = 0x009E;
|
||||
pub const PRINT_STR_FLAG: u16 = 0x009F;
|
||||
|
||||
// TODO: Handle input
|
||||
struct OutputConsole
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
impl OutputConsole
|
||||
{
|
||||
// fn new() -> Console
|
||||
// {
|
||||
// Console { }
|
||||
// }
|
||||
|
||||
fn clock(_cpu: &mut R6502, bus: &mut TBus )
|
||||
{
|
||||
// Check for a string to print
|
||||
let mut value = bus.read(PRINT_STR_FLAG);
|
||||
if value != 0
|
||||
{
|
||||
let mut msg: Vec<u8> = Vec::new();
|
||||
|
||||
let mut idx = 0;
|
||||
while value != 0
|
||||
{
|
||||
value = bus.read(OUTPUT_ADDR + idx);
|
||||
msg.push(value);
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
// Mark the string as empty again
|
||||
bus.write(PRINT_STR_FLAG, 0);
|
||||
|
||||
println!("{}", str::from_utf8(&msg).unwrap());
|
||||
}
|
||||
|
||||
// Check for byte to print
|
||||
let flag = bus.read(PRINT_BYTE_FLAG);
|
||||
if flag != 0
|
||||
{
|
||||
let byte = bus.read(OUTPUT_ADDR);
|
||||
bus.write(PRINT_BYTE_FLAG, 0);
|
||||
println!("{}", byte as u8);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// MACHINE
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
const PROGRAM_START_ADDR: u16 = 0x0200; // Program code starts on page 2
|
||||
const CPU_RESET_START_ADDR: u16 = 0xFFFC; // This is where the cpu looks for the address to start executing code at
|
||||
|
||||
pub struct TestMachine
|
||||
{
|
||||
bus: TBus,
|
||||
cpu: R6502,
|
||||
|
||||
}
|
||||
|
||||
impl TestMachine
|
||||
{
|
||||
pub fn new() -> TestMachine
|
||||
{
|
||||
TestMachine { bus: TBus::new(), cpu: R6502::new() }
|
||||
}
|
||||
|
||||
pub fn reset(&mut self)
|
||||
{
|
||||
self.cpu.reset(&mut self.bus);
|
||||
}
|
||||
|
||||
pub fn load_program(&mut self, program: &[u8])
|
||||
{
|
||||
self.bus.clear_memory();
|
||||
|
||||
// Load program starting at page 2
|
||||
self.bus.load_into_memory(program, PROGRAM_START_ADDR);
|
||||
|
||||
// Set the cpu program start address
|
||||
self.bus.write(CPU_RESET_START_ADDR, (PROGRAM_START_ADDR & 0x00FF) as u8);
|
||||
self.bus.write(CPU_RESET_START_ADDR + 1, ((PROGRAM_START_ADDR & 0xFF00) >> 8) as u8);
|
||||
|
||||
self.reset();
|
||||
}
|
||||
|
||||
pub fn run_program(&mut self)
|
||||
{
|
||||
// Program should run until the cpu detects that it has stopped
|
||||
// or when the program hits a BRK instruction
|
||||
while !self.cpu.is_program_stopped() && self.cpu.check_flag(Flags::B) == 0
|
||||
{
|
||||
self.cpu.clock(&mut self.bus);
|
||||
OutputConsole::clock(&mut self.cpu, &mut self.bus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
use machine::{OUTPUT_ADDR, PRINT_STR_FLAG, PRINT_BYTE_FLAG, TestMachine};
|
||||
|
||||
|
||||
mod machine;
|
||||
|
||||
fn main()
|
||||
{
|
||||
hello_world_test();
|
||||
println!();
|
||||
fast_mult_by_10();
|
||||
}
|
||||
|
||||
fn hello_world_test()
|
||||
{
|
||||
let print_flag_addr = (PRINT_STR_FLAG & 0x00FF) as u8;
|
||||
let output_addr = (OUTPUT_ADDR & 0x00FF) as u8;
|
||||
let program =
|
||||
[
|
||||
// Load string into memory at the output address
|
||||
0xA2, b'H', // LDX H
|
||||
0x86, output_addr, // STX
|
||||
0xA2, b'e', // LDX e
|
||||
0x86, output_addr + 1, // STX
|
||||
0xA2, b'l', // LDX l
|
||||
0x86, output_addr + 2, // STX
|
||||
0xA2, b'l', // LDX l
|
||||
0x86, output_addr + 3, // STX
|
||||
0xA2, b'o', // LDX o
|
||||
0x86, output_addr + 4, // STX
|
||||
|
||||
0xA2, b' ', // LDX ' '
|
||||
0x86, output_addr + 5, // STX
|
||||
|
||||
0xA2, b'w', // LDX w
|
||||
0x86, output_addr + 6, // STX
|
||||
0xA2, b'o', // LDX o
|
||||
0x86, output_addr + 7, // STX
|
||||
0xA2, b'r', // LDX r
|
||||
0x86, output_addr + 8, // STX
|
||||
0xA2, b'l', // LDX l
|
||||
0x86, output_addr + 9, // STX
|
||||
0xA2, b'd', // LDX d
|
||||
0x86, output_addr + 10, // STX
|
||||
0xA2, b'!', // LDX !
|
||||
0x86, output_addr + 11, // STX
|
||||
|
||||
0xA2, 0x00, // LDX 0
|
||||
0x86, output_addr + 12, // STX
|
||||
|
||||
// Set flag to do the print
|
||||
0xA2, 0x01, // LDX 1
|
||||
0x86, print_flag_addr, // STX
|
||||
|
||||
// End the program
|
||||
0x60 // RTS
|
||||
];
|
||||
|
||||
let mut vm = TestMachine::new();
|
||||
|
||||
vm.load_program(&program);
|
||||
vm.reset();
|
||||
vm.run_program();
|
||||
|
||||
println!("Program stopped");
|
||||
|
||||
|
||||
}
|
||||
|
||||
fn fast_mult_by_10()
|
||||
{
|
||||
// Program from:
|
||||
// http://6502.org/source/integers/fastx10.htm
|
||||
|
||||
// MULT10
|
||||
// ASL ;multiply by 2
|
||||
// STA TEMP ;temp store in TEMP
|
||||
// ASL ;again multiply by 2 (*4)
|
||||
// ASL ;again multiply by 2 (*8)
|
||||
// CLC
|
||||
// ADC TEMP ;as result, A = x*8 + x*2
|
||||
// RTS
|
||||
//
|
||||
// TEMP .byte 0
|
||||
|
||||
let print_flag_addr = (PRINT_BYTE_FLAG & 0x00FF) as u8;
|
||||
let output_addr = (OUTPUT_ADDR & 0x00FF) as u8;
|
||||
let temp_addr: u8 = 0xB0;
|
||||
|
||||
let program =
|
||||
[
|
||||
0xA9, 0x07, // LDA 7 - The value we want to multiply
|
||||
|
||||
// START OF MULT10 FUNCTION
|
||||
0x0A, // ASL ;multiply by 2
|
||||
0x85, temp_addr, // STA TEMP ;temp store in TEMP
|
||||
0x0A, // ASL ;again multiply by 2 (*4)
|
||||
0x0A, // ASL ;again multiply by 2 (*8)
|
||||
0x18, // CLC
|
||||
0x65, temp_addr, // ADC TEMP ;as result, A = x*8 + x*2
|
||||
|
||||
// PRINT RESULT
|
||||
0x85, output_addr, // STA output addr
|
||||
0xA2, 0x00, // LDX 0 - null terminator
|
||||
0x86, output_addr + 1, // STX
|
||||
|
||||
// Set flag to do the print
|
||||
0xA2, 0x01, // LDX 1
|
||||
0x86, print_flag_addr, // STX
|
||||
|
||||
// End the program
|
||||
0x60 // RTS
|
||||
|
||||
];
|
||||
|
||||
let mut vm = TestMachine::new();
|
||||
|
||||
vm.load_program(&program);
|
||||
vm.reset();
|
||||
vm.run_program();
|
||||
|
||||
println!("Program stopped");
|
||||
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
pub mod r6502;
|
||||
Loading…
Reference in New Issue