# HG changeset patch # User Paul Boddie # Date 1323649911 -3600 # Node ID c82fef2dd5622183514b592fdf63d69d22b8ad6d # Parent 42d9fec68cdd7ba8c8231590b90af61e99befa63 Moved the vertical scaling into the main program, ensuring that the ULA only processes 256 scanlines per frame, although it needs to be made aware of 312 scanlines per frame. Added timing information to the notes. diff -r 42d9fec68cdd -r c82fef2dd562 ULA.txt --- a/ULA.txt Sun Dec 11 01:11:35 2011 +0100 +++ b/ULA.txt Mon Dec 12 01:31:51 2011 +0100 @@ -1,3 +1,16 @@ +Timing +------ + +According to the above (15.3.2 in the AUG), there are 312 scanlines, 256 of +which are used to generate pixel data. At 50Hz, this means that 128 cycles are +used to produce pixel data (2000000 / 50 = 40000; 40000 / 312 ~= 128). This is +consistent with the observation that each scanline requires at most 80 bytes +of data, and that the ULA is apparently busy for 40 out of 64 microseconds in +each scanline. + +See: The Advanced User Guide for the Acorn Electron +See: http://mdfs.net/Docs/Comp/Electron/Techinfo.htm + Hardware Scrolling ------------------ diff -r 42d9fec68cdd -r c82fef2dd562 main.py --- a/main.py Sun Dec 11 01:11:35 2011 +0100 +++ b/main.py Mon Dec 12 01:31:51 2011 +0100 @@ -7,9 +7,32 @@ from ula import * import pygame -def update_surface(surface_array, screen_array): - surface_array[:] = screen_array - surface_array *= INTENSITY +WIDTH = 640 +HEIGHT = 512 +INTENSITY = 255 + +def update_screen(screen, screen_array): + + """ + Update the host's 'screen' surface with the contents of the 'screen_array' + containing the pixel content of the screen. + """ + + surface = pygame.Surface((WIDTH, MAX_HEIGHT), 0, screen) + a = pygame.surfarray.pixels3d(surface) + + # Copy the array to a surface and apply a pixel intensity. + + try: + a[:] = screen_array + a *= INTENSITY + finally: + del a + + # Scale the surface to the dimensions of the host's screen and copy the + # result to the host's screen. + + screen.blit(pygame.transform.scale(surface, (WIDTH, HEIGHT)), (0, 0)) def mainloop(): while 1: @@ -21,7 +44,6 @@ if __name__ == "__main__": pygame.init() screen = pygame.display.set_mode((WIDTH, HEIGHT), 0) - a = pygame.surfarray.pixels3d(screen) ula = get_ula() ula_screen = get_screen() @@ -33,12 +55,12 @@ ula.fill(0x3000, 0x5800 - 320, encode((1, 6), 4)) ula.fill(0x5800 - 320, 0x8000, encode((2, 7), 4)) update(ula_screen, ula) - update_surface(a, ula_screen) + update_screen(screen, ula_screen) mainloop() ula.screen_start = 0x3000 + 2 update(ula_screen, ula) - update_surface(a, ula_screen) + update_screen(screen, ula_screen) mainloop() # Test MODE 6. @@ -48,12 +70,12 @@ ula.fill(0x6000, 0x6f00 + 160, encode((1, 0, 1, 1, 0, 0, 1, 1), 1)) ula.fill(0x6f00 + 160, 0x7f40, encode((1, 0, 1, 0, 1, 0, 1, 0), 1)) update(ula_screen, ula) - update_surface(a, ula_screen) + update_screen(screen, ula_screen) mainloop() ula.screen_start = 0x6f00 + 160 update(ula_screen, ula) - update_surface(a, ula_screen) + update_screen(screen, ula_screen) mainloop() # vim: tabstop=4 expandtab shiftwidth=4 diff -r 42d9fec68cdd -r c82fef2dd562 ula.py --- a/ula.py Sun Dec 11 01:11:35 2011 +0100 +++ b/ula.py Mon Dec 12 01:31:51 2011 +0100 @@ -4,29 +4,31 @@ Acorn Electron ULA simulation. """ -WIDTH = 640 -HEIGHT = 512 -INTENSITY = 255 - -LINES_PER_ROW = 8 -MAX_HEIGHT = 256 -SCREEN_LIMIT = 0x8000 -MAX_MEMORY = 0x10000 +LINES_PER_ROW = 8 # the number of pixel lines per character row +MAX_HEIGHT = 256 # the height of the screen in pixels +MAX_SCANLINE = 312 # the number of scanlines in each frame +MAX_WIDTH = 640 # the width of the screen in pixels +MAX_SCANPOS = 1024 # the number of positions in each scanline +SCREEN_LIMIT = 0x8000 # the first address after the screen memory +MAX_MEMORY = 0x10000 # the number of addressable memory locations BLANK = (0, 0, 0) def update(screen, ula): """ - Update the 'screen' array by reading from the 'ula'. + Update the 'screen' array by reading from the 'ula'. This function + effectively has the role of the video circuit, but also provides the clock + signal to the ULA. """ ula.vsync() y = 0 - while y < HEIGHT: + while y < MAX_SCANLINE: x = 0 - while x < WIDTH: + while x < MAX_SCANPOS: colour = ula.get_pixel_colour() - screen[x][y] = colour + if x < MAX_WIDTH and y < MAX_HEIGHT: + screen[x][y] = colour x += 1 ula.hsync() y += 1 @@ -66,14 +68,14 @@ * screen size in bytes * default screen start address * horizontal pixel scaling factor - * vertical pixel scaling factor * line spacing in pixels * number of entries in the pixel buffer """ self.width, self.depth, rows = self.modes[mode] - row_size = (self.width * self.depth * LINES_PER_ROW) / 8 # bits per row -> bytes per row + self.columns = (self.width * self.depth) / 8 # bits read -> bytes read + row_size = self.columns * LINES_PER_ROW # Memory access configuration. # Note the limitation on positioning the screen start. @@ -84,15 +86,13 @@ # Scanline configuration. - self.xscale = WIDTH / self.width # pixel width in display pixels - self.yscale = HEIGHT / (rows * LINES_PER_ROW) # pixel height in display pixels - + self.xscale = MAX_WIDTH / self.width # pixel width in display pixels self.spacing = MAX_HEIGHT / rows - LINES_PER_ROW # pixels between rows # Start of unused region. self.footer = rows * LINES_PER_ROW - self.margin = MAX_HEIGHT - rows * (LINES_PER_ROW + self.spacing) + self.spacing + self.margin = MAX_SCANLINE - rows * (LINES_PER_ROW + self.spacing) + self.spacing # Internal pixel buffer size. @@ -104,7 +104,6 @@ self.line_start = self.address = self.screen_start self.line = self.line_start % LINES_PER_ROW - self.ysub = 0 self.ssub = 0 self.reset_horizontal() @@ -113,6 +112,7 @@ "Reset horizontal state." self.xsub = 0 + self.column = 0 self.buffer_index = self.buffer_limit # need refill def hsync(self): @@ -126,20 +126,6 @@ return self.reset_horizontal() - - # Scale pixels vertically. - - self.ysub += 1 - - # Re-read the current line if appropriate. - - if self.ysub < self.yscale: - self.address = self.line_start - return - - # Otherwise, move on to the next line. - - self.ysub = 0 self.line += 1 # If not on a row boundary, move to the next line. @@ -158,12 +144,12 @@ # Test for the footer region. if self.spacing and self.line == self.footer: - self.ssub = self.margin * self.yscale + self.ssub = self.margin return # Support spacing between character rows. - self.ssub = self.spacing * self.yscale + self.ssub = self.spacing self.line_start = self.address @@ -186,6 +172,13 @@ if self.buffer_index == self.buffer_limit: self.buffer_index = 0 + self.column += 1 + + # Detect the end of the scanline. + + if self.column > self.columns: + return BLANK + self.fill_pixel_buffer() self.xsub += 1 @@ -289,8 +282,8 @@ x = 0 screen = [] - while x < WIDTH: - screen.append([(0, 0, 0)] * HEIGHT) + while x < MAX_WIDTH: + screen.append([(0, 0, 0)] * MAX_HEIGHT) x += 1 return screen