Skip to content

Commit f6eef3f

Browse files
committed
Allow mapping a device to non-contiguous addresses
1 parent 56ee7a9 commit f6eef3f

8 files changed

Lines changed: 119 additions & 44 deletions

File tree

k0emu/bus.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33

44
class Bus(object):
5+
ADDRESS_SPACE_SIZE = 2**16 # 64K address space
6+
57
def __init__(self, processor):
68
self.processor = processor
79
self._unmapped = BaseDevice("unmapped")
8-
self._devices_by_address = [self._unmapped] * 0x10000
9-
self._dev_bases_by_address = [0] * 0x10000
10+
self._devices_by_address = [self._unmapped] * self.ADDRESS_SPACE_SIZE
11+
self._device_registers_by_address = [0] * self.ADDRESS_SPACE_SIZE
1012
self._all_devices = set()
1113

1214
def __getitem__(self, address):
@@ -17,12 +19,35 @@ def __setitem__(self, address, value):
1719

1820
# device registration
1921

20-
def add_device(self, start, end, device):
21-
"""Register a device for an address range (inclusive).
22-
The device sees local addresses 0 through (end - start)."""
23-
for address in range(start, end + 1):
24-
self._devices_by_address[address] = device
25-
self._dev_bases_by_address[address] = start
22+
def add_device(self, address_ranges, device):
23+
"""Register a device for one or more address ranges.
24+
25+
address_ranges: list of (start, end) tuples of bus addresses,
26+
inclusive. The device's registers are mapped sequentially
27+
across the given ranges, allowing a single device model to
28+
be mapped to non-contigous addresses in the memory map.
29+
"""
30+
register = 0
31+
for start, end in address_ranges:
32+
if (start < 0) or (end >= self.ADDRESS_SPACE_SIZE) or (start > end):
33+
raise ValueError("address range 0x%04X-0x%04X out of bounds" %
34+
(start, end))
35+
36+
for address in range(start, end + 1):
37+
existing_device = self._devices_by_address[address]
38+
if existing_device is not self._unmapped:
39+
raise ValueError("address 0x%04X already mapped to %s" %
40+
(address, existing_device.name))
41+
42+
self._devices_by_address[address] = device
43+
self._device_registers_by_address[address] = register
44+
register += 1
45+
46+
if register != device.size:
47+
raise ValueError("%s: ranges cover %d bytes but device size is %d" %
48+
(device.name, register, device.size))
49+
50+
device.bus = self
2651
self._all_devices.add(device)
2752

2853
def device(self, name):
@@ -46,10 +71,10 @@ def tick(self, cycles):
4671

4772
def read(self, address):
4873
device = self._devices_by_address[address]
49-
local_address = address - self._dev_bases_by_address[address]
50-
return device.read(local_address)
74+
register = self._device_registers_by_address[address]
75+
return device.read(register)
5176

5277
def write(self, address, value):
5378
device = self._devices_by_address[address]
54-
local_address = address - self._dev_bases_by_address[address]
55-
device.write(local_address, value)
79+
register = self._device_registers_by_address[address]
80+
device.write(register, value)

k0emu/devices.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
class BaseDevice(object):
22
def __init__(self, name):
33
self.name = name
4+
self.bus = None
45
self.size = 0
56
self.ticks = 0
67

k0emu/system.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,18 @@ def make_processor():
88
proc = Processor()
99

1010
rom = MemoryDevice("rom", size=0xF000, fill=0xFF, writable=False)
11-
proc.bus.add_device(0x0000, 0xEFFF, rom)
11+
proc.bus.add_device([(0x0000, 0xEFFF)], rom)
1212

1313
expansion_ram = MemoryDevice("expansion_ram", size=0x0800)
14-
proc.bus.add_device(0xF000, 0xF7FF, expansion_ram)
14+
proc.bus.add_device([(0xF000, 0xF7FF)], expansion_ram)
1515

1616
reserved = MemoryDevice("reserved", size=0x0300, fill=0x08, writable=False)
17-
proc.bus.add_device(0xF800, 0xFAFF, reserved)
17+
proc.bus.add_device([(0xF800, 0xFAFF)], reserved)
1818

1919
high_speed_ram = MemoryDevice("high_speed_ram", size=0x03E0)
20-
proc.bus.add_device(0xFB00, 0xFEDF, high_speed_ram)
20+
proc.bus.add_device([(0xFB00, 0xFEDF)], high_speed_ram)
2121

2222
register_file = RegisterFileDevice()
23-
proc.bus.add_device(0xFEE0, 0xFEFF, register_file)
24-
25-
sfr = MemoryDevice("sfr", size=0x0100)
26-
proc.bus.add_device(0xFF00, 0xFFFF, sfr)
23+
proc.bus.add_device([(0xFEE0, 0xFEFF)], register_file)
2724

2825
return proc

