ULA

Annotated ula.py

68:83648f6e7be7
2014-02-01 Paul Boddie Merged general changes. shedskin
paul@1 1
#!/usr/bin/env python
paul@1 2
paul@1 3
"""
paul@1 4
Acorn Electron ULA simulation.
paul@1 5
"""
paul@1 6
paul@29 7
from array import array
paul@29 8
from itertools import repeat
paul@29 9
paul@22 10
LINES_PER_ROW = 8       # the number of pixel lines per character row
paul@22 11
MAX_HEIGHT = 256        # the height of the screen in pixels
paul@40 12
MAX_WIDTH = 640         # the width of the screen in pixels
paul@40 13
paul@40 14
MAX_CSYNC = 2           # the scanline during which vsync ends
paul@40 15
MIN_PIXELLINE = 38      # the first scanline involving pixel generation
paul@22 16
MAX_SCANLINE = 312      # the number of scanlines in each frame
paul@40 17
paul@40 18
MAX_PIXELLINE = MIN_PIXELLINE + MAX_HEIGHT
paul@40 19
paul@40 20
MAX_HSYNC = 75          # the number of cycles in each hsync period
paul@42 21
MIN_PIXELPOS = 256      # the first cycle involving pixel generation
paul@40 22
MAX_SCANPOS = 1024      # the number of cycles in each scanline
paul@40 23
paul@40 24
MAX_PIXELPOS = MIN_PIXELPOS + MAX_WIDTH
paul@40 25
paul@22 26
SCREEN_LIMIT = 0x8000   # the first address after the screen memory
paul@22 27
MAX_MEMORY = 0x10000    # the number of addressable memory locations
paul@40 28
MAX_RAM = 0x10000       # the number of addressable RAM locations (64Kb in each IC)
paul@3 29
BLANK = (0, 0, 0)
paul@1 30
paul@29 31
def update(ula):
paul@1 32
paul@1 33
    """
paul@31 34
    Update the 'ula' for one frame. Return the resulting screen.
paul@31 35
    """
paul@31 36
paul@31 37
    video = ula.video
paul@31 38
paul@31 39
    i = 0
paul@31 40
    limit = MAX_SCANLINE * MAX_SCANPOS
paul@31 41
    while i < limit:
paul@31 42
        ula.update()
paul@31 43
        video.update()
paul@31 44
        i += 1
paul@32 45
    return video.screen.tolist()
paul@31 46
paul@31 47
class Video:
paul@31 48
paul@31 49
    """
paul@31 50
    A class representing the video circuitry.
paul@1 51
    """
paul@1 52
paul@31 53
    def __init__(self):
paul@31 54
        self.screen = array("B", repeat(0, MAX_WIDTH * 3 * MAX_HEIGHT))
paul@31 55
        self.colour = BLANK
paul@31 56
        self.csync = 1
paul@31 57
        self.hs = 1
paul@40 58
        self.x = 0
paul@40 59
        self.y = 0
paul@1 60
paul@40 61
    def set_csync(self, value):
paul@40 62
        if self.csync and not value:
paul@40 63
            self.y = 0
paul@40 64
            self.pos = 0
paul@40 65
        self.csync = value
paul@40 66
paul@40 67
    def set_hs(self, value):
paul@40 68
        if self.hs and not value:
paul@40 69
            self.x = 0
paul@40 70
            self.y += 1
paul@40 71
        self.hs = value
paul@31 72
paul@31 73
    def update(self):
paul@40 74
        if MIN_PIXELLINE <= self.y < MAX_PIXELLINE:
paul@42 75
            if MIN_PIXELPOS + 8 <= self.x < MAX_PIXELPOS + 8:
paul@31 76
                self.screen[self.pos] = self.colour[0]; self.pos += 1
paul@31 77
                self.screen[self.pos] = self.colour[1]; self.pos += 1
paul@31 78
                self.screen[self.pos] = self.colour[2]; self.pos += 1
paul@40 79
            self.x += 1
