Lots of stuff.
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "beneater"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
core = { path = "../core" }
|
||||
macroquad.workspace = true
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod parts;
|
||||
pub mod backplane;
|
||||
@@ -0,0 +1,9 @@
|
||||
pub struct AddressBus {
|
||||
pub address: u16,
|
||||
}
|
||||
|
||||
impl AddressBus {
|
||||
pub fn new() -> Self {
|
||||
AddressBus { address: 0x0000 }
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
pub struct DataBus {
|
||||
pub data: u8,
|
||||
}
|
||||
|
||||
impl DataBus {
|
||||
pub fn new() -> Self {
|
||||
DataBus { data: 0x00 }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user