# HG changeset patch # User Paul Boddie # Date 1323728879 -3600 # Node ID 1687504dab558ab29ba5a60312fdf8d1a815e585 # Parent c3c47581db46f648f56f0b2d18215a320de194b4# Parent c82fef2dd5622183514b592fdf63d69d22b8ad6d Merged general changes. diff -r c3c47581db46 -r 1687504dab55 ULA.txt --- a/ULA.txt Sun Dec 11 01:18:22 2011 +0100 +++ b/ULA.txt Mon Dec 12 23:27:59 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 c3c47581db46 -r 1687504dab55 main.py --- a/main.py Sun Dec 11 01:18:22 2011 +0100 +++ b/main.py Mon Dec 12 23:27:59 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 c3c47581db46 -r 1687504dab55 ula.py --- a/ula.py Sun Dec 11 01:18:22 2011 +0100 +++ b/ula.py Mon Dec 12 23:27:59 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 = ULA.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