paul@40 80
paul@40 81
class RAM:
paul@40 82
paul@40 83
    """
paul@40 84
    A class representing the RAM circuits (IC4 to IC7). Each circuit
paul@48 85
    traditionally holds 64 kilobits, with each access obtaining 1 bit from each
paul@48 86
    IC, and thus two accesses being required to obtain a whole byte. Here, we
paul@48 87
    model the circuits with a list of 65536 half-bytes with each bit in a
paul@48 88
    half-byte representing a bit stored on a separate IC.
paul@40 89
    """
paul@40 90
paul@40 91
    def __init__(self):
paul@40 92
paul@40 93
        "Initialise the RAM circuits."
paul@40 94
paul@40 95
        self.memory = [0] * MAX_RAM
paul@40 96
        self.row_address = 0
paul@40 97
        self.column_address = 0
paul@40 98
        self.data = 0
paul@40 99
paul@40 100
    def row_select(self, address):
paul@59 101
paul@59 102
        "The operation of asserting a row 'address' via RA0...RA7."
paul@59 103
paul@40 104
        self.row_address = address
paul@40 105
paul@40 106
    def row_deselect(self):
paul@40 107
        pass
paul@40 108
paul@40 109
    def column_select(self, address):
paul@59 110
paul@59 111
        "The operation of asserting a column 'address' via RA0...RA7."
paul@59 112
paul@40 113
        self.column_address = address
paul@40 114
paul@40 115
        # Read the data.
paul@40 116
paul@40 117
        self.data = self.memory[self.row_address << 8 | self.column_address]
paul@40 118
paul@40 119
    def column_deselect(self):
paul@40 120
        pass
paul@40 121
paul@40 122
    # Convenience methods.
paul@40 123
paul@40 124
    def fill(self, start, end, value):
paul@40 125
        for i in xrange(start, end):
paul@40 126
            self.memory[i << 1] = value >> 4
paul@40 127
            self.memory[i << 1 | 0x1] = value & 0xf
paul@29 128
paul@66 129
class ShiftRegister:
paul@66 130
paul@66 131
    """
paul@66 132
    A class representing a shift register, used for the internal state of the
paul@66 133
    ULA within each 2MHz period.
paul@66 134
    """
paul@66 135
paul@66 136
    def __init__(self):
paul@66 137
        self.state = [0] * 8
paul@66 138
        self.input = 0
paul@66 139
paul@66 140
    def set_input(self, input):
paul@66 141
        self.input = input
paul@66 142
paul@66 143
    def shift(self):
paul@66 144
paul@66 145
        # NOTE: This is not meant to be "nice" Python, but instead models the
paul@66 146
        # NOTE: propagation of state through the latches.
paul@66 147
paul@66 148
        self.state[0], self.state[1], self.state[2], self.state[3], \
paul@66 149
        self.state[4], self.state[5], self.state[6], self.state[7] = \
paul@66 150
        self.input,    self.state[0], self.state[1], self.state[2], \
paul@66 151
        self.state[3], self.state[4], self.state[5], self.state[6]
paul@66 152
paul@66 153
    def __getitem__(self, i):
paul@66 154
        return self.state[i]
paul@66 155
paul@2 156
class ULA:
paul@2 157
paul@31 158
    """
paul@31 159
    A class providing the ULA functionality. Instances of this class refer to
paul@31 160
    the system memory, maintain internal state (such as information about the
paul@31 161
    current screen mode), and provide outputs (such as the current pixel
paul@31 162
    colour).
paul@31 163
    """
paul@1 164
paul@2 165
    modes = [
paul@2 166
        (640, 1, 32), (320, 2, 32), (160, 4, 32),   # (width, depth, rows)
paul@3 167
        (640, 1, 25), (320, 1, 32), (160, 2, 32),
paul@3 168
        (320, 1, 25)
paul@2 169
        ]
paul@2 170
paul@2 171
    palette = range(0, 8) * 2