k0emu/tests/test_bus.py

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ def test_read_routes_to_device(self):
1919
proc = _FakeProcessor()
2020
bus = Bus(proc)
2121
mem = MemoryDevice("test", size=4)
22-
bus.add_device(0x1000, 0x1003, mem)
22+
bus.add_device([(0x1000, 0x1003)], mem)
2323
mem.write(2, 0x42)
2424
self.assertEqual(bus.read(0x1002), 0x42)
2525

2626
def test_write_routes_to_device(self):
2727
proc = _FakeProcessor()
2828
bus = Bus(proc)
2929
mem = MemoryDevice("test", size=4)
30-
bus.add_device(0x1000, 0x1003, mem)
30+
bus.add_device([(0x1000, 0x1003)], mem)
3131
bus.write(0x1001, 0x42)
3232
self.assertEqual(mem.read(1), 0x42)
3333

@@ -37,7 +37,7 @@ def test_device_sees_local_address(self):
3737
proc = _FakeProcessor()
3838
bus = Bus(proc)
3939
mem = MemoryDevice("test", size=4)
40-
bus.add_device(0x8000, 0x8003, mem)
40+
bus.add_device([(0x8000, 0x8003)], mem)
4141
bus.write(0x8003, 0x42)
4242
self.assertEqual(mem.read(3), 0x42)
4343

@@ -60,30 +60,83 @@ def test_two_devices_at_different_ranges(self):
6060
bus = Bus(proc)
6161
mem_a = MemoryDevice("a", size=4)
6262
mem_b = MemoryDevice("b", size=4)
63-
bus.add_device(0x1000, 0x1003, mem_a)
64-
bus.add_device(0x2000, 0x2003, mem_b)
63+
bus.add_device([(0x1000, 0x1003)], mem_a)
64+
bus.add_device([(0x2000, 0x2003)], mem_b)
6565
bus.write(0x1000, 0x11)
6666
bus.write(0x2000, 0x22)
6767
self.assertEqual(bus.read(0x1000), 0x11)
6868
self.assertEqual(bus.read(0x2000), 0x22)
6969

70-
def test_later_device_overlays_earlier(self):
70+
# registration validation
71+
72+
def test_overlapping_device_raises(self):
73+
proc = _FakeProcessor()
74+
bus = Bus(proc)
75+
mem_a = MemoryDevice("a", size=4)
76+
mem_b = MemoryDevice("b", size=2)
77+
bus.add_device([(0x1000, 0x1003)], mem_a)
78+
with self.assertRaises(ValueError):
79+
bus.add_device([(0x1000, 0x1001)], mem_b)
80+
81+
def test_start_negative_raises(self):
82+
proc = _FakeProcessor()
83+
bus = Bus(proc)
84+
mem = MemoryDevice("test", size=1)
85+
with self.assertRaises(ValueError):
86+
bus.add_device([(-1, -1)], mem)
87+
88+
def test_end_past_address_space_raises(self):
89+
proc = _FakeProcessor()
90+
bus = Bus(proc)
91+
mem = MemoryDevice("test", size=2)
92+
with self.assertRaises(ValueError):
93+
bus.add_device([(0xFFFF, 0x10000)], mem)
94+
95+
def test_start_greater_than_end_raises(self):
7196
proc = _FakeProcessor()
7297
bus = Bus(proc)
73-
mem_a = MemoryDevice("a", size=4, fill=0xAA)
74-
mem_b = MemoryDevice("b", size=2, fill=0xBB)
75-
bus.add_device(0x1000, 0x1003, mem_a)
76-
bus.add_device(0x1000, 0x1001, mem_b)
77-
self.assertEqual(bus.read(0x1000), 0xBB) # overlaid by b
78-
self.assertEqual(bus.read(0x1002), 0xAA) # still a
98+
mem = MemoryDevice("test", size=4)
99+
with self.assertRaises(ValueError):
100+
bus.add_device([(0x1003, 0x1000)], mem)
101+
102+
def test_range_size_mismatch_raises(self):
103+
proc = _FakeProcessor()
104+
bus = Bus(proc)
105+
mem = MemoryDevice("test", size=4)
106+
with self.assertRaises(ValueError):
107+
bus.add_device([(0x1000, 0x1001)], mem)
108+
109+
# non-contiguous ranges
110+
111+
def test_non_contiguous_ranges_assign_sequential_registers(self):
112+
proc = _FakeProcessor()
113+
bus = Bus(proc)
114+
mem = MemoryDevice("test", size=3)
115+
bus.add_device([(0x1000, 0x1000), (0x2000, 0x2001)], mem)
116+
bus.write(0x1000, 0xAA) # register 0
117+
bus.write(0x2000, 0xBB) # register 1
118+
bus.write(0x2001, 0xCC) # register 2
119+
self.assertEqual(mem.read(0), 0xAA)
120+
self.assertEqual(mem.read(1), 0xBB)
121+
self.assertEqual(mem.read(2), 0xCC)
122+
123+
# add_device sets bus reference
124+
125+
def test_add_device_sets_bus_on_device(self):
126+
proc = _FakeProcessor()
127+
bus = Bus(proc)
128+
mem = MemoryDevice("test", size=4)
129+
self.assertIsNone(mem.bus)
130+
bus.add_device([(0x1000, 0x1003)], mem)
131+
self.assertIs(mem.bus, bus)
79132

