1.1 --- a/ULA.txt Sun Dec 18 19:29:20 2011 +0100
1.2 +++ b/ULA.txt Tue Dec 20 00:56:33 2011 +0100
1.3 @@ -8,7 +8,7 @@
1.4 of data, and that the ULA is apparently busy for 40 out of 64 microseconds in
1.5 each scanline.
1.6
1.7 -See: The Advanced User Guide for the Acorn Electron
1.8 +See: Acorn Electron Advanced User Guide
1.9 See: http://mdfs.net/Docs/Comp/Electron/Techinfo.htm
1.10
1.11 Hardware Scrolling
1.12 @@ -338,3 +338,75 @@
1.13 control over the palette (using address &FE21, compared to &FE07-F on the
1.14 Electron) and other system-specific functions. Since the location usage is
1.15 generally incompatible, this region could be reused for other purposes.
1.16 +
1.17 +ULA Pin Functions
1.18 +-----------------
1.19 +
1.20 +The functions of the ULA pins are described in the Electron Service Manual. Of
1.21 +interest to video processing are the following:
1.22 +
1.23 + CSYNC (low during horizontal or vertical synchronisation periods, high
1.24 + otherwise)
1.25 +
1.26 + HS (low during horizontal synchronisation periods, high otherwise)
1.27 +
1.28 + RED, GREEN, BLUE (pixel colour outputs)
1.29 +
1.30 + CLOCK IN (a 16MHz clock input, 4V peak to peak)
1.31 +
1.32 + PHI OUT (a 1MHz, 2MHz and stopped clock signal for the CPU)
1.33 +
1.34 +More general memory access pins:
1.35 +
1.36 + RAM0...RAM3 (data lines to/from the RAM)
1.37 +
1.38 + RA0...RA7 (address lines for sending both row and column addresses to the RAM)
1.39 +
1.40 + RAS (row address strobe setting the row address on a negative edge)
1.41 +
1.42 + CAS (column address strobe setting the column address on a negative edge)
1.43 +
1.44 + WE (sets write enable with logic 0, read with logic 1)
1.45 +
1.46 + ROM (select data access from ROM)
1.47 +
1.48 +CPU-oriented memory access pins:
1.49 +
1.50 + A0...A15 (CPU address lines)
1.51 +
1.52 + PD0...PD7 (CPU data lines)
1.53 +
1.54 + R/W (indicates CPU write with logic 0, CPU read with logic 1)
1.55 +
1.56 +Interrupt-related pins:
1.57 +
1.58 + NMI (CPU request for uninterrupted 1MHz access to memory)
1.59 +
1.60 + IRQ (signal event to CPU)
1.61 +
1.62 + POR (power-on reset, resetting the ULA on a positive edge and asserting the
1.63 + CPU's RST pin)
1.64 +
1.65 + RST (master reset for the CPU signalled on power-up and by the Break key)
1.66 +
1.67 +Keyboard-related pins:
1.68 +
1.69 + KBD0...KBD3 (keyboard inputs)
1.70 +
1.71 + CAPS LOCK (control status LED)
1.72 +
1.73 +Sound-related pins:
1.74 +
1.75 + SOUND O/P (sound output using internal oscillator)
1.76 +
1.77 +Cassette-related pins:
1.78 +
1.79 + CAS IN (cassette circuit input, between 0.5V to 2V peak to peak)
1.80 +
1.81 + CAS OUT (pseudo-sinusoidal output, 1.8V peak to peak)
1.82 +
1.83 + CAS RC (detect high tone)
1.84 +
1.85 + CAS MO (motor relay output)
1.86 +
1.87 + ÷13 IN (~1200 baud clock input)
2.1 --- a/main.py Sun Dec 18 19:29:20 2011 +0100
2.2 +++ b/main.py Tue Dec 20 00:56:33 2011 +0100
2.3 @@ -50,7 +50,7 @@
2.4
2.5 # Test MODE 2.
2.6
2.7 - ula.set_mode(2)
2.8 + ula.set_mode(2); ula.reset()
2.9
2.10 ula.fill(0x3000, 0x5800 - 320, encode((1, 6), 4))
2.11 ula.fill(0x5800 - 320, 0x8000, encode((2, 7), 4))
2.12 @@ -65,7 +65,7 @@
2.13
2.14 # Test MODE 6.
2.15
2.16 - ula.set_mode(6)
2.17 + ula.set_mode(6); ula.reset()
2.18
2.19 ula.fill(0x6000, 0x6f00 + 160, encode((1, 0, 1, 1, 0, 0, 1, 1), 1))
2.20 ula.fill(0x6f00 + 160, 0x7f40, encode((1, 0, 1, 0, 1, 0, 1, 0), 1))
3.1 --- a/ula.py Sun Dec 18 19:29:20 2011 +0100
3.2 +++ b/ula.py Tue Dec 20 00:56:33 2011 +0100
3.3 @@ -16,36 +16,55 @@
3.4 MAX_MEMORY = 0x10000 # the number of addressable memory locations
3.5 BLANK = (0, 0, 0)
3.6
3.7 -screen = array("B", repeat(0, MAX_WIDTH * 3 * MAX_HEIGHT))
3.8 -
3.9 def update(ula):
3.10
3.11 """
3.12 - Return a screen array by reading from the 'ula'. This function effectively
3.13 - has the role of the video circuit, but also provides the clock signal to the
3.14 - ULA.
3.15 + Update the 'ula' for one frame. Return the resulting screen.
3.16 + """
3.17 +
3.18 + video = ula.video
3.19 +
3.20 + i = 0
3.21 + limit = MAX_SCANLINE * MAX_SCANPOS
3.22 + while i < limit:
3.23 + ula.update()
3.24 + video.update()
3.25 + i += 1
3.26 + return video.screen
3.27 +
3.28 +class Video:
3.29 +
3.30 + """
3.31 + A class representing the video circuitry.
3.32 """
3.33
3.34 - ula.vsync()
3.35 - pos = 0
3.36 - y = 0
3.37 - while y < MAX_SCANLINE:
3.38 - x = 0
3.39 - while x < MAX_SCANPOS:
3.40 - colour = ula.get_pixel_colour()
3.41 - if x < MAX_WIDTH and y < MAX_HEIGHT:
3.42 - screen[pos] = colour[0]; pos += 1
3.43 - screen[pos] = colour[1]; pos += 1
3.44 - screen[pos] = colour[2]; pos += 1
3.45 - x += 1
3.46 - ula.hsync()
3.47 - y += 1
3.48 + def __init__(self):
3.49 + self.screen = array("B", repeat(0, MAX_WIDTH * 3 * MAX_HEIGHT))
3.50 + self.colour = BLANK
3.51 + self.csync = 1
3.52 + self.hs = 1
3.53 + self.reset()
3.54
3.55 - return screen
3.56 + def reset(self):
3.57 + self.pos = 0
3.58 +
3.59 + def update(self):
3.60 + if self.csync:
3.61 + if self.hs:
3.62 + self.screen[self.pos] = self.colour[0]; self.pos += 1
3.63 + self.screen[self.pos] = self.colour[1]; self.pos += 1
3.64 + self.screen[self.pos] = self.colour[2]; self.pos += 1
3.65 + else:
3.66 + self.pos = 0
3.67
3.68 class ULA:
3.69
3.70 - "The ULA functionality."
3.71 + """
3.72 + A class providing the ULA functionality. Instances of this class refer to
3.73 + the system memory, maintain internal state (such as information about the
3.74 + current screen mode), and provide outputs (such as the current pixel
3.75 + colour).
3.76 + """
3.77
3.78 modes = [
3.79 (640, 1, 32), (320, 2, 32), (160, 4, 32), # (width, depth, rows)
3.80 @@ -55,17 +74,26 @@
3.81
3.82 palette = range(0, 8) * 2
3.83
3.84 - def __init__(self, memory):
3.85 + def __init__(self, memory, video):
3.86
3.87 - "Initialise the ULA with the given 'memory'."
3.88 + "Initialise the ULA with the given 'memory' and 'video'."
3.89
3.90 self.memory = memory
3.91 + self.video = video
3.92 self.set_mode(6)
3.93
3.94 # Internal state.
3.95
3.96 self.buffer = [0] * 8
3.97
3.98 + self.reset()
3.99 +
3.100 + def reset(self):
3.101 +
3.102 + "Reset the ULA."
3.103 +
3.104 + self.vsync()
3.105 +
3.106 def set_mode(self, mode):
3.107
3.108 """
3.109 @@ -80,12 +108,15 @@
3.110 * horizontal pixel scaling factor
3.111 * line spacing in pixels
3.112 * number of entries in the pixel buffer
3.113 +
3.114 + The ULA should be reset after a mode switch in order to cleanly display
3.115 + a full screen.
3.116 """
3.117
3.118 self.width, self.depth, rows = self.modes[mode]
3.119
3.120 - self.columns = (self.width * self.depth) / 8 # bits read -> bytes read
3.121 - row_size = self.columns * LINES_PER_ROW
3.122 + columns = (self.width * self.depth) / 8 # bits read -> bytes read
3.123 + row_size = columns * LINES_PER_ROW
3.124
3.125 # Memory access configuration.
3.126 # Note the limitation on positioning the screen start.
3.127 @@ -115,19 +146,19 @@
3.128 self.line_start = self.address = self.screen_start
3.129 self.line = self.line_start % LINES_PER_ROW
3.130 self.ssub = 0
3.131 + self.y = 0
3.132 self.reset_horizontal()
3.133
3.134 - def reset_horizontal(self):
3.135 -
3.136 - "Reset horizontal state."
3.137 + # Signal the video circuit.
3.138
3.139 - self.xsub = 0
3.140 - self.column = 0
3.141 - self.buffer_index = self.buffer_limit # need refill
3.142 + self.csync = self.video.csync = 1
3.143
3.144 def hsync(self):
3.145
3.146 - "Signal the end of a line."
3.147 + "Signal the end of a scanline."
3.148 +
3.149 + self.y += 1
3.150 + self.reset_horizontal()
3.151
3.152 # Support spacing between character rows.
3.153
3.154 @@ -135,7 +166,6 @@
3.155 self.ssub -= 1
3.156 return
3.157
3.158 - self.reset_horizontal()
3.159 self.line += 1
3.160
3.161 # If not on a row boundary, move to the next line.
3.162 @@ -163,36 +193,73 @@
3.163
3.164 self.line_start = self.address
3.165
3.166 - def get_pixel_colour(self):
3.167 + def reset_horizontal(self):
3.168 +
3.169 + "Reset horizontal state."
3.170 +
3.171 + self.x = 0
3.172 + self.buffer_index = self.buffer_limit # need refill
3.173 +
3.174 + # Signal the video circuit.
3.175 +
3.176 + self.hs = self.video.hs = 1
3.177 +
3.178 + def update(self):
3.179
3.180 """
3.181 - Return a pixel colour by reading from the pixel buffer.
3.182 + Update the pixel colour by reading from the pixel buffer.
3.183 """
3.184
3.185 + # Detect the end of the line.
3.186 +
3.187 + if self.x >= MAX_WIDTH:
3.188 + if self.x == MAX_WIDTH:
3.189 + self.hs = self.video.hs = 0
3.190 +
3.191 + # Detect the end of the scanline.
3.192 +
3.193 + elif self.x == MAX_SCANPOS:
3.194 + self.hsync()
3.195 +
3.196 + # Detect the end of the frame.
3.197 +
3.198 + if self.y == MAX_SCANLINE:
3.199 + self.vsync()
3.200 +
3.201 + # Detect the end of the screen.
3.202 +
3.203 + elif self.y == MAX_HEIGHT:
3.204 + self.csync = self.video.csync = 0
3.205 +
3.206 # Detect spacing between character rows.
3.207
3.208 if self.ssub:
3.209 - return BLANK
3.210 + self.video.colour = BLANK
3.211
3.212 - # Scale pixels horizontally.
3.213 + # Detect horizontal and vertical sync conditions.
3.214
3.215 - if self.xsub == self.xscale:
3.216 - self.xsub = 0
3.217 - self.buffer_index += 1
3.218 + elif not self.hs or not self.csync:
3.219 + pass
3.220 +
3.221 + # For pixels within the frame, obtain and output the value.
3.222 +
3.223 + else:
3.224
3.225 - if self.buffer_index == self.buffer_limit:
3.226 - self.buffer_index = 0
3.227 - self.column += 1
3.228 + # Scale pixels horizontally, only accessing the next pixel value
3.229 + # after the required number of scan positions.
3.230
3.231 - # Detect the end of the scanline.
3.232 + if self.x % self.xscale == 0:
3.233 + self.buffer_index += 1
3.234 +
3.235 + # Fill the buffer once all values have been read.
3.236
3.237 - if self.column > self.columns:
3.238 - return BLANK
3.239 + if self.buffer_index >= self.buffer_limit:
3.240 + self.buffer_index = 0
3.241 + self.fill_pixel_buffer()
3.242
3.243 - self.fill_pixel_buffer()
3.244 + self.video.colour = self.buffer[self.buffer_index]
3.245
3.246 - self.xsub += 1
3.247 - return self.buffer[self.buffer_index]
3.248 + self.x += 1
3.249
3.250 def fill_pixel_buffer(self):
3.251
3.252 @@ -276,9 +343,15 @@
3.253
3.254 def get_ula():
3.255
3.256 - "Return a ULA initialised with a memory array."
3.257 + "Return a ULA initialised with a memory array and video."
3.258 +
3.259 + return ULA(get_memory(), get_video())
3.260
3.261 - return ULA(get_memory())
3.262 +def get_video():
3.263 +
3.264 + "Return a video circuit."
3.265 +
3.266 + return Video()
3.267
3.268 def get_memory():
3.269