paul@2 172
paul@40 173
    def __init__(self, ram, video):
paul@1 174
paul@40 175
        "Initialise the ULA with the given 'ram' and 'video' instances."
paul@2 176
paul@40 177
        self.ram = ram
paul@31 178
        self.video = video
paul@2 179
        self.set_mode(6)
paul@1 180
paul@2 181
        # Internal state.
paul@2 182
paul@31 183
        self.reset()
paul@31 184
paul@31 185
    def reset(self):
paul@31 186
paul@31 187
        "Reset the ULA."
paul@31 188
paul@43 189
        # General state.
paul@43 190
paul@43 191
        self.nmi = 0            # no NMI asserted initially
paul@43 192
        self.irq_vsync = 0      # no IRQ asserted initially
paul@43 193
paul@59 194
        # Communication.
paul@59 195
paul@59 196
        self.ram_address = 0    # address given to the RAM via RA0...RA7
paul@59 197
        self.data = 0           # data read from the RAM via RAM0...RAM3
paul@59 198
        self.cpu_address = 0    # address selected by the CPU via A0...A15
paul@59 199
        self.cpu_read = 0       # data read/write by the CPU selected using R/W
paul@59 200
paul@40 201
        # Internal state.
paul@40 202
paul@40 203
        self.access = 0         # counter used to determine whether a byte needs reading
paul@42 204
        self.have_pixels = 0    # whether pixel data has been read
paul@42 205
        self.writing_pixels = 0 # whether pixel data can be written
paul@45 206
        self.buffer = [BLANK]*8 # pixel buffer for decoded RAM data
paul@40 207
paul@66 208
        self.cycle = ShiftRegister()    # 8-state counter within each 2MHz period
paul@66 209
paul@66 210
        self.cycle.set_input(1) # assert the input to set the first state output
paul@66 211
        self.cycle.shift()
paul@66 212
        self.cycle.set_input(0) # reset the input since only one state output will be active
paul@50 213
paul@40 214
        self.reset_vertical()
paul@31 215
paul@2 216
    def set_mode(self, mode):
paul@1 217
paul@2 218
        """
paul@2 219
        For the given 'mode', initialise the...
paul@1 220
paul@2 221
          * width in pixels
paul@2 222
          * colour depth in bits per pixel
paul@2 223
          * number of character rows
paul@2 224
          * character row size in bytes
paul@2 225
          * screen size in bytes
paul@2 226
          * default screen start address
paul@2 227
          * horizontal pixel scaling factor
paul@2 228
          * line spacing in pixels
paul@2 229
          * number of entries in the pixel buffer
paul@31 230
paul@31 231
        The ULA should be reset after a mode switch in order to cleanly display
paul@31 232
        a full screen.
paul@2 233
        """
paul@1 234
paul@12 235
        self.width, self.depth, rows = ULA.modes[mode]
paul@3 236
paul@31 237
        columns = (self.width * self.depth) / 8                             # bits read -> bytes read
paul@40 238
        self.access_frequency = 80 / columns                                # cycle frequency for reading bytes
paul@31 239
        row_size = columns * LINES_PER_ROW
paul@2 240
paul@3 241
        # Memory access configuration.
paul@4 242
        # Note the limitation on positioning the screen start.
paul@3 243
paul@4 244
        screen_size = row_size * rows
paul@4 245
        self.screen_start = (SCREEN_LIMIT - screen_size) & 0xff00
paul@4 246
        self.screen_size = SCREEN_LIMIT - self.screen_start
paul@3 247
paul@3 248
        # Scanline configuration.
paul@1 249
paul@22 250
        self.xscale = MAX_WIDTH / self.width                                # pixel width in display pixels
paul@3 251
        self.spacing = MAX_HEIGHT / rows - LINES_PER_ROW                    # pixels between rows
paul@3 252
paul@3 253
        # Start of unused region.
paul@3 254
paul@3 255
        self.footer = rows * LINES_PER_ROW
