1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! The On-Chip I/O.
use core::panic;
use bitflags::bitflags;
use ufmt::{uDebug, uwrite, uwriteln};
use volatile_register::RW;
#[repr(C, packed)]
pub struct RegisterBlock {
data_direction: RW<u8>,
port: RW<u8>,
}
impl RegisterBlock {
/// Fetches the current value of the processor port `$01`, which includes the memory
/// configuration.
pub fn port(&self) -> Port {
Port::from_bits(self.port.read()).expect("No unknown bits")
}
/// Alters the memory configuration.
///
/// For each parameter, `None` means "preserve the current configuration".
///
/// # Panics
///
/// Panics on an inconsistent parameter setting (e.g. `kernal = Some(false)`,
/// `basic = Some(true)`).
pub unsafe fn configure_memory(
&self,
kernal: Option<bool>,
basic: Option<bool>,
io_config: Option<IoConfig>,
) {
unsafe {
self.port.modify(|p| {
let cur_hiram = (p & Port::HIRAM.bits()) != 0;
let cur_loram = (p & Port::LORAM.bits()) != 0;
let cur_charen = (p & Port::CHAREN.bits()) != 0;
// Valid settings are defined by this constraint table:
//
// Bit+-------------+-----------+------------+
// 210| $8000-$BFFF |$D000-$DFFF|$E000-$FFFF |
// +---+---+-------------+-----------+------------+
// | 7 |111| Cart.+Basic | I/O | Kernal ROM |
// +---+---+-------------+-----------+------------+
// | 6 |110| RAM | I/O | Kernal ROM |
// +---+---+-------------+-----------+------------+
// | 5 |101| RAM | I/O | RAM |
// +---+---+-------------+-----------+------------+
// | 4 |100| RAM | RAM | RAM |
// +---+---+-------------+-----------+------------+
// | 3 |011| Cart.+Basic | Char. ROM | Kernal ROM |
// +---+---+-------------+-----------+------------+
// | 2 |010| RAM | Char. ROM | Kernal ROM |
// +---+---+-------------+-----------+------------+
// | 1 |001| RAM | Char. ROM | RAM |
// +---+---+-------------+-----------+------------+
// | 0 |000| RAM | RAM | RAM |
// +---+---+-------------+-----------+------------+
// |||
// /CharEn|/LoRam
// |
// /HiRam
// First, evaluate the desired configuration by replacing `None`s with the
// respective current configurations.
let kernal = kernal.unwrap_or(cur_hiram);
let basic = basic.unwrap_or(cur_loram && cur_hiram);
let io_config =
io_config.unwrap_or_else(|| match (cur_loram || cur_hiram, cur_charen) {
(false, _) => IoConfig::Ram,
(true, false) => IoConfig::CharacterRom,
(true, true) => IoConfig::Io,
});
// Now determine the correct bit settings from the constraint table. If
// the desired configuration doesn't exist in the table, we panic.
let new_bits: u8 = match (basic, io_config, kernal) {
(true, IoConfig::Io, true) => 0b111,
(false, IoConfig::Io, true) => 0b110,
(false, IoConfig::Io, false) => 0b101,
(true, IoConfig::CharacterRom, true) => 0b011,
(false, IoConfig::CharacterRom, true) => 0b010,
(false, IoConfig::CharacterRom, false) => 0b001,
// This configuration appears twice in the table; normalize for
// reliability.
(false, IoConfig::Ram, false) => 0b000,
_ => panic!(),
};
// Clear the current bits and then set the new bits.
(p & !(Port::HIRAM | Port::LORAM | Port::CHAREN).bits()) | new_bits
});
}
}
}
bitflags! {
/// The processor port `$01`, which includes the [memory configuration].
///
/// [memory configuration]: http://unusedino.de/ec64/technical/aay/c64/memcfg.htm
pub struct Port: u8 {
/// Selects ROM or RAM at `$A000`.
///
/// - `0` = RAM
/// - `1` = BASIC ROM
const LORAM = 0b00000001;
/// Selects ROM or RAM at `$E000`.
///
/// - `0` = RAM
/// - `1` = Kernal ROM
const HIRAM = 0b00000010;
/// Selects character ROM or I/O devices.
///
/// - `0` = Character ROM
/// - `1` = I/O
const CHAREN = 0b00000100;
/// Cassette Data Output line.
///
/// This line is connected to the Cassette Data Write line on the cassette port,
/// and is used to send the data which is written to tape.
const CASSETTE_DATA_OUTPUT = 0b00001000;
/// Cassette Switch Sense.
const CASSETTE_SWITCH_SENSE = 0b00010000;
/// Cassette Motor Switch Control.
const CASSETTE_MOTOR_SWITCH_CONTROL = 0b00100000;
/// Not connected, no function presently defined.
const RESERVED_6 = 0b01000000;
const RESERVED_7 = 0b10000000;
}
}
impl uDebug for Port {
fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
where
W: ufmt::uWrite + ?Sized,
{
uwriteln!(f, "On-Chip I/O Port:")?;
uwriteln!(f, " loram: {}", self.contains(Self::LORAM))?;
uwriteln!(f, " hiram: {}", self.contains(Self::HIRAM))?;
uwrite!(f, " charen: {}", self.contains(Self::CHAREN))
}
}
impl Port {
/// Returns whether the Kernal ROM is selected instead of RAM.
///
/// Because the BASIC interpreter uses the Kernal, it is also switched out and
/// replaced by RAM whenever the Kernal ROM is switched out.
///
/// - When this is `false`, addresses `$A000-$BFFF` and `$E000-$FFFF` are RAM.
/// - Note that the RAM in these address rangees is still writable; it just cannot
/// be read.
/// - When this is `true`, addresses `$E000-$FFFF` are the Kernal ROM.
pub fn kernal(&self) -> bool {
self.contains(Self::HIRAM)
}
/// Returns whether the BASIC ROM is selected instead of RAM.
///
/// - When this is `false`, addresses `$A000-$BFFF` are RAM.
/// - When this is `true`, addresses `$A000-$BFFF` are the BASIC ROM.
/// - Note that the RAM in this address range is still writable; it just cannot be
/// read.
pub fn basic(&self) -> bool {
self.contains(Self::LORAM | Self::HIRAM)
}
/// Returns whether I/O devices or the character ROM are available.
///
/// This specifies the configuration of addresses `$D000-$DFFF`.
pub fn io_config(&self) -> IoConfig {
if self.intersects(Self::LORAM | Self::HIRAM) {
if self.contains(Self::CHAREN) {
IoConfig::Io
} else {
IoConfig::CharacterRom
}
} else {
IoConfig::Ram
}
}
}
/// Whether I/O devices or the character ROM are available.
pub enum IoConfig {
/// Addresses `$D000-$DFFF` are RAM.
Ram,
/// Addresses `$D000-$DFFF` are the Character ROM.
CharacterRom,
/// Addresses `$D000-$DFFF` are I/O devices.
Io,
}