Coverage for MC6809/components/memory.py: 51%
151 statements
« prev ^ index » next coverage.py v7.2.1, created at 2023-03-06 19:50 +0100
« prev ^ index » next coverage.py v7.2.1, created at 2023-03-06 19:50 +0100
1#!/usr/bin/env python
3"""
4 MC6809 - 6809 CPU emulator in Python
5 =======================================
7 TODO:
8 Maybe "merge" ROM, RAM,
9 so the offset calulation in ROM is not needed.
11 6809 is Big-Endian
13 :copyleft: 2013-2014 by the MC6809 team, see AUTHORS for more details.
14 :license: GNU GPL v3 or above, see LICENSE for more details.
16 Based on:
17 * ApplyPy by James Tauber (MIT license)
18 * XRoar emulator by Ciaran Anscomb (GPL license)
19 more info, see README
20"""
23import array
24import logging
27log = logging.getLogger("MC6809")
30class Memory:
31 def __init__(self, cfg, read_bus_request_queue=None, read_bus_response_queue=None, write_bus_queue=None):
32 self.cfg = cfg
33 self.read_bus_request_queue = read_bus_request_queue
34 self.read_bus_response_queue = read_bus_response_queue
35 self.write_bus_queue = write_bus_queue
37 self.INTERNAL_SIZE = (0xFFFF + 1)
39 self.RAM_SIZE = (self.cfg.RAM_END - self.cfg.RAM_START) + 1
40 self.ROM_SIZE = (self.cfg.ROM_END - self.cfg.ROM_START) + 1
41 assert not hasattr(cfg, "RAM_SIZE"), (
42 f"cfg.RAM_SIZE is deprecated! Remove it from: {self.cfg.__class__.__name__}"
43 )
44 assert not hasattr(cfg, "ROM_SIZE"), (
45 f"cfg.ROM_SIZE is deprecated! Remove it from: {self.cfg.__class__.__name__}"
46 )
48 assert not hasattr(cfg, "ram"), (
49 f"cfg.ram is deprecated! Remove it from: {self.cfg.__class__.__name__}"
50 )
52 assert not hasattr(cfg, "DEFAULT_ROM"), (
53 f"cfg.DEFAULT_ROM must be converted"
54 f" to DEFAULT_ROMS tuple in {self.cfg.__class__.__name__}"
55 )
57 assert self.RAM_SIZE + self.RAM_SIZE <= self.INTERNAL_SIZE, (
58 f"{self.RAM_SIZE + self.RAM_SIZE} Bytes < {self.INTERNAL_SIZE} Bytes"
59 )
61 # About different types of memory see:
62 # http://www.python-forum.de/viewtopic.php?p=263775#p263775 (de)
64 # The old variant: Use a simple List:
65# self._mem = [None] * self.cfg.MEMORY_SIZE
67 # Bytearray will be consume less RAM, but it's slower:
68# self._mem = bytearray(self.cfg.MEMORY_SIZE)
70 # array consumes also less RAM than lists and it's a little bit faster:
71 self._mem = array.array("B", [0x00] * self.INTERNAL_SIZE) # unsigned char
73 if cfg and cfg.rom_cfg: 73 ↛ 74line 73 didn't jump to line 74, because the condition on line 73 was never true
74 for romfile in cfg.rom_cfg:
75 self.load_file(romfile)
77 self._read_byte_callbacks = {}
78 self._read_word_callbacks = {}
79 self._write_byte_callbacks = {}
80 self._write_word_callbacks = {}
82 # Memory middlewares are function that called on memory read or write
83 # the function can change the value that is read/write
84 #
85 # init read/write byte middlewares:
86 self._read_byte_middleware = {}
87 self._write_byte_middleware = {}
88 for addr_range, functions in list(cfg.memory_byte_middlewares.items()): 88 ↛ 89line 88 didn't jump to line 89, because the loop on line 88 never started
89 start_addr, end_addr = addr_range
90 read_func, write_func = functions
92 if read_func:
93 self.add_read_byte_middleware(read_func, start_addr, end_addr)
95 if write_func:
96 self.add_write_byte_middleware(write_func, start_addr, end_addr)
98 # init read/write word middlewares:
99 self._read_word_middleware = {}
100 self._write_word_middleware = {}
101 for addr_range, functions in list(cfg.memory_word_middlewares.items()): 101 ↛ 102line 101 didn't jump to line 102, because the loop on line 101 never started
102 start_addr, end_addr = addr_range
103 read_func, write_func = functions
105 if read_func:
106 self.add_read_word_middleware(read_func, start_addr, end_addr)
108 if write_func:
109 self.add_write_word_middleware(write_func, start_addr, end_addr)
111# log.critical(
112# # log.debug(
113# "memory read middlewares: %s", self._read_byte_middleware
114# )
115# log.critical(
116# # log.debug(
117# "memory write middlewares: %s", self._write_byte_middleware
118# )
120 log.critical("init RAM $%04x (dez.:%s) Bytes RAM $%04x (dez.:%s) Bytes (total %s real: %s)",
121 self.RAM_SIZE, self.RAM_SIZE,
122 self.ROM_SIZE, self.ROM_SIZE,
123 self.RAM_SIZE + self.ROM_SIZE,
124 len(self._mem)
125 )
127 # ---------------------------------------------------------------------------
129 def _map_address_range(self, callbacks_dict, callback_func, start_addr, end_addr=None):
130 if end_addr is None:
131 callbacks_dict[start_addr] = callback_func
132 else:
133 for addr in range(start_addr, end_addr + 1):
134 callbacks_dict[addr] = callback_func
136 # ---------------------------------------------------------------------------
138 def add_read_byte_callback(self, callback_func, start_addr, end_addr=None):
139 self._map_address_range(self._read_byte_callbacks, callback_func, start_addr, end_addr)
141 def add_read_word_callback(self, callback_func, start_addr, end_addr=None):
142 self._map_address_range(self._read_word_callbacks, callback_func, start_addr, end_addr)
144 def add_write_byte_callback(self, callback_func, start_addr, end_addr=None):
145 self._map_address_range(self._write_byte_callbacks, callback_func, start_addr, end_addr)
147 def add_write_word_callback(self, callback_func, start_addr, end_addr=None):
148 self._map_address_range(self._write_word_callbacks, callback_func, start_addr, end_addr)
150 # ---------------------------------------------------------------------------
152 def add_read_byte_middleware(self, callback_func, start_addr, end_addr=None):
153 self._map_address_range(self._read_byte_middleware, callback_func, start_addr, end_addr)
155 def add_write_byte_middleware(self, callback_func, start_addr, end_addr=None):
156 self._map_address_range(self._write_byte_middleware, callback_func, start_addr, end_addr)
158 def add_read_word_middleware(self, callback_func, start_addr, end_addr=None):
159 self._map_address_range(self._read_word_middleware, callback_func, start_addr, end_addr)
161 def add_write_word_middleware(self, callback_func, start_addr, end_addr=None):
162 self._map_address_range(self._write_word_middleware, callback_func, start_addr, end_addr)
164 # ---------------------------------------------------------------------------
166 def load(self, address, data):
167 if isinstance(data, str):
168 data = [ord(c) for c in data]
170 log.debug("ROM load at $%04x: %s", address,
171 ", ".join(f"${i:02x}" for i in data)
172 )
173 for ea, datum in enumerate(data, address):
174 try:
175 self._mem[ea] = datum
176 except OverflowError as err:
177 raise OverflowError(
178 f"{err} - datum=${datum:x} ea=${ea:04x}"
179 f" (load address was: ${address:04x} - data length: {len(data):d}Bytes)"
180 )
182 def load_file(self, romfile):
183 data = romfile.get_data()
184 self.load(romfile.address, data)
185 log.critical("Load ROM file %r to $%04x", romfile.filepath, romfile.address)
187 # ---------------------------------------------------------------------------
189 def read_byte(self, address):
190 self.cpu.cycles += 1
192 if address in self._read_byte_callbacks: 192 ↛ 193line 192 didn't jump to line 193, because the condition on line 192 was never true
193 byte = self._read_byte_callbacks[address](
194 self.cpu.cycles, self.cpu.last_op_address, address
195 )
196 assert byte is not None, (
197 f"Error: read byte callback for ${address:04x}"
198 f" func {self._read_byte_callbacks[address].__name__!r} has return None!"
199 )
200 return byte
202 try:
203 byte = self._mem[address]
204 except KeyError:
205 msg = f"reading outside memory area (PC:${self.cpu.program_counter.value:x})"
206 self.cfg.mem_info(address, msg)
207 msg2 = f"{msg}: ${address:x}"
208 log.warning(msg2)
209 # raise RuntimeError(msg2)
210 byte = 0x0
212 if address in self._read_byte_middleware: 212 ↛ 213line 212 didn't jump to line 213, because the condition on line 212 was never true
213 byte = self._read_byte_middleware[address](
214 self.cpu.cycles, self.cpu.last_op_address, address, byte
215 )
216 assert byte is not None, (
217 f"Error: read byte middleware for ${address:04x}"
218 f" func {self._read_byte_middleware[address].__name__!r} has return None!"
219 )
221# log.log(5, "%04x| (%i) read byte $%x from $%x",
222# self.cpu.last_op_address, self.cpu.cycles,
223# byte, address
224# )
225 return byte
227 def read_word(self, address):
228 if address in self._read_word_callbacks: 228 ↛ 229line 228 didn't jump to line 229, because the condition on line 228 was never true
229 word = self._read_word_callbacks[address](
230 self.cpu.cycles, self.cpu.last_op_address, address
231 )
232 assert word is not None, (
233 f"Error: read word callback for ${address:04x}"
234 f" func {self._read_word_callbacks[address].__name__!r} has return None!"
235 )
236 return word
238 # 6809 is Big-Endian
239 return (self.read_byte(address) << 8) + self.read_byte(address + 1)
241 # ---------------------------------------------------------------------------
243 def write_byte(self, address, value):
244 self.cpu.cycles += 1
246 assert value >= 0, f"Write negative byte hex:{value:00x} dez:{value:d} to ${address:04x}"
247 assert value <= 0xff, (
248 f"Write out of range byte hex:{value:02x} dez:{value:d} to ${address:04x}"
249 )
250# if not (0x0 <= value <= 0xff):
251# log.error("Write out of range value $%02x to $%04x", value, address)
252# value = value & 0xff
253# log.error(" ^^^^ wrap around to $%x", value)
255 if address in self._write_byte_middleware: 255 ↛ 256line 255 didn't jump to line 256, because the condition on line 255 was never true
256 value = self._write_byte_middleware[address](
257 self.cpu.cycles, self.cpu.last_op_address, address, value
258 )
259 assert value is not None, (
260 f"Error: write byte middleware for ${address:04x}"
261 f" func {self._write_byte_middleware[address].__name__!r} has return None!"
262 )
264 if address in self._write_byte_callbacks: 264 ↛ 265line 264 didn't jump to line 265, because the condition on line 264 was never true
265 return self._write_byte_callbacks[address](
266 self.cpu.cycles, self.cpu.last_op_address, address, value
267 )
269 if self.cfg.ROM_START <= address <= self.cfg.ROM_END:
270 msg = (
271 f"{self.cpu.program_counter.value:04x}|"
272 f" writing into ROM at ${address:04x} ignored."
273 )
274 self.cfg.mem_info(address, msg)
275 msg2 = f"{msg}: ${address:x}"
276 log.critical(msg2)
277 return
279 try:
280 self._mem[address] = value
281 except (IndexError, KeyError):
282 msg = (
283 f"{self.cpu.program_counter.value:04x}|"
284 f" writing to {address:x} is outside RAM/ROM !"
285 )
286 self.cfg.mem_info(address, msg)
287 msg2 = f"{msg}: ${address:x}"
288 log.warning(msg2)
289# raise RuntimeError(msg2)
291 def write_word(self, address, word):
292 assert word >= 0, f"Write negative word hex:{word:04x} dez:{word:d} to ${address:04x}"
293 assert word <= 0xffff, (
294 f"Write out of range word hex:{word:04x} dez:{word:d} to ${address:04x}"
295 )
297 if address in self._write_word_middleware: 297 ↛ 298line 297 didn't jump to line 298, because the condition on line 297 was never true
298 word = self._write_word_middleware[address](
299 self.cpu.cycles, self.cpu.last_op_address, address, word
300 )
301 assert word is not None, (
302 f"Error: write word middleware for ${address:04x}"
303 f" func {self._write_word_middleware[address].__name__!r} has return None!"
304 )
306 if address in self._write_word_callbacks: 306 ↛ 307line 306 didn't jump to line 307, because the condition on line 306 was never true
307 return self._write_word_callbacks[address](
308 self.cpu.cycles, self.cpu.last_op_address, address, word
309 )
311 # 6809 is Big-Endian
312 self.write_byte(address, word >> 8)
313 self.write_byte(address + 1, word & 0xff)
315 # ---------------------------------------------------------------------------
317 def get(self, start, end):
318 """
319 used in unittests
320 """
321 return [self.read_byte(addr) for addr in range(start, end)]
323 def iter_bytes(self, start, end):
324 for addr in range(start, end):
325 yield addr, self.read_byte(addr)
327 def get_dump(self, start, end):
328 dump_lines = []
329 for addr, value in self.iter_bytes(start, end):
330 msg = f"${addr:04x}: ${value:02x} (dez: {value:d})"
331 msg = f"{msg:<25}| {self.cfg.mem_info.get_shortest(addr)}"
332 dump_lines.append(msg)
333 return dump_lines
335 def print_dump(self, start, end):
336 print(f"Memory dump from ${start:04x} to ${end:04x}:")
337 dump_lines = self.get_dump(start, end)
338 print("\n".join(f"\t{line}" for line in dump_lines))