paul@22 256
        self.margin = MAX_SCANLINE - rows * (LINES_PER_ROW + self.spacing) + self.spacing
paul@3 257
paul@3 258
        # Internal pixel buffer size.
paul@3 259
paul@2 260
        self.buffer_limit = 8 / self.depth
paul@1 261
paul@40 262
    def vsync(self, value=0):
paul@40 263
paul@40 264
        "Signal the start of a frame."
paul@40 265
paul@40 266
        self.csync = value
paul@40 267
        self.video.set_csync(value)
paul@40 268
paul@40 269
    def hsync(self, value=0):
paul@40 270
paul@40 271
        "Signal the end of a scanline."
paul@40 272
paul@40 273
        self.hs = value
paul@40 274
        self.video.set_hs(value)
paul@40 275
paul@40 276
    def reset_vertical(self):
paul@2 277
paul@2 278
        "Signal the start of a frame."
paul@1 279
paul@2 280
        self.line_start = self.address = self.screen_start
paul@5 281
        self.line = self.line_start % LINES_PER_ROW
paul@3 282
        self.ssub = 0
paul@31 283
        self.y = 0
paul@40 284
        self.x = 0
paul@2 285
paul@40 286
    def reset_horizontal(self):
paul@1 287
paul@40 288
        "Reset horizontal state within the active region of the frame."
paul@31 289
paul@31 290
        self.y += 1
paul@40 291
        self.x = 0
paul@40 292
paul@40 293
        if not self.inside_frame():
paul@40 294
            return
paul@2 295
paul@3 296
        # Support spacing between character rows.
paul@3 297
paul@3 298
        if self.ssub:
paul@3 299
            self.ssub -= 1
paul@3 300
            return
paul@3 301
paul@2 302
        self.line += 1
paul@2 303
paul@3 304
        # If not on a row boundary, move to the next line.
paul@3 305
paul@3 306
        if self.line % LINES_PER_ROW:
paul@2 307
            self.address = self.line_start + 1
paul@2 308
            self.wrap_address()
paul@2 309
paul@2 310
        # After the end of the last line in a row, the address should already
paul@2 311
        # have been positioned on the last line of the next column.
paul@1 312
paul@2 313
        else:
paul@2 314
            self.address -= LINES_PER_ROW - 1
paul@2 315
            self.wrap_address()
paul@1 316
paul@3 317
            # Test for the footer region.
paul@3 318
paul@3 319
            if self.spacing and self.line == self.footer:
paul@22 320
                self.ssub = self.margin
paul@3 321
                return
paul@1 322
paul@3 323
            # Support spacing between character rows.
paul@2 324
paul@22 325
            self.ssub = self.spacing
paul@3 326
paul@3 327
        self.line_start = self.address
paul@1 328
paul@40 329
    def in_frame(self): return MIN_PIXELLINE <= self.y < MAX_PIXELLINE
paul@40 330
    def inside_frame(self): return MIN_PIXELLINE < self.y < MAX_PIXELLINE
paul@42 331
    def read_pixels(self): return MIN_PIXELPOS <= self.x < MAX_PIXELPOS and self.in_frame()
paul@31 332
paul@31 333
    def update(self):
paul@1 334
paul@2 335
        """
paul@40 336
        Update the state of the ULA for each clock cycle. This involves updating
paul@40 337
        the pixel colour by reading from the pixel buffer.
paul@2 338
        """
paul@2 339
paul@40 340
        # Detect the end of the scanline.
paul@40 341
paul@40 342
        if self.x == MAX_SCANPOS:
paul@40 343
            self.reset_horizontal()
paul@40 344
paul@40 345
        # Detect the end of the frame.
paul@40 346
paul@40 347
        if self.y == MAX_SCANLINE:
paul@40 348
            self.reset_vertical()
paul@40 349
paul@40 350
paul@40 351
paul@40 352
        # Clock management.
paul@40 353
paul@43 354
        access_ram = not self.nmi and self.access == 0 and self.read_pixels() and not self.ssub
