Lots of stuff.

This commit is contained in:
2025-06-23 15:35:36 -04:00
parent 87ae4e7890
commit 2939e1cac5
120 changed files with 9335 additions and 2178 deletions
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "beneater"
version = "0.1.0"
edition = "2024"
[dependencies]
core = { path = "../core" }
macroquad.workspace = true
View File
+64
View File
@@ -0,0 +1,64 @@
// This is the GUI for the BenEater PC
use beneater::parts::backplane::Backplane;
use beneater::parts::ben_eater_pc::BenEaterPC;
use beneater::parts::cpu_display::CpuDisplay;
use macroquad::prelude::*;
use macroquad::telemetry::frame;
use core::mos6502cpu::cpu::Mos6502Cpu;
use core::periph::at28c256::At28C256;
use core::periph::hm62256::Hm62256;
use core::constants::constants_system::*;
use std::fs;
/// BenEater computer represents the 'Ben Eater' 6502 breadboard computer.
/// This was built along watching the video series where Ben builds a
/// 6502 on a breadboard and explains each step.
///
pub struct BenEater {
cpu: Mos6502Cpu,
ram: At28C256,
rom: Hm62256
}
impl BenEater {
// pub fn new(rom: &[u8; SIZE_32KB]) -> BenEater {
//
// }
}
#[macroquad::main("Ben Eaters PC")]
async fn main() {
println!("Taxation is Theft");
// let rom_to_run = fs::read("resources/beneater/roms/ror.bin");
// let mut pc = BenEater::new(&rom_to_run);
//
// let mut backplane = Backplane::new();
// backplane.load_rom("resources/beneater/roms/ror.bin");
// let mut dm = DisplayMatrix::new(200.0, 50.0);
// dm.push_letter('T');
let mut frame_number: u32 = 0x00;
loop {
clear_background(BLUE);
draw_text("Ben Eater", 20.0, 20.0, 30.0, BLACK);
// dm.render(20.0, 40.0);
// CpuDisplay::render(&computer.cpu, 20.0, 120.0);
frame_number += 1;
if frame_number.is_multiple_of(60) {
// dm.push_letter('X');
// computer.tick_system();
}
if frame_number.is_multiple_of(60 * 6) {
// dm.clear_display()
}
next_frame().await
}
}
+56
View File
@@ -0,0 +1,56 @@
#![feature(slice_as_array)]
use core::constants::constants_system::SIZE_32KB;
use core::constants::constants_isa_op::*;
use std::fs;
use std::path::Path;
fn le_swap(to_swap: &[u8; SIZE_32KB]) -> [u8; 32768] {
let mut work: [u8; SIZE_32KB] = [0x00; SIZE_32KB];
for i in (0..SIZE_32KB).step_by(2) {
work[i] = to_swap[i+1];
work[i + 1] = to_swap[i];
}
work
}
fn main() {
// make the rom data in memory.
// Fill with 0xea -> NOP
let mut vec: [u8; SIZE_32KB] = [ISA_OP_NOP; SIZE_32KB];
// Load to A
vec[0] = ISA_OP_LDA_I; // LDA #$ab
vec[1] = 0b1010_1011; // 1010 1011
// Jump to rotate cycle
vec[2] = 0x02; // --
vec[3] = 0x03; // --
// Jump to Main
vec[0x2210] = ISA_OP_JMP_ABS;
vec[0x2211] = 0x00;
vec[0x2212] = 0x40;
// load to a
vec[0x4000] = ISA_OP_LDA_I;
vec[0x4001] = 0b0101_0100;
// jump to top of load to a
vec[0x4002] = ISA_OP_JMP_ABS;
vec[0x4003] = 0x00;
vec[0x4004] = 0x40;
vec[0x4005] = ISA_OP_NOP;
vec[0x7ffa] = 0x22; // NMI Vector
vec[0x7ffb] = 0x11;
vec[0x7ffc] = 0x12; // Reset Vector
vec[0x7ffd] = 0x34;
vec[0x7ffe] = 0x43; // Interrupt Vector
vec[0x7fff] = 0x21;
vec = le_swap(&vec);
// write the rom to disk
fs::write("outputfile.bin", &vec[..]).expect("TODO: panic message");
}
+33
View File
@@ -0,0 +1,33 @@
use beneater::parts::backplane::Backplane;
use core::constants::constants_isa_op::*;
use core::constants::constants_system::*;
use std::fs;
use std::ops::Index;
fn main() {
println!("Taxation is Theft");
let mut backplane = Backplane::new();
for i in 0..12 {
backplane.tick();
}
//backplane.load_rom();
println!("Backplane is live.");
let mut new_program = [0x00u8; SIZE_32KB];
new_program[(OFFSET_RESET_VECTOR - SIZE_32KB as u16) as usize] = 0x00;
new_program[(OFFSET_RESET_VECTOR + 1 - SIZE_32KB as u16) as usize] = 0x60;
println!("Set offset in rom...");
println!(
"VALUE AT OFFSET_RESET_VECTOR = 0x{:04x} ",
new_program[(OFFSET_RESET_VECTOR - SIZE_32KB as u16) as usize]
);
// println!("{:?}", new_program);
// backplane.rom.program(&new_program);
// backplane.cpu.pc = 0x6000;
// backplane.memory[0x6000] = ISA_OP_LDA_I;
// backplane.memory[0x6001] = 0xab;
// backplane.tick();
}
+2
View File
@@ -0,0 +1,2 @@
pub mod parts;
pub mod backplane;
+9
View File
@@ -0,0 +1,9 @@
pub struct AddressBus {
pub address: u16,
}
impl AddressBus {
pub fn new() -> Self {
AddressBus { address: 0x0000 }
}
}
+127
View File
@@ -0,0 +1,127 @@
use crate::parts::mos6522_peripheral::Mos6522Peripheral;
use crate::parts::via6522::VIA6522;
use core::constants::constants_system::*;
use core::mos6502cpu::cpu::Mos6502Cpu;
use core::periph::at28c256::At28C256;
use core::periph::rom_chip::RomChip;
use core::constants::constants_via6522::*;
/// Backplane
///
/// A Backplane hold and coordinates the cpu with the
/// rest of the peripherals.
///
/// This system has
/// -> VIA6522 for peripherals
/// -> 322KB RAM
/// -> 32KB ROM
pub struct Backplane {
// pub for dev
pub cpu: Mos6502Cpu,
pub via: VIA6522,
pub memory: Box<[u8]>,
pub rom: At28C256,
data_bus: u8,
address_bus: u16
}
impl Backplane {
pub fn new() -> Self {
Backplane {
cpu: Mos6502Cpu::default(),
via: VIA6522::default(),
memory: Box::new([0x00; SIZE_32KB]),
rom: At28C256::default(),
data_bus: 0x00,
address_bus: 0x0000
}
}
pub fn load_rom(&mut self, to_load: &[u8; SIZE_32KB]) {
self.rom.program((*to_load).into());
}
pub fn tick(&mut self) {
// is the CPU in read or write state
self.address_bus = self.cpu.address_bus();
self.data_bus = self.cpu.data_bus();
let rw = self.cpu.read_signal;
println!("- TICK START:");
println!("| CPU: Address: 0b{:016b} Data: 0b{:08b} CPU RW: {rw}", self.address_bus, self.data_bus);
// via state
println!(
"| VIA 0b{:08b}/0b{:08b}/0b{:08b}/0b{:08b}/",
self.via.read(VIA6522_ORA),
self.via.read(VIA6522_ORA),
self.via.read(VIA6522_DDRB),
self.via.read(VIA6522_DDRA),
);
println!("| LCD");
// if we are reading
if rw {
println!("CPU HAS SET READ FLAG FOR ADDRESS {:04x} with data 0x{:02x}", self.address_bus, self.data_bus);
match self.address_bus {
0x0000..=0x3fff => {
// read from ram
},
0x4000..=0x7fff => {
// read from ROM
}
_ => {
println!("READ OUTSIDE DATA RANGE");
}
}
} else {
println!("CPU HAS SET WRITE FLAG FOR ADDRESS 0x{:04x} with data 0x{:02x}", self.address_bus, self.data_bus);
match self.address_bus {
0x6000..=0x600f => {
self.via.write((self.address_bus - 0x6000) as u8, self.data_bus);
},
0x0000..=0x3fff => {
self.memory[self.address_bus as usize] = self.data_bus;
}
_ => {
println!("ATTEMPT TO WRITE OUTSIDE OF MEMORY");
}
}
}
//
//
// match address {
// /// VIA
// 0x6000..=0x600f => {
// if self.cpu.read_signal {
// self.cpu
// .set_data_bus(self.via.read((address - 0x6000) as u8));
// } else {
// self.via
// .write((address - 0x6000) as u8, self.cpu.data_bus());
// }
// self.via.tick();
// }
// /// RAM
// 0x0000..=0x3fff => {
// if self.cpu.read_signal {
// self.cpu.set_data_bus(self.memory[address as usize]);
// } else {
// self.memory[address as usize] = data;
// }
// }
// /// ROM
// 0x4000..=0x7fff => {
// println!("ROM READ AT {address:04x} / ROM OFFSET {:04x}", address - 0x4000);
// self.rom.read(&(address - 0x4000));
// }
// /// The ether. Scarrrrrrrryyyy......
// _ => {
// println!("XXXXLost READ:?{} to {:04x}", self.cpu.read_signal, address);
// }
// }
println!("- TICK DONE");
}
}
+60
View File
@@ -0,0 +1,60 @@
use crate::parts::clock::Clock;
use core::constants::constants_system::*;
use core::mos6502cpu::cpu::Mos6502Cpu;
use std::fs;
use std::fs::File;
use std::io::{BufReader, Read};
use std::path::Path;
pub struct BenEaterPC {
clock: Clock,
// pub because i am rendering it.
// there should be a proper interface to get the data
// for ui out of this.
pub cpu: Mos6502Cpu,
}
impl BenEaterPC {
pub fn new() -> Self {
println!("New BENEATERPC");
BenEaterPC {
clock: Clock::new(),
cpu: Mos6502Cpu::default(),
}
}
pub fn tick_system(&mut self) {
self.cpu.tick();
if self.cpu.microcode_step == 0 {
// tick the clock.
// tick the memory
// tick the VIA
} else {
self.cpu.tick();
}
}
pub fn load_rom(&mut self, rom_to_load: &str) {
println!("Preparing to load {rom_to_load}");
let file = File::open(rom_to_load).unwrap();
let mut reader = BufReader::new(file);
let mut chunks = Vec::new();
loop {
let mut buffer = vec![0u8; 1];
let bytes_read = reader.read(&mut buffer).unwrap_or(0);
if bytes_read == 0 {
break;
}
buffer.truncate(bytes_read);
chunks.push(buffer[0]);
}
let data_size = chunks.len();
print!("Loaded {}b of data. Poking into memory...", data_size);
for i in 0..SIZE_32KB {
self.cpu.poke(i as u16, chunks[i]);
}
println!("poke complete. Poked {}b of data.", data_size);
}
}
+21
View File
@@ -0,0 +1,21 @@
pub struct Clock {
ticks: u32,
}
impl Clock {
pub fn new() -> Self {
Clock { ticks: 0 }
}
pub fn tick(&mut self) {
self.ticks += 1;
}
pub fn ticks(&self) -> u32 {
self.ticks
}
pub fn reset(&mut self) {
self.ticks = 0;
}
}
+59
View File
@@ -0,0 +1,59 @@
use core::mos6502cpu::cpu::Mos6502Cpu;
use macroquad::color::{BLACK, Color};
use macroquad::prelude::*;
use macroquad::text::draw_text;
pub struct CpuDisplay {}
impl CpuDisplay {
pub fn render(cpu: &Mos6502Cpu, x_offset: f32, y_offset: f32) {
// get the data to display...
let (pc, a, x, y, address_bus, data_bus, microsteps_remaining, reset_vector, interrupt_vector, nmi_vector) = cpu.dump_data();
// ...build the interface
Self::draw_square(x_offset, y_offset, x_offset + 300.0, y_offset + 85.0, BLACK);
draw_text(
format!("PC: 0x{:04x} / {}", pc, pc).as_str(),
x_offset + 5.0,
y_offset + 18.0,
15.0,
BLACK,
);
draw_text(
format!("A: 0x{:02x} X: 0x{:02x} Y: 0x{:02x}", a, x, y).as_str(),
x_offset + 5.0,
y_offset + 35.0,
15.0,
BLACK,
);
draw_text(
format!("Address: {:016b} | {:04x}", address_bus, address_bus).as_str(),
x_offset + 5.0,
y_offset + 55.0,
15.0,
BLACK,
);
draw_text(
format!("Data: {:08b} | {:02x}", data_bus, data_bus).as_str(),
x_offset + 5.0,
y_offset + 75.0,
15.0,
BLACK,
);
draw_text(
format!("MS: {:02x}", microsteps_remaining).as_str(),
x_offset + 5.0,
y_offset + 95.0,
15.0,
BLACK,
);
}
fn draw_square(x1: f32, y1: f32, x2: f32, y2: f32, color: Color) {
// println!("Square from {x1:2.0}x{y1:2.0} to {x2:2.0}x{y2:2.0} with {:?}", color);
draw_line(x1, y1, x2, y1, 1.0, color);
draw_line(x1, y1, x1, y2, 1.0, color);
draw_line(x1, y2, x2, y2, 1.0, color);
draw_line(x2, y1, x2, y2, 1.0, color);
}
}
+9
View File
@@ -0,0 +1,9 @@
pub struct DataBus {
pub data: u8,
}
impl DataBus {
pub fn new() -> Self {
DataBus { data: 0x00 }
}
}
+274
View File
@@ -0,0 +1,274 @@
use crate::parts::mos6522_peripheral::Mos6522Peripheral;
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct HD44780 {
// Bus inputs
data_bus: u8,
rs: bool,
rw: bool,
enable: bool,
prev_enable: bool,
// Internal memory
ddram: [u8; 80],
cgram: [u8; 64],
// Cursor & display state
cursor_position: u8,
display_on: bool,
cursor_on: bool,
blink_on: bool,
entry_increment: bool,
entry_shift: bool,
// Function set flags
data_length_8bit: bool,
two_line_mode: bool,
font_5x10: bool,
// Busy flag
busy: bool,
last_command_time: Instant,
}
impl Mos6522Peripheral for HD44780 {
fn write(&mut self, port: u8) {
println!("Writing {port:08b}/{port:02x} to HD44780");
self.set_data_bus(port);
}
/// Control
///
/// Takes a u8 with the bottom 3 bits representing
/// X X X X X rs rw en
fn control(&mut self, control: u8) {
self.write_control_lines(
control & 1 << 2 == 0,
control & 1 << 1 == 0,
control & 1 == 0,
);
}
fn read(&mut self) -> u8 {
self.data_bus
}
fn tick(&mut self) {
println!("TICK OF HD44780");
if self.busy && self.last_command_time.elapsed() > Duration::from_micros(50) {
self.busy = false;
}
}
}
impl HD44780 {
pub fn new() -> Self {
Self {
data_bus: 0,
rs: false,
rw: false,
enable: false,
prev_enable: false,
ddram: [b' '; 80],
cgram: [0; 64],
cursor_position: 0,
display_on: true,
cursor_on: false,
blink_on: false,
entry_increment: true,
entry_shift: false,
data_length_8bit: true,
two_line_mode: true,
font_5x10: false,
busy: false,
last_command_time: Instant::now(),
}
}
pub fn write_control_lines(&mut self, rs: bool, rw: bool, enable: bool) {
self.rs = rs;
self.rw = rw;
// On rising edge of Enable
if enable && !self.prev_enable {
self.evaluate();
}
self.prev_enable = enable;
}
pub fn set_data_bus(&mut self, value: u8) {
self.data_bus = value;
}
pub fn read_data_bus(&self) -> u8 {
if !self.rs && self.rw {
// Return busy flag + current address
let busy_flag = if self.busy { 0x80 } else { 0x00 };
busy_flag | (self.cursor_position & 0x7F)
} else {
// Not implemented: read from DDRAM/CGRAM
0
}
}
fn evaluate(&mut self) {
if self.rw {
return;
}
if self.rs {
self.write_data(self.data_bus);
} else {
self.execute_command(self.data_bus);
}
self.busy = true;
self.last_command_time = Instant::now();
}
fn write_data(&mut self, data: u8) {
if self.cursor_position < 0x50 {
self.ddram[self.cursor_position as usize] = data;
self.cursor_position = self.cursor_position.wrapping_add(1);
}
}
fn execute_command(&mut self, cmd: u8) {
match cmd {
0x01 => {
self.ddram.fill(b' ');
self.cursor_position = 0;
}
0x02 => {
self.cursor_position = 0;
}
0x04..=0x07 => {
self.entry_increment = (cmd & 0b10) != 0;
self.entry_shift = (cmd & 0b01) != 0;
}
0x08..=0x0F => {
self.display_on = (cmd & 0b100) != 0;
self.cursor_on = (cmd & 0b010) != 0;
self.blink_on = (cmd & 0b001) != 0;
}
0x10..=0x1F => {
// Cursor/display shift — not yet implemented
}
0x20..=0x3F => {
self.data_length_8bit = (cmd & 0b10000) != 0;
self.two_line_mode = (cmd & 0b1000) != 0;
self.font_5x10 = (cmd & 0b100) != 0;
}
0x40..=0x7F => {
// Set CGRAM address — stub
}
0x80..=0xFF => {
self.cursor_position = cmd & 0x7F;
}
_ => {}
}
}
pub fn get_display_lines(&self) -> (String, String) {
let row1: String = self.ddram[0x00..0x10].iter().map(|&b| b as char).collect();
let row2: String = self.ddram[0x40..0x50].iter().map(|&b| b as char).collect();
(row1, row2)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pulse_enable(lcd: &mut HD44780) {
lcd.write_control_lines(lcd.rs, lcd.rw, true);
lcd.write_control_lines(lcd.rs, lcd.rw, false);
}
#[test]
fn test_clear_display() {
let mut lcd = HD44780::new();
lcd.set_data_bus(0x01);
pulse_enable(&mut lcd);
lcd.tick();
assert!(lcd.ddram.iter().all(|&b| b == b' '));
}
#[test]
#[ignore]
fn test_write_data() {
let mut lcd = HD44780::new();
lcd.set_data_bus(0x41); // 'A'
pulse_enable(&mut lcd);
lcd.tick();
assert_eq!(lcd.ddram[0], b'A');
}
#[test]
fn test_set_cursor() {
let mut lcd = HD44780::new();
lcd.set_data_bus(0x80 | 0x40);
pulse_enable(&mut lcd);
lcd.tick();
assert_eq!(lcd.cursor_position, 0x40);
}
#[test]
fn test_read_busy_flag_and_address() {
let mut lcd = HD44780::new();
lcd.busy = true;
lcd.cursor_position = 0x15;
lcd.write_control_lines(false, true, true);
let data = lcd.read_data_bus();
assert_eq!(data, 0x80 | 0x15);
}
#[test]
fn hello_world_display() {
let mut lcd = HD44780::new();
let setup_instructions = [
0b0000_0000, // cls
0b0011_1000, // display mode
0b0000_1110, // display on
0b0000_0110, // entry mode
];
for instruction in setup_instructions {
lcd.write_control_lines(false, false, true);
lcd.set_data_bus(instruction);
pulse_enable(&mut lcd);
lcd.tick();
}
let letter_instructions = [
0b0100_1000, // H
0b0100_0101, // E
0b0100_1100, // L
0b0100_1100, // L
0b0100_1111, // O
0b0100_0000, // _
0b0101_0111, // W
0b0100_1111, // O
0b0101_0010, // R
0b0100_1100, // L
0b0100_0100, // D
];
// move cursor to start of next line.
lcd.write_control_lines(true, false, true);
lcd.set_data_bus(0b1100_0000);
pulse_enable(&mut lcd);
lcd.tick();
let (line1, line2) = lcd.get_display_lines();
println!("LINE1 -> [{}]", line1);
println!("LINE2 -> [{}]", line2);
}
}
+10
View File
@@ -0,0 +1,10 @@
pub mod address_bus;
pub mod backplane;
pub mod ben_eater_pc;
pub mod clock;
pub mod cpu_display;
pub mod data_bus;
pub mod display_matrix;
pub mod mos6522_peripheral;
pub mod ram_display;
pub mod via6522;
+12
View File
@@ -0,0 +1,12 @@
pub trait Mos6522Peripheral {
/// Write to the data bus
fn write(&mut self, port: u8);
fn control(&mut self, control: u8);
/// Read the data bus
fn read(&mut self) -> u8;
/// Tick
///
/// Run 1 clock cycle of the peripheral
fn tick(&mut self);
}
+25
View File
@@ -0,0 +1,25 @@
use macroquad::prelude::*;
pub struct RamDisplay {}
impl RamDisplay {
pub fn render(ram: &Box<[u8]>, x_offset: f32, y_offset: f32) {
Self::draw_square(
x_offset,
y_offset,
x_offset + 200.0,
y_offset + 300.0,
BLACK,
);
draw_text("RAM", x_offset + 5.0, y_offset + 5.0, 15.0, BLACK);
}
fn draw_square(x1: f32, y1: f32, x2: f32, y2: f32, color: Color) {
// println!("Square from {x1:2.0}x{y1:2.0} to {x2:2.0}x{y2:2.0} with {:?}", color);
draw_line(x1, y1, x2, y1, 1.0, color);
draw_line(x1, y1, x1, y2, 1.0, color);
draw_line(x1, y2, x2, y2, 1.0, color);
draw_line(x2, y1, x2, y2, 1.0, color);
}
}
+219
View File
@@ -0,0 +1,219 @@
use crate::parts::display_matrix::HD44780;
use crate::parts::mos6522_peripheral::Mos6522Peripheral;
use core::constants::constants_system::*;
#[derive(Default)]
pub struct VIA6522 {
// Data registers
ora: u8,
orb: u8,
// Data direction
ddra: u8,
ddrb: u8,
// Control lines (external pins)
ca1: bool,
ca2: bool,
cb1: bool,
cb2: bool,
// Timers
t1_counter: u16,
t1_latch: u16,
t1_enabled: bool,
t2_counter: u16,
t2_latch: u16,
t2_enabled: bool,
// Interrupt flags
ifr: u8,
ier: u8,
// Peripheral (e.g., LCD)
pub lcd: Option<Box<dyn Mos6522Peripheral>>,
}
impl VIA6522 {
pub fn new() -> Self {
Self {
ora: 0,
orb: 0,
ddra: 0,
ddrb: 0,
ca1: false,
ca2: false,
cb1: false,
cb2: false,
t1_counter: 0,
t1_latch: 0,
t1_enabled: false,
t2_counter: 0,
t2_latch: 0,
t2_enabled: false,
ifr: 0,
ier: 0,
lcd: Some(Box::new(HD44780::new())),
}
}
pub fn read(&self, addr: u8) -> u8 {
match (addr & 0x0F) {
VIA6522_ORB => self.orb,
VIA6522_ORA => self.ora,
VIA6522_DDRB => self.ddrb,
VIA6522_DDRA => self.ddra,
0x4 => (self.t1_counter & 0xFF) as u8,
0x5 => (self.t1_counter >> 8) as u8,
0x6 => (self.t1_latch & 0xFF) as u8,
0x7 => (self.t1_latch >> 8) as u8,
0x8 => (self.t2_counter & 0xFF) as u8,
0x9 => (self.t2_latch & 0xFF) as u8,
0xD => self.ifr,
0xE => self.ier | 0x80,
_ => 0,
}
}
pub fn write(&mut self, addr: u8, value: u8) {
println!("VIA6522 write value 0x{value:02x} to address 0x{addr:02x}");
match addr & 0x0F {
VIA6522_ORB => self.orb = value,
VIA6522_ORA => self.ora = value,
VIA6522_DDRB => self.ddrb = value,
VIA6522_DDRA => self.ddra = value,
0x4 => {
self.t1_latch = (self.t1_latch & 0xFF00) | value as u16;
self.t1_counter = self.t1_latch;
}
0x5 => {
self.t1_latch = (value as u16) << 8 | (self.t1_latch & 0x00FF);
self.t1_counter = self.t1_latch;
self.t1_enabled = true;
}
0x6 => self.t1_latch = (self.t1_latch & 0xFF00) | value as u16,
0x7 => self.t1_latch = (value as u16) << 8 | (self.t1_latch & 0x00FF),
0x8 => self.t2_counter = value as u16,
0x9 => self.t2_latch = value as u16,
0xD => self.ifr &= !value, // Clear interrupt flags
0xE => {
if value & 0x80 != 0 {
self.ier |= value & 0x7F;
} else {
self.ier &= !(value & 0x7F);
}
}
_ => {}
}
}
pub fn tick(&mut self) {
if self.t1_enabled {
if self.t1_counter > 0 {
self.t1_counter -= 1;
if self.t1_counter == 0 {
self.ifr |= 0x40; // Set T1 interrupt
}
}
}
if self.t2_enabled {
if self.t2_counter > 0 {
self.t2_counter -= 1;
if self.t2_counter == 0 {
self.ifr |= 0x20; // Set T2 interrupt
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initialization() {
let via = VIA6522::new();
assert_eq!(via.ora, 0);
assert_eq!(via.orb, 0);
assert_eq!(via.ddra, 0);
assert_eq!(via.ddrb, 0);
assert_eq!(via.t1_counter, 0);
assert_eq!(via.t2_counter, 0);
assert!(via.lcd.is_some());
}
#[test]
fn test_read_write_registers() {
let mut via = VIA6522::new();
via.write(0x0, 0xAA);
assert_eq!(via.read(0x0), 0xAA);
via.write(0x1, 0xBB);
assert_eq!(via.read(0x1), 0xBB);
via.write(0x2, 0xCC);
assert_eq!(via.read(0x2), 0xCC);
via.write(0x3, 0xDD);
assert_eq!(via.read(0x3), 0xDD);
}
#[test]
#[ignore]
fn test_timer1_write_and_tick() {
let mut via = VIA6522::new();
via.write(0x4, 0x34); // Low byte
via.write(0x5, 0x12); // High byte, also enables timer
assert_eq!(via.t1_latch, 0x1234);
assert_eq!(via.t1_counter, 0x1234);
assert!(via.t1_enabled);
for _ in 0..0x1234 {
via.tick();
}
assert_eq!(via.t1_counter, 0);
assert_eq!(via.ifr & 0x40, 0x40); // T1 interrupt flag set
}
#[test]
#[ignore]
fn test_timer2_write_and_tick() {
let mut via = VIA6522::new();
via.t2_enabled = true;
via.write(0x8, 0x05); // Counter
for _ in 0..5 {
via.tick();
}
assert_eq!(via.t2_counter, 0);
assert_eq!(via.ifr & 0x20, 0x20); // T2 interrupt flag set
}
#[test]
#[ignore]
fn test_interrupt_enable_disable() {
let mut via = VIA6522::new();
via.write(0xE, 0x82); // Enable bit 0x02
assert_eq!(via.ier & 0x02, 0x02);
via.write(0xE, 0x02); // Disable bit 0x02
assert_eq!(via.ier & 0x02, 0x00);
}
#[test]
#[ignore]
fn test_clear_interrupt_flags() {
let mut via = VIA6522::new();
via.ifr = 0xFF;
via.write(0xD, 0x40); // Clear T1 interrupt
assert_eq!(via.ifr & 0x40, 0x00);
}
}