80133
# reset
81134

82135
def test_reset_resets_all_devices(self):
83136
proc = _FakeProcessor()
84137
bus = Bus(proc)
85138
mem = MemoryDevice("test", size=4)
86-
bus.add_device(0x1000, 0x1003, mem)
139+
bus.add_device([(0x1000, 0x1003)], mem)
87140
mem.ticks = 99
88141
bus.reset()
89142
# BaseDevice.reset() is a no-op, but processor.reset() is called
@@ -102,8 +155,8 @@ def test_tick_fans_out_to_all_devices(self):
102155
bus = Bus(proc)
103156
mem_a = MemoryDevice("a", size=4)
104157
mem_b = MemoryDevice("b", size=4)
105-
bus.add_device(0x1000, 0x1003, mem_a)
106-
bus.add_device(0x2000, 0x2003, mem_b)
158+
bus.add_device([(0x1000, 0x1003)], mem_a)
159+
bus.add_device([(0x2000, 0x2003)], mem_b)
107160
bus.tick(5)
108161
self.assertEqual(mem_a.ticks, 5)
109162
self.assertEqual(mem_b.ticks, 5)
@@ -112,7 +165,7 @@ def test_tick_accumulates(self):
112165
proc = _FakeProcessor()
113166
bus = Bus(proc)
114167
mem = MemoryDevice("test", size=4)
115-
bus.add_device(0x1000, 0x1003, mem)
168+
bus.add_device([(0x1000, 0x1003)], mem)
116169
bus.tick(3)
117170
bus.tick(7)
118171
self.assertEqual(mem.ticks, 10)
@@ -123,7 +176,7 @@ def test_device_finds_by_name(self):
123176
proc = _FakeProcessor()
124177
bus = Bus(proc)
125178
mem = MemoryDevice("my_ram", size=4)
126-
bus.add_device(0x1000, 0x1003, mem)
179+
bus.add_device([(0x1000, 0x1003)], mem)
127180
self.assertIs(bus.device("my_ram"), mem)
128181

129182
def test_device_raises_for_unknown_name(self):
@@ -138,15 +191,15 @@ def test_getitem_reads_address(self):
138191
proc = _FakeProcessor()
139192
bus = Bus(proc)
140193
mem = MemoryDevice("test", size=4)
141-
bus.add_device(0x1000, 0x1003, mem)
194+
bus.add_device([(0x1000, 0x1003)], mem)
142195
mem.write(0, 0x42)
143196
self.assertEqual(bus[0x1000], 0x42)
144197

145198
def test_setitem_writes_address(self):
146199
proc = _FakeProcessor()
147200
bus = Bus(proc)
148201
mem = MemoryDevice("test", size=4)
149-
bus.add_device(0x1000, 0x1003, mem)
202+
bus.add_device([(0x1000, 0x1003)], mem)
150203
bus[0x1001] = 0x42
151204
self.assertEqual(mem.read(1), 0x42)
152205

k0emu/tests/test_devices.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
from k0emu.bus import Bus
23
from k0emu.devices import MemoryDevice, RegisterFileDevice
34

45

@@ -115,5 +116,3 @@ def test_out_of_bounds_raises(self):
115116
rf = RegisterFileDevice()
116117
with self.assertRaises(IndexError):
117118
rf.read(32)
118-
119-

k0emu/tests/test_processor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
def _make_processor():
88
proc = Processor()
99
mem = MemoryDevice("test_memory", size=0x10000)
10-
proc.bus.add_device(0x0000, 0xFFFF, mem)
10+
proc.bus.add_device([(0x0000, 0xFFFF)], mem)
1111
return proc, mem
1212

1313

k0emu/tests/test_processor_cycles.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
def _make_processor():
77
proc = Processor()
88
mem = MemoryDevice("test_memory", size=0x10000)
9-
proc.bus.add_device(0x0000, 0xFFFF, mem)
9+
proc.bus.add_device([(0x0000, 0xFFFF)], mem)
1010
return proc
1111

1212

k0emu/tests/test_run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
def _make_runner(code):
99
proc = Processor()
1010
mem = MemoryDevice("test_memory", size=0x10000)
11-
proc.bus.add_device(0x0000, 0xFFFF, mem)
11+
proc.bus.add_device([(0x0000, 0xFFFF)], mem)
1212
for i, byte in enumerate(code):
1313
mem.write(i, byte)
1414
output = io.StringIO()

0 commit comments

Comments
 (0)