paul@40 355
paul@40 356
        # Set row address (for ULA access only).
paul@40 357
paul@50 358
        if self.cycle[0]:
paul@40 359
paul@59 360
            # Either assert a required address or propagate the CPU address.
paul@40 361
paul@40 362
            if access_ram:
paul@59 363
                self.init_row_address(self.address)
paul@59 364
            else:
paul@59 365
                self.init_row_address(self.cpu_address)
paul@40 366
paul@42 367
            # Initialise the pixel buffer if appropriate.
paul@42 368
paul@42 369
            if not self.writing_pixels and self.have_pixels:
paul@42 370
                self.xcounter = self.xscale
paul@42 371
                self.buffer_index = 0
paul@42 372
                self.fill_pixel_buffer()
paul@42 373
                self.writing_pixels = 1
paul@42 374
paul@40 375
        # Latch row address, set column address (for ULA access only).
paul@40 376
paul@50 377
        elif self.cycle[1]:
paul@40 378
paul@59 379
            # Select an address needed by the ULA or CPU.
paul@59 380
paul@59 381
            self.ram.row_select(self.ram_address)
paul@59 382
paul@59 383
            # Either assert a required address or propagate the CPU address.
paul@31 384
paul@40 385
            if access_ram:
paul@59 386
                self.init_column_address(self.address, 0)
paul@59 387
            else:
paul@59 388
                self.init_column_address(self.cpu_address, 0)
paul@40 389
paul@40 390
        # Latch column address.
paul@40 391
paul@50 392
        elif self.cycle[2]:
paul@40 393
paul@59 394
            # Select an address needed by the ULA or CPU.
paul@31 395
paul@59 396
            self.ram.column_select(self.ram_address)
paul@40 397
paul@40 398
        # Read 4 bits (for ULA access only).
paul@40 399
        # NOTE: Perhaps map alternate bits, not half-bytes.
paul@40 400
paul@50 401
        elif self.cycle[3]:
paul@40 402
paul@59 403
            # Either read from a required address or transfer CPU data.
paul@40 404
paul@40 405
            if access_ram:
paul@40 406
                self.data = self.ram.data << 4
paul@59 407
            else:
paul@59 408
                self.cpu_transfer_high()
paul@40 409
paul@40 410
        # Set column address (for ULA access only).
paul@40 411
paul@50 412
        elif self.cycle[4]:
paul@40 413
            self.ram.column_deselect()
paul@31 414
paul@59 415
            # Either assert a required address or propagate the CPU address.
paul@40 416
paul@40 417
            if access_ram:
paul@59 418
                self.init_column_address(self.address, 1)
paul@59 419
            else:
paul@59 420
                self.init_column_address(self.cpu_address, 1)
paul@40 421
paul@40 422
        # Latch column address.
paul@40 423
paul@50 424
        elif self.cycle[5]:
paul@40 425
paul@59 426
            # Select an address needed by the ULA or CPU.
paul@40 427
paul@59 428
            self.ram.column_select(self.ram_address)
paul@31 429
paul@40 430
        # Read 4 bits (for ULA access only).
paul@40 431
        # NOTE: Perhaps map alternate bits, not half-bytes.
paul@40 432
paul@50 433
        elif self.cycle[6]:
paul@40 434
paul@59 435
            # Either read from a required address or transfer CPU data.
paul@40 436
paul@40 437
            if access_ram:
paul@40 438
                self.data = self.data | self.ram.data
paul@42 439
                self.have_pixels = 1
paul@40 440
paul@40 441
                # Advance to the next column.
paul@40 442
paul@40 443
                self.address += LINES_PER_ROW
paul@40 444
                self.wrap_address()
paul@59 445
            else:
paul@59 446
                self.cpu_transfer_low()
paul@40 447
paul@40 448
        # Reset addresses.
paul@31 449
paul@50 450
        elif self.cycle[7]:
paul@40 451
            self.ram.column_deselect()
