# HG changeset patch # User Paul Boddie # Date 1466434632 -7200 # Node ID ea19ad85988b2cea60688ffaaf549202edc823fe # Parent ac3b43bc9447991661434699ebbf0f9f3e8e7d27 Fixed and tidied up pixel production, employing the general state counter for pixel output and a specific state counter to track pixel scaling. Replaced the shift register abstraction with plain integer use. Made the rotate function more general. diff -r ac3b43bc9447 -r ea19ad85988b ula.py --- a/ula.py Mon Jun 20 15:40:58 2016 +0200 +++ b/ula.py Mon Jun 20 16:57:12 2016 +0200 @@ -144,33 +144,6 @@ self.memory[i << 1] = value >> 4 self.memory[i << 1 | 0x1] = value & 0xf -class ShiftRegister: - - """ - A class representing a shift register, used for the internal state of the - ULA within each 2MHz period. - """ - - def __init__(self): - self.state = [0] * 8 - self.input = 0 - - def set_input(self, input): - self.input = input - - def shift(self): - - # NOTE: This is not meant to be "nice" Python, but instead models the - # NOTE: propagation of state through the latches. - - self.state[0], self.state[1], self.state[2], self.state[3], \ - self.state[4], self.state[5], self.state[6], self.state[7] = \ - self.input, self.state[0], self.state[1], self.state[2], \ - self.state[3], self.state[4], self.state[5], self.state[6] - - def __getitem__(self, i): - return self.state[i] - class ULA: """ @@ -217,14 +190,9 @@ self.access = 0 # counter used to determine whether a byte needs reading self.have_pixels = 0 # whether pixel data has been read - self.writing_pixels = 0 # whether pixel data can be written - self.pdata = [0]*8 # decoded RAM data for pixel output - - self.cycle = ShiftRegister() # 8-state counter within each 2MHz period - - self.cycle.set_input(1) # assert the input to set the first state output - self.cycle.shift() - self.cycle.set_input(0) # reset the input since only one state output will be active + self.pdata = 0 # decoded RAM data for pixel output + self.cycle = 1 # 8-state counter within each 2MHz period + self.pcycle = 0 # 8/4/2-state pixel output counter self.next_frame() @@ -336,6 +304,8 @@ def in_frame(self): return MIN_PIXELLINE <= self.y < (MIN_PIXELLINE + self.display_height) def inside_frame(self): return MIN_PIXELLINE < self.y < (MIN_PIXELLINE + self.display_height) def read_pixels(self): return MIN_PIXELPOS <= self.x < MAX_PIXELPOS and self.in_frame() + def end_pixels(self): return self.pcycle == 1 + def next_pixel(self): return self.xscale == 1 or (self.xscale == 2 and self.cycle & 0b10101010) or (self.xscale == 4 and self.cycle & 0b10001000) def update(self): @@ -363,7 +333,7 @@ # Set row address (for ULA access only). - if self.cycle[0]: + if self.cycle == 1: # Either assert a required address or propagate the CPU address. @@ -372,15 +342,9 @@ else: self.init_row_address(self.cpu_address) - # Initialise the pixel buffer if appropriate. - - if self.have_pixels: - self.pdata = decode(self.data, self.depth) - self.writing_pixels = 1 - # Latch row address, set column address (for ULA access only). - elif self.cycle[1]: + elif self.cycle == 2: # Select an address needed by the ULA or CPU. @@ -395,16 +359,15 @@ # Latch column address. - elif self.cycle[2]: + elif self.cycle == 4: # Select an address needed by the ULA or CPU. self.ram.column_select(self.ram_address) # Read 4 bits (for ULA access only). - # NOTE: Perhaps map alternate bits, not half-bytes. - elif self.cycle[3]: + elif self.cycle == 8: # Either read from a required address or transfer CPU data. @@ -415,7 +378,7 @@ # Set column address (for ULA access only). - elif self.cycle[4]: + elif self.cycle == 16: self.ram.column_deselect() # Either assert a required address or propagate the CPU address. @@ -427,16 +390,15 @@ # Latch column address. - elif self.cycle[5]: + elif self.cycle == 32: # Select an address needed by the ULA or CPU. self.ram.column_select(self.ram_address) # Read 4 bits (for ULA access only). - # NOTE: Perhaps map alternate bits, not half-bytes. - elif self.cycle[6]: + elif self.cycle == 64: # Either read from a required address or transfer CPU data. @@ -453,7 +415,7 @@ # Reset addresses. - elif self.cycle[7]: + elif self.cycle == 128: self.ram.column_deselect() self.ram.row_deselect() @@ -461,10 +423,12 @@ self.access = (self.access + 1) % self.access_frequency - # Update the state of the device. + # Initialise the pixel buffer if appropriate. - self.cycle.set_input(self.cycle[7]) - self.cycle.shift() + if self.have_pixels: + self.pdata = decode(self.data, self.depth) + self.pcycle = 1 + self.have_pixels = 0 @@ -491,14 +455,19 @@ self.vsync(1) + + # Update the state of the device. + + self.cycle = rotate(self.cycle, 1) self.x += 1 + # Pixel production. # Detect spacing between character rows. - if not self.writing_pixels or not self.in_line(): + if not self.pcycle: self.video.colour = BLANK # For pixels within the frame, obtain and output the value. @@ -509,13 +478,16 @@ # Scale pixels horizontally, only accessing the next pixel value # after the required number of scan positions. - if self.x % self.xscale == 0: + if self.next_pixel(): self.next_pixel_value() + self.pcycle = rotate(self.pcycle, self.depth) - # Finish writing pixels. + # Finish writing pixels. - if self.x % PIXEL_POSITIONS == 0: - self.writing_pixels = 0 + if self.end_pixels(): + self.pcycle = 0 + + def output_colour_value(self): @@ -548,13 +520,16 @@ if self.cpu_read: self.cpu_data = self.data | self.ram.data -def rotate(value, depth): - - "Return 'value' rotated by the number of bits given by 'depth'." +def rotate(value, depth, width=8): - field = 8 - depth + """ + Return 'value' rotated by the number of bits given by 'depth' within a + storage 'width' given in bits. + """ + + field = width - depth top = value >> field - mask = 2 ** (8 - depth) - 1 + mask = 2 ** (width - depth) - 1 rest = value & mask return (rest << depth) | top