1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/ula.py Sun Dec 04 01:17:40 2011 +0100
1.3 @@ -0,0 +1,205 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +Acorn Electron ULA simulation.
1.8 +"""
1.9 +
1.10 +import pygame
1.11 +import array
1.12 +import itertools
1.13 +
1.14 +WIDTH = 640
1.15 +HEIGHT = 512
1.16 +
1.17 +LINES_PER_ROW = 8
1.18 +MAX_HEIGHT = 256
1.19 +SCREEN_LIMIT = 0x8000
1.20 +MAX_MEMORY = 0x10000
1.21 +INTENSITY = 255
1.22 +
1.23 +palette = range(0, 8) * 2
1.24 +
1.25 +def get_mode((width, depth, rows)):
1.26 +
1.27 + """
1.28 + Return the 'width', 'depth', 'rows', calculated character row size in
1.29 + bytes, screen size in bytes, horizontal pixel scaling, vertical pixel
1.30 + spacing, and line spacing between character rows, as elements of a tuple for
1.31 + a particular screen mode.
1.32 + """
1.33 +
1.34 + return (width, depth, rows,
1.35 + (width * depth * LINES_PER_ROW) / 8, # bits per row -> bytes per row
1.36 + (width * depth * MAX_HEIGHT) / 8, # bits per screen -> bytes per screen
1.37 + WIDTH / width, # pixel width in display pixels
1.38 + HEIGHT / (rows * LINES_PER_ROW), # pixel height in display pixels
1.39 + MAX_HEIGHT / rows - LINES_PER_ROW) # pixels between rows
1.40 +
1.41 +modes = map(get_mode, [
1.42 + (640, 1, 32), (320, 2, 32), (160, 4, 32), # (width, depth, rows)
1.43 + (640, 1, 24), (320, 1, 32), (160, 2, 32),
1.44 + (320, 1, 24)
1.45 + ])
1.46 +
1.47 +def update(screen, memory, start, mode):
1.48 +
1.49 + """
1.50 + Update the 'screen' array by reading from 'memory' at the given 'start'
1.51 + address for the given 'mode'.
1.52 + """
1.53 +
1.54 + # Get the width in pixels, colour depth in bits per pixel, number of
1.55 + # character rows, character row size in bytes, screen size in bytes,
1.56 + # horizontal pixel scaling factor, vertical pixel scaling factor, and line
1.57 + # spacing in pixels.
1.58 +
1.59 + width, depth, rows, row_size, screen_size, xscale, yscale, spacing = modes[mode]
1.60 +
1.61 + address = start
1.62 + y = 0
1.63 + row = 0
1.64 +
1.65 + while row < rows:
1.66 + row_start = address
1.67 + line = 0
1.68 +
1.69 + # Emit each character row.
1.70 +
1.71 + while line < LINES_PER_ROW:
1.72 + line_start = address
1.73 + ysub = 0
1.74 +
1.75 + # Scale each row of pixels vertically.
1.76 +
1.77 + while ysub < yscale:
1.78 + x = 0
1.79 +
1.80 + # Emit each row of pixels.
1.81 +
1.82 + while x < WIDTH:
1.83 + byte_value = memory[address]
1.84 +
1.85 + for colour in decode(byte_value, depth):
1.86 + colour = get_physical_colour(palette[colour])
1.87 + pixel = tuple(map(lambda x: x * INTENSITY, colour))
1.88 +
1.89 + # Scale the pixels horizontally.
1.90 +
1.91 + xsub = 0
1.92 + while xsub < xscale:
1.93 + screen[x][y] = pixel
1.94 + xsub += 1
1.95 + x += 1
1.96 +
1.97 + # Advance to the next column.
1.98 +
1.99 + address += LINES_PER_ROW
1.100 + address = wrap_address(address, screen_size)
1.101 +
1.102 + ysub += 1
1.103 + y += 1
1.104 +
1.105 + # Return to the address at the start of the line in order to be
1.106 + # able to repeat the line.
1.107 +
1.108 + address = line_start
1.109 +
1.110 + # Move on to the next line in the row.
1.111 +
1.112 + line += 1
1.113 + address += 1
1.114 +
1.115 + # Skip spacing between rows.
1.116 +
1.117 + if spacing:
1.118 + y += spacing * yscale
1.119 +
1.120 + # Move on to the next row.
1.121 +
1.122 + address = row_start + row_size
1.123 + address = wrap_address(address, screen_size)
1.124 +
1.125 + row += 1
1.126 +
1.127 +def wrap_address(address, screen_size):
1.128 + if address >= SCREEN_LIMIT:
1.129 + address -= screen_size
1.130 + return address
1.131 +
1.132 +def get_physical_colour(value):
1.133 +
1.134 + """
1.135 + Return the physical colour as an RGB triple for the given 'value'.
1.136 + """
1.137 +
1.138 + return value & 1, value >> 1 & 1, value >> 2 & 1
1.139 +
1.140 +def decode(value, depth):
1.141 +
1.142 + """
1.143 + Decode the given byte 'value' according to the 'depth' in bits per pixel,
1.144 + returning a sequence of pixel values.
1.145 + """
1.146 +
1.147 + if depth == 1:
1.148 + return (value >> 7, value >> 6 & 1, value >> 5 & 1, value >> 4 & 1,
1.149 + value >> 3 & 1, value >> 2 & 1, value >> 1 & 1, value & 1)
1.150 + elif depth == 2:
1.151 + return (value >> 6 & 2 | value >> 3 & 1, value >> 5 & 2 | value >> 2 & 1,
1.152 + value >> 4 & 2 | value >> 1 & 1, value >> 3 & 2 | value & 1)
1.153 + elif depth == 4:
1.154 + return (value >> 4 & 8 | value >> 3 & 4 | value >> 2 & 2 | value >> 1 & 1,
1.155 + value >> 3 & 8 | value >> 2 & 4 | value >> 1 & 2 | value & 1)
1.156 + else:
1.157 + raise ValueError, "Only depths of 1, 2 and 4 are supported, not %d." % depth
1.158 +
1.159 +# Convenience functions.
1.160 +
1.161 +def encode(values, depth):
1.162 +
1.163 + """
1.164 + Encode the given 'values' according to the 'depth' in bits per pixel,
1.165 + returning a byte value for the pixels.
1.166 + """
1.167 +
1.168 + result = 0
1.169 +
1.170 + if depth == 1:
1.171 + for value in values:
1.172 + result = result << 1 | (value & 1)
1.173 + elif depth == 2:
1.174 + for value in values:
1.175 + result = result << 1 | (value & 2) << 3 | (value & 1)
1.176 + elif depth == 4:
1.177 + for value in values:
1.178 + result = result << 1 | (value & 8) << 3 | (value & 4) << 2 | (value & 2) << 1 | (value & 1)
1.179 + else:
1.180 + raise ValueError, "Only depths of 1, 2 and 4 are supported, not %d." % depth
1.181 +
1.182 + return result
1.183 +
1.184 +def fill(memory, start, end, value):
1.185 + for i in xrange(start, end):
1.186 + memory[i] = value
1.187 +
1.188 +def mainloop():
1.189 + while 1:
1.190 + pygame.display.flip()
1.191 + event = pygame.event.wait()
1.192 + if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
1.193 + break
1.194 +
1.195 +if __name__ == "__main__":
1.196 + pygame.init()
1.197 + screen = pygame.display.set_mode((WIDTH, HEIGHT), 0)
1.198 +
1.199 + memory = array.array("B", itertools.repeat(0, MAX_MEMORY))
1.200 + a = pygame.surfarray.pixels3d(screen)
1.201 +
1.202 + # Test MODE 2.
1.203 +
1.204 + fill(memory, 0x3000, 0x8000, encode((1, 6), 4))
1.205 + update(a, memory, 0x3000, 2)
1.206 + mainloop()
1.207 +
1.208 +# vim: tabstop=4 expandtab shiftwidth=4