paul@40 452
            self.ram.row_deselect()
paul@40 453
paul@40 454
            # Update the RAM access controller.
paul@40 455
paul@40 456
            self.access = (self.access + 1) % self.access_frequency
paul@40 457
paul@66 458
        # Update the state of the device.
paul@66 459
paul@66 460
        self.cycle.set_input(self.cycle[7])
paul@66 461
        self.cycle.shift()
paul@66 462
paul@40 463
paul@40 464
paul@40 465
        # Video signalling.
paul@40 466
paul@40 467
        # Detect any sync conditions.
paul@31 468
paul@40 469
        if self.x == 0:
paul@40 470
            self.hsync()
paul@40 471
            if self.y == 0:
paul@40 472
                self.vsync()
paul@43 473
                self.irq_vsync = 0
paul@43 474
            elif self.y == MAX_PIXELLINE:
paul@43 475
                self.irq_vsync = 1
paul@40 476
paul@40 477
        # Detect the end of hsync.
paul@31 478
paul@40 479
        elif self.x == MAX_HSYNC:
paul@40 480
            self.hsync(1)
paul@40 481
paul@40 482
        # Detect the end of vsync.
paul@40 483
paul@40 484
        elif self.y == MAX_CSYNC and self.x == MAX_SCANPOS / 2:
paul@40 485
            self.vsync(1)
paul@40 486
paul@40 487
paul@40 488
paul@40 489
        # Pixel production.
paul@31 490
paul@3 491
        # Detect spacing between character rows.
paul@3 492
paul@42 493
        if not self.writing_pixels or self.ssub:
paul@31 494
            self.video.colour = BLANK
paul@3 495
paul@31 496
        # For pixels within the frame, obtain and output the value.
paul@31 497
paul@31 498
        else:
paul@40 499
paul@42 500
            self.xcounter -= 1
paul@42 501
            self.video.colour = self.buffer[self.buffer_index]
paul@1 502
paul@31 503
            # Scale pixels horizontally, only accessing the next pixel value
paul@31 504
            # after the required number of scan positions.
paul@22 505
paul@42 506
            if self.xcounter == 0:
paul@40 507
                self.xcounter = self.xscale
paul@31 508
                self.buffer_index += 1
paul@31 509
paul@42 510
                # Handle the buffer empty condition.
paul@22 511
paul@40 512
                if self.buffer_index >= self.buffer_limit:
paul@42 513
                    self.writing_pixels = 0
paul@2 514
paul@31 515
        self.x += 1
paul@2 516
paul@2 517
    def fill_pixel_buffer(self):
paul@1 518
paul@2 519
        """
paul@2 520
        Fill the pixel buffer by translating memory content for the current
paul@2 521
        mode.
paul@2 522
        """
paul@1 523
paul@40 524
        byte_value = self.data # which should have been read automatically
paul@1 525
paul@2 526
        i = 0
paul@2 527
        for colour in decode(byte_value, self.depth):
paul@12 528
            self.buffer[i] = get_physical_colour(ULA.palette[colour])
paul@2 529
            i += 1
paul@2 530
paul@2 531
    def wrap_address(self):
paul@2 532
        if self.address >= SCREEN_LIMIT:
paul@2 533
            self.address -= self.screen_size
paul@1 534
paul@59 535
    def init_row_address(self, address):
paul@59 536
        self.ram_address = (address & 0xff80) >> 7
paul@59 537
paul@59 538
    def init_column_address(self, address, offset):
paul@59 539
        self.ram_address = (address & 0x7f) << 1 | offset
paul@59 540
paul@59 541
    def cpu_transfer_high(self):
paul@59 542
        if self.cpu_read:
paul@59 543
            self.cpu_data = self.ram.data << 4
paul@59 544
paul@59 545
    def cpu_transfer_low(self):
paul@59 546
        if self.cpu_read:
paul@59 547
            self.cpu_data = self.data | self.ram.data
paul@59 548
paul@1 549
def get_physical_colour(value):
paul@1 550
paul@1 551
    """
paul@1 552
    Return the physical colour as an RGB triple for the given 'value'.
paul@1 553
    """
paul@1 554
paul@1 555
    return value & 1, value >> 1 & 1, value >> 2 & 1
paul@1 556
paul@1 557
def decode(value, depth):
paul@1 558
paul@1 559
    """
paul@1 560
    Decode the given byte 'value' according to the 'depth' in bits per pixel,
paul@1 561
    returning a sequence of pixel values.
paul@1 562
    """
paul@1 563
paul@1 564
    if depth == 1:
paul@1 565
        return (value >> 7, value >> 6 & 1, value >> 5 & 1, value >> 4 & 1,
paul@1 566
                value >> 3 & 1, value >> 2 & 1, value >> 1 & 1, value & 1)
paul@1 567
    elif depth == 2:
paul@1 568
        return (value >> 6 & 2 | value >> 3 & 1, value >> 5 & 2 | value >> 2 & 1,
paul@1 569
                value >> 4 & 2 | value >> 1 & 1, value >> 3 & 2 | value & 1)
paul@1 570
    elif depth == 4:
paul@1 571
        return (value >> 4 & 8 | value >> 3 & 4 | value >> 2 & 2 | value >> 1 & 1,
paul@1 572
                value >> 3 & 8 | value >> 2 & 4 | value >> 1 & 2 | value & 1)
paul@1 573
    else:
paul@8 574
        raise ValueError("Only depths of 1, 2 and 4 are supported, not %d." % depth)
paul@1 575
paul@1 576
# Convenience functions.
paul@1 577
paul@1 578
def encode(values, depth):
paul@1 579
paul@1 580
    """
paul@1 581
    Encode the given 'values' according to the 'depth' in bits per pixel,
paul@1 582
    returning a byte value for the pixels.
paul@1 583
    """
paul@1 584
paul@1 585
    result = 0
paul@1 586
paul@1 587
    if depth == 1:
paul@1 588
        for value in values:
paul@1 589
            result = result << 1 | (value & 1)
paul@1 590
    elif depth == 2:
paul@1 591
        for value in values:
paul@1 592
            result = result << 1 | (value & 2) << 3 | (value & 1)
paul@1 593
    elif depth == 4:
paul@1 594
        for value in values:
paul@1 595
            result = result << 1 | (value & 8) << 3 | (value & 4) << 2 | (value & 2) << 1 | (value & 1)
paul@1 596
    else:
paul@8 597
        raise ValueError("Only depths of 1, 2 and 4 are supported, not %d." % depth)
paul@1 598
paul@1 599
    return result
paul@1 600
paul@11 601
def get_ula():
paul@11 602
paul@31 603
    "Return a ULA initialised with a memory array and video."
paul@31 604
paul@40 605
    return ULA(get_ram(), get_video())
paul@11 606
paul@31 607
def get_video():
paul@31 608
paul@31 609
    "Return a video circuit."
paul@31 610
paul@31 611
    return Video()
paul@11 612
paul@40 613
def get_ram():
paul@10 614
paul@40 615
    "Return an instance representing the computer's RAM hardware."
paul@7 616
paul@40 617
    return RAM()
paul@1 618
paul@7 619
# Test program providing coverage (necessary for compilers like Shedskin).
paul@7 620
paul@7 621
if __name__ == "__main__":
paul@11 622
    ula = get_ula()
paul@12 623
    ula.set_mode(6)
paul@40 624
    ula.reset()
paul@41 625
    ula.ram.fill(0x6000, 0x8000, encode((1, 0, 1, 0, 1, 0, 1, 0), 1))
paul@7 626
paul@7 627
    # Make a simple two-dimensional array of tuples (three-dimensional in pygame
paul@7 628
    # terminology).
paul@7 629
paul@29 630
    a = update(ula)
paul@7 631
paul@1 632
# vim: tabstop=4 expandtab shiftwidth=4