1.1 --- a/ULA.txt Mon Jun 20 21:32:22 2016 +0200
1.2 +++ b/ULA.txt Mon Jun 20 23:17:48 2016 +0200
1.3 @@ -57,6 +57,11 @@
1.4 F S F S ...
1.5 a b c a b c ...
1.6
1.7 + ~WE: ......W ...
1.8 + PHI OUT: ______________/---------------\ ...
1.9 + CPU: D L ...
1.10 + RnW: R ...
1.11 +
1.12 Here, "A" and "B" respectively indicate the row and first column addresses
1.13 being latched into the RAM (on a negative edge for ~RAS and ~CAS
1.14 respectively), and "C" indicates the second column address being latched into
1.15 @@ -64,6 +69,15 @@
1.16 "S" respectively, and the row and column addresses must be made available at
1.17 "a" and "b" (and "c") respectively at the latest.
1.18
1.19 +For the CPU, "L" indicates the point at which an address is taken from the CPU
1.20 +address bus, on a negative edge of PHI OUT, with "D" being the point at which
1.21 +data may either be read or be asserted for writing, on a positive edge of PHI
1.22 +OUT. Here, PHI OUT is driven at 1MHz. Given that ~WE needs to be driven low
1.23 +for writing or high for reading, and thus propagates RnW from the CPU, this
1.24 +would need to be done before data would be retrieved and, according to the
1.25 +TM4164EC4 datasheet, even as late as the column address is presented and ~CAS
1.26 +brought low.
1.27 +
1.28 The TM4164EC4-15 has a row address access time of 150ns (maximum) and a column
1.29 address access time of 90ns (maximum), which appears to mean that
1.30 approximately two 16MHz cycles after the row address is latched, and one and a
2.1 --- a/ula.py Mon Jun 20 21:32:22 2016 +0200
2.2 +++ b/ula.py Mon Jun 20 23:17:48 2016 +0200
2.3 @@ -112,6 +112,7 @@
2.4 self.row_address = 0
2.5 self.column_address = 0
2.6 self.data = 0
2.7 + self.read_not_write = 1
2.8
2.9 def row_select(self, address):
2.10
2.11 @@ -130,11 +131,20 @@
2.12
2.13 # Read the data.
2.14
2.15 - self.data = self.memory[self.row_address << 8 | self.column_address]
2.16 + if self.read_not_write:
2.17 + self.data = self.memory[self.row_address << 8 | self.column_address]
2.18 + else:
2.19 + self.memory[self.row_address << 8 | self.column_address] = self.data
2.20
2.21 def column_deselect(self):
2.22 pass
2.23
2.24 + def read_select(self):
2.25 + self.read_not_write = 1
2.26 +
2.27 + def write_select(self):
2.28 + self.read_not_write = 0
2.29 +
2.30 # Convenience methods.
2.31
2.32 def fill(self, start, end, value):
2.33 @@ -142,6 +152,14 @@
2.34 self.memory[i << 1] = value >> 4
2.35 self.memory[i << 1 | 0x1] = value & 0xf
2.36
2.37 +class CPU:
2.38 +
2.39 + "A CPU abstraction."
2.40 +
2.41 + def __init__(self):
2.42 + self.address = 0x1000
2.43 + self.read_not_write = 1
2.44 +
2.45 class ULA:
2.46
2.47 """
2.48 @@ -157,10 +175,11 @@
2.49 (320, 1, 25)
2.50 ]
2.51
2.52 - def __init__(self, ram, video):
2.53 + def __init__(self, cpu, ram, video):
2.54
2.55 - "Initialise the ULA with the given 'ram' and 'video' instances."
2.56 + "Initialise the ULA with the given 'cpu', 'ram' and 'video' instances."
2.57
2.58 + self.cpu = cpu
2.59 self.ram = ram
2.60 self.video = video
2.61 self.set_mode(6)
2.62 @@ -176,6 +195,7 @@
2.63
2.64 self.nmi = 0 # no NMI asserted initially
2.65 self.irq_vsync = 0 # no IRQ asserted initially
2.66 + self.cpu_clock = 0 # drive the CPU clock low
2.67
2.68 # Communication.
2.69
2.70 @@ -390,6 +410,13 @@
2.71
2.72 self.ram.column_select(self.ram_address)
2.73
2.74 + # Assert the RAM write enable if appropriate.
2.75 +
2.76 + if access_ram:
2.77 + self.ram.read_select()
2.78 + else:
2.79 + self.cpu_transfer_select()
2.80 +
2.81 # Read 4 bits (for ULA access only).
2.82
2.83 elif self.cycle == 8:
2.84 @@ -448,6 +475,11 @@
2.85
2.86 self.access = (self.access + 1) % self.access_frequency
2.87
2.88 + # Read the CPU address, if appropriate.
2.89 +
2.90 + if not access_ram:
2.91 + self.cpu_update_clock()
2.92 +
2.93
2.94
2.95 # Pixel production.
2.96 @@ -512,10 +544,37 @@
2.97 def cpu_transfer_high(self):
2.98 if self.cpu_read:
2.99 self.cpu_data = self.ram.data << 4
2.100 + else:
2.101 + self.ram.data = self.cpu_data >> 4
2.102
2.103 def cpu_transfer_low(self):
2.104 if self.cpu_read:
2.105 self.cpu_data = self.data | self.ram.data
2.106 + else:
2.107 + self.ram.data = self.cpu_data & 0b00001111
2.108 +
2.109 + def cpu_read_address(self):
2.110 + self.cpu_address = self.cpu.address
2.111 +
2.112 + def cpu_transfer_data(self):
2.113 + if self.cpu_read:
2.114 + self.cpu.data = self.cpu_data
2.115 + else:
2.116 + self.cpu_data = self.cpu.data
2.117 +
2.118 + def cpu_update_clock(self):
2.119 + self.cpu_clock = not self.cpu_clock
2.120 + if self.cpu_clock:
2.121 + self.cpu_transfer_data()
2.122 + else:
2.123 + self.cpu_read_address()
2.124 +
2.125 + def cpu_transfer_select(self):
2.126 + self.cpu_read = self.cpu.read_not_write
2.127 + if self.cpu_read:
2.128 + self.ram.read_select()
2.129 + else:
2.130 + self.ram.write_select()
2.131
2.132 def rotate(value, depth, width=8, zero=False):
2.133
2.134 @@ -595,7 +654,7 @@
2.135
2.136 "Return a ULA initialised with a memory array and video."
2.137
2.138 - return ULA(get_ram(), get_video())
2.139 + return ULA(get_cpu(), get_ram(), get_video())
2.140
2.141 def get_video():
2.142
2.143 @@ -609,6 +668,12 @@
2.144
2.145 return RAM()
2.146
2.147 +def get_cpu():
2.148 +
2.149 + "Return an instance representing the CPU."
2.150 +
2.151 + return CPU()
2.152 +
2.153 # Test program providing coverage (necessary for compilers like Shedskin).
2.154
2.155 if __name__ == "__main__":