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

1#!/usr/bin/env python 

2 

3""" 

4 MC6809 - 6809 CPU emulator in Python 

5 ======================================= 

6 

7 TODO: 

8 Maybe "merge" ROM, RAM, 

9 so the offset calulation in ROM is not needed. 

10 

11 6809 is Big-Endian 

12 

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. 

15 

16 Based on: 

17 * ApplyPy by James Tauber (MIT license) 

18 * XRoar emulator by Ciaran Anscomb (GPL license) 

19 more info, see README 

20""" 

21 

22 

23import array 

24import logging 

25 

26 

27log = logging.getLogger("MC6809") 

28 

29 

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 

36 

37 self.INTERNAL_SIZE = (0xFFFF + 1) 

38 

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 ) 

47 

48 assert not hasattr(cfg, "ram"), ( 

49 f"cfg.ram is deprecated! Remove it from: {self.cfg.__class__.__name__}" 

50 ) 

51 

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 ) 

56 

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 ) 

60 

61 # About different types of memory see: 

62 # http://www.python-forum.de/viewtopic.php?p=263775#p263775 (de) 

63 

64 # The old variant: Use a simple List: 

65# self._mem = [None] * self.cfg.MEMORY_SIZE 

66 

67 # Bytearray will be consume less RAM, but it's slower: 

68# self._mem = bytearray(self.cfg.MEMORY_SIZE) 

69 

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 

72 

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) 

76 

77 self._read_byte_callbacks = {} 

78 self._read_word_callbacks = {} 

79 self._write_byte_callbacks = {} 

80 self._write_word_callbacks = {} 

81 

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 

91 

92 if read_func: 

93 self.add_read_byte_middleware(read_func, start_addr, end_addr) 

94 

95 if write_func: 

96 self.add_write_byte_middleware(write_func, start_addr, end_addr) 

97 

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 

104 

105 if read_func: 

106 self.add_read_word_middleware(read_func, start_addr, end_addr) 

107 

108 if write_func: 

109 self.add_write_word_middleware(write_func, start_addr, end_addr) 

110 

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# ) 

119 

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 ) 

126 

127 # --------------------------------------------------------------------------- 

128 

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 

135 

136 # --------------------------------------------------------------------------- 

137 

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) 

140 

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) 

143 

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) 

146 

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) 

149 

150 # --------------------------------------------------------------------------- 

151 

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) 

154 

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) 

157 

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) 

160 

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) 

163 

164 # --------------------------------------------------------------------------- 

165 

166 def load(self, address, data): 

167 if isinstance(data, str): 

168 data = [ord(c) for c in data] 

169 

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 ) 

181 

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) 

186 

187 # --------------------------------------------------------------------------- 

188 

189 def read_byte(self, address): 

190 self.cpu.cycles += 1 

191 

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 

201 

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 

211 

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 ) 

220 

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 

226 

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 

237 

238 # 6809 is Big-Endian 

239 return (self.read_byte(address) << 8) + self.read_byte(address + 1) 

240 

241 # --------------------------------------------------------------------------- 

242 

243 def write_byte(self, address, value): 

244 self.cpu.cycles += 1 

245 

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) 

254 

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 ) 

263 

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 ) 

268 

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 

278 

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) 

290 

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 ) 

296 

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 ) 

305 

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 ) 

310 

311 # 6809 is Big-Endian 

312 self.write_byte(address, word >> 8) 

313 self.write_byte(address + 1, word & 0xff) 

314 

315 # --------------------------------------------------------------------------- 

316 

317 def get(self, start, end): 

318 """ 

319 used in unittests 

320 """ 

321 return [self.read_byte(addr) for addr in range(start, end)] 

322 

323 def iter_bytes(self, start, end): 

324 for addr in range(start, end): 

325 yield addr, self.read_byte(addr) 

326 

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 

334 

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))