# HG changeset patch # User Paul Boddie # Date 1466457468 -7200 # Node ID 5101ef5b3e3a7c7694d6005bbe22e1765095e57f # Parent c0f8098f982fa6a13e631c4cd8c8c1ad659e7b62 Added initial CPU abstraction support together with read/write selection and PHI OUT clock signal generation. diff -r c0f8098f982f -r 5101ef5b3e3a ULA.txt --- a/ULA.txt Mon Jun 20 21:32:22 2016 +0200 +++ b/ULA.txt Mon Jun 20 23:17:48 2016 +0200 @@ -57,6 +57,11 @@ F S F S ... a b c a b c ... + ~WE: ......W ... + PHI OUT: ______________/---------------\ ... + CPU: D L ... + RnW: R ... + Here, "A" and "B" respectively indicate the row and first column addresses being latched into the RAM (on a negative edge for ~RAS and ~CAS respectively), and "C" indicates the second column address being latched into @@ -64,6 +69,15 @@ "S" respectively, and the row and column addresses must be made available at "a" and "b" (and "c") respectively at the latest. +For the CPU, "L" indicates the point at which an address is taken from the CPU +address bus, on a negative edge of PHI OUT, with "D" being the point at which +data may either be read or be asserted for writing, on a positive edge of PHI +OUT. Here, PHI OUT is driven at 1MHz. Given that ~WE needs to be driven low +for writing or high for reading, and thus propagates RnW from the CPU, this +would need to be done before data would be retrieved and, according to the +TM4164EC4 datasheet, even as late as the column address is presented and ~CAS +brought low. + The TM4164EC4-15 has a row address access time of 150ns (maximum) and a column address access time of 90ns (maximum), which appears to mean that approximately two 16MHz cycles after the row address is latched, and one and a diff -r c0f8098f982f -r 5101ef5b3e3a ula.py --- a/ula.py Mon Jun 20 21:32:22 2016 +0200 +++ b/ula.py Mon Jun 20 23:17:48 2016 +0200 @@ -112,6 +112,7 @@ self.row_address = 0 self.column_address = 0 self.data = 0 + self.read_not_write = 1 def row_select(self, address): @@ -130,11 +131,20 @@ # Read the data. - self.data = self.memory[self.row_address << 8 | self.column_address] + if self.read_not_write: + self.data = self.memory[self.row_address << 8 | self.column_address] + else: + self.memory[self.row_address << 8 | self.column_address] = self.data def column_deselect(self): pass + def read_select(self): + self.read_not_write = 1 + + def write_select(self): + self.read_not_write = 0 + # Convenience methods. def fill(self, start, end, value): @@ -142,6 +152,14 @@ self.memory[i << 1] = value >> 4 self.memory[i << 1 | 0x1] = value & 0xf +class CPU: + + "A CPU abstraction." + + def __init__(self): + self.address = 0x1000 + self.read_not_write = 1 + class ULA: """ @@ -157,10 +175,11 @@ (320, 1, 25) ] - def __init__(self, ram, video): + def __init__(self, cpu, ram, video): - "Initialise the ULA with the given 'ram' and 'video' instances." + "Initialise the ULA with the given 'cpu', 'ram' and 'video' instances." + self.cpu = cpu self.ram = ram self.video = video self.set_mode(6) @@ -176,6 +195,7 @@ self.nmi = 0 # no NMI asserted initially self.irq_vsync = 0 # no IRQ asserted initially + self.cpu_clock = 0 # drive the CPU clock low # Communication. @@ -390,6 +410,13 @@ self.ram.column_select(self.ram_address) + # Assert the RAM write enable if appropriate. + + if access_ram: + self.ram.read_select() + else: + self.cpu_transfer_select() + # Read 4 bits (for ULA access only). elif self.cycle == 8: @@ -448,6 +475,11 @@ self.access = (self.access + 1) % self.access_frequency + # Read the CPU address, if appropriate. + + if not access_ram: + self.cpu_update_clock() + # Pixel production. @@ -512,10 +544,37 @@ def cpu_transfer_high(self): if self.cpu_read: self.cpu_data = self.ram.data << 4 + else: + self.ram.data = self.cpu_data >> 4 def cpu_transfer_low(self): if self.cpu_read: self.cpu_data = self.data | self.ram.data + else: + self.ram.data = self.cpu_data & 0b00001111 + + def cpu_read_address(self): + self.cpu_address = self.cpu.address + + def cpu_transfer_data(self): + if self.cpu_read: + self.cpu.data = self.cpu_data + else: + self.cpu_data = self.cpu.data + + def cpu_update_clock(self): + self.cpu_clock = not self.cpu_clock + if self.cpu_clock: + self.cpu_transfer_data() + else: + self.cpu_read_address() + + def cpu_transfer_select(self): + self.cpu_read = self.cpu.read_not_write + if self.cpu_read: + self.ram.read_select() + else: + self.ram.write_select() def rotate(value, depth, width=8, zero=False): @@ -595,7 +654,7 @@ "Return a ULA initialised with a memory array and video." - return ULA(get_ram(), get_video()) + return ULA(get_cpu(), get_ram(), get_video()) def get_video(): @@ -609,6 +668,12 @@ return RAM() +def get_cpu(): + + "Return an instance representing the CPU." + + return CPU() + # Test program providing coverage (necessary for compilers like Shedskin). if __name__ == "__main__":