ULA

Annotated ula.py

101:4658aa28f367
2016-06-21 Paul Boddie Moved address preparation onto negative edges and data acquisition onto positive edges, using the have_pixels variable to indicate if a read operation needs completing at the start of a new 2MHz period. Fixed various comments and docstrings.
paul@1 1
#!/usr/bin/env python
paul@1 2
paul@1 3
"""
paul@1 4
Acorn Electron ULA simulation.
paul@71 5
paul@84 6
Copyright (C) 2011, 2012, 2013, 2014, 2016 Paul Boddie <paul@boddie.org.uk>
paul@71 7
paul@71 8
This program is free software; you can redistribute it and/or modify it under
paul@71 9
the terms of the GNU General Public License as published by the Free Software
paul@71 10
Foundation; either version 3 of the License, or (at your option) any later
paul@71 11
version.
paul@71 12
paul@71 13
This program is distributed in the hope that it will be useful, but WITHOUT ANY
paul@71 14
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
paul@71 15
PARTICULAR PURPOSE.  See the GNU General Public License for more details.
paul@71 16
paul@71 17
You should have received a copy of the GNU General Public License along
paul@71 18
with this program.  If not, see <http://www.gnu.org/licenses/>.
paul@1 19
"""
paul@1 20
paul@29 21
from array import array
paul@29 22
from itertools import repeat
paul@29 23
paul@22 24
LINES_PER_ROW = 8       # the number of pixel lines per character row
paul@22 25
MAX_HEIGHT = 256        # the height of the screen in pixels
paul@40 26
MAX_WIDTH = 640         # the width of the screen in pixels
paul@40 27
paul@40 28
MAX_CSYNC = 2           # the scanline during which vsync ends
paul@84 29
MIN_PIXELLINE = 28      # the first scanline involving pixel generation
paul@22 30
MAX_SCANLINE = 312      # the number of scanlines in each frame
paul@40 31
paul@40 32
MAX_PIXELLINE = MIN_PIXELLINE + MAX_HEIGHT
paul@40 33
paul@84 34
MAX_HSYNC = 64          # the number of cycles in each hsync period
paul@42 35
MIN_PIXELPOS = 256      # the first cycle involving pixel generation
paul@40 36
MAX_SCANPOS = 1024      # the number of cycles in each scanline
paul@40 37
paul@40 38
MAX_PIXELPOS = MIN_PIXELPOS + MAX_WIDTH
paul@40 39
paul@22 40
SCREEN_LIMIT = 0x8000   # the first address after the screen memory
paul@22 41
MAX_MEMORY = 0x10000    # the number of addressable memory locations
paul@40 42
MAX_RAM = 0x10000       # the number of addressable RAM locations (64Kb in each IC)
paul@3 43
BLANK = (0, 0, 0)
paul@1 44
paul@29 45
def update(ula):
paul@1 46
paul@1 47
    """
paul@31 48
    Update the 'ula' for one frame. Return the resulting screen.
paul@31 49
    """
paul@31 50
paul@31 51
    video = ula.video
paul@31 52
paul@31 53
    i = 0
paul@31 54
    limit = MAX_SCANLINE * MAX_SCANPOS
paul@31 55
    while i < limit:
paul@95 56
        ula.posedge()
paul@31 57
        video.update()
paul@95 58
        ula.negedge()
paul@31 59
        i += 1
paul@40 60
paul@31 61
    return video.screen
paul@31 62
paul@31 63
class Video:
paul@31 64
paul@31 65
    """
paul@31 66
    A class representing the video circuitry.
paul@1 67
    """
paul@1 68
paul@31 69
    def __init__(self):
paul@31 70
        self.screen = array("B", repeat(0, MAX_WIDTH * 3 * MAX_HEIGHT))
paul@31 71
        self.colour = BLANK
paul@31 72
        self.csync = 1
paul@31 73
        self.hs = 1
paul@40 74
        self.x = 0
paul@40 75
        self.y = 0
paul@1 76
paul@40 77
    def set_csync(self, value):
paul@40 78
        if self.csync and not value:
paul@40 79
            self.y = 0
paul@40 80
            self.pos = 0
paul@40 81
        self.csync = value
paul@40 82
paul@40 83
    def set_hs(self, value):
paul@40 84
        if self.hs and not value:
paul@40 85
            self.x = 0
paul@40 86
            self.y += 1
paul@40 87
        self.hs = value
paul@31 88
paul@31 89
    def update(self):
paul@40 90
        if MIN_PIXELLINE <= self.y < MAX_PIXELLINE:
paul@42 91
            if MIN_PIXELPOS + 8 <= self.x < MAX_PIXELPOS + 8:
paul@31 92
                self.screen[self.pos] = self.colour[0]; self.pos += 1
paul@31 93
                self.screen[self.pos] = self.colour[1]; self.pos += 1
paul@31 94
                self.screen[self.pos] = self.colour[2]; self.pos += 1
paul@40 95
            self.x += 1
paul@40 96
paul@40 97
class RAM:
paul@40 98
paul@40 99
    """
paul@40 100
    A class representing the RAM circuits (IC4 to IC7). Each circuit
paul@48 101
    traditionally holds 64 kilobits, with each access obtaining 1 bit from each
paul@48 102
    IC, and thus two accesses being required to obtain a whole byte. Here, we
paul@48 103
    model the circuits with a list of 65536 half-bytes with each bit in a
paul@48 104
    half-byte representing a bit stored on a separate IC.
paul@40 105
    """
paul@40 106
paul@40 107
    def __init__(self):
paul@40 108
paul@40 109
        "Initialise the RAM circuits."
paul@40 110
paul@40 111
        self.memory = [0] * MAX_RAM
paul@40 112
        self.row_address = 0
paul@40 113
        self.column_address = 0
paul@40 114
        self.data = 0
paul@97 115
        self.read_not_write = 1
paul@40 116
paul@40 117
    def row_select(self, address):
paul@59 118
paul@59 119
        "The operation of asserting a row 'address' via RA0...RA7."
paul@59 120
paul@40 121
        self.row_address = address
paul@40 122
paul@40 123
    def row_deselect(self):
paul@40 124
        pass
paul@40 125
paul@40 126
    def column_select(self, address):
paul@59 127
paul@59 128
        "The operation of asserting a column 'address' via RA0...RA7."
paul@59 129
paul@40 130
        self.column_address = address
paul@40 131
paul@40 132
        # Read the data.
paul@40 133
paul@97 134
        if self.read_not_write:
paul@97 135
            self.data = self.memory[self.row_address << 8 | self.column_address]
paul@97 136
        else:
paul@97 137
            self.memory[self.row_address << 8 | self.column_address] = self.data
paul@40 138
paul@40 139
    def column_deselect(self):
paul@40 140
        pass
paul@40 141
paul@97 142
    def read_select(self):
paul@97 143
        self.read_not_write = 1
paul@97 144
paul@97 145
    def write_select(self):
paul@97 146
        self.read_not_write = 0
paul@97 147
paul@40 148
    # Convenience methods.
paul@40 149
paul@40 150
    def fill(self, start, end, value):
paul@40 151
        for i in xrange(start, end):
paul@40 152
            self.memory[i << 1] = value >> 4
paul@40 153
            self.memory[i << 1 | 0x1] = value & 0xf
paul@29 154
paul@97 155
class CPU:
paul@97 156
paul@97 157
    "A CPU abstraction."
paul@97 158
paul@97 159
    def __init__(self):
paul@97 160
        self.address = 0x1000
paul@100 161
        self.data = 0
paul@97 162
        self.read_not_write = 1
paul@97 163
paul@2 164
class ULA:
paul@2 165
paul@31 166
    """
paul@31 167
    A class providing the ULA functionality. Instances of this class refer to
paul@31 168
    the system memory, maintain internal state (such as information about the
paul@31 169
    current screen mode), and provide outputs (such as the current pixel
paul@31 170
    colour).
paul@31 171
    """
paul@1 172
paul@2 173
    modes = [
paul@2 174
        (640, 1, 32), (320, 2, 32), (160, 4, 32),   # (width, depth, rows)
paul@3 175
        (640, 1, 25), (320, 1, 32), (160, 2, 32),
paul@3 176
        (320, 1, 25)
paul@2 177
        ]
paul@2 178
paul@97 179
    def __init__(self, cpu, ram, video):
paul@1 180
paul@97 181
        "Initialise the ULA with the given 'cpu', 'ram' and 'video' instances."
paul@2 182
paul@97 183
        self.cpu = cpu
paul@40 184
        self.ram = ram
paul@31 185
        self.video = video
paul@2 186
        self.set_mode(6)
paul@91 187
        self.palette = map(get_physical_colour, range(0, 8) * 2)
paul@1 188
paul@31 189
        self.reset()
paul@31 190
paul@31 191
    def reset(self):
paul@31 192
paul@31 193
        "Reset the ULA."
paul@31 194
paul@43 195
        # General state.
paul@43 196
paul@43 197
        self.nmi = 0            # no NMI asserted initially
paul@43 198
        self.irq_vsync = 0      # no IRQ asserted initially
paul@97 199
        self.cpu_clock = 0      # drive the CPU clock low
paul@43 200
paul@59 201
        # Communication.
paul@59 202
paul@59 203
        self.ram_address = 0    # address given to the RAM via RA0...RA7
paul@59 204
        self.data = 0           # data read from the RAM via RAM0...RAM3
paul@59 205
        self.cpu_address = 0    # address selected by the CPU via A0...A15
paul@59 206
        self.cpu_read = 0       # data read/write by the CPU selected using R/W
paul@59 207
paul@40 208
        # Internal state.
paul@40 209
paul@42 210
        self.have_pixels = 0    # whether pixel data has been read
paul@93 211
        self.pdata = 0          # decoded RAM data for pixel output
paul@93 212
        self.cycle = 1          # 8-state counter within each 2MHz period
paul@93 213
        self.pcycle = 0         # 8/4/2-state pixel output counter
paul@50 214
paul@87 215
        self.next_frame()
paul@31 216
paul@2 217
    def set_mode(self, mode):
paul@1 218
paul@2 219
        """
paul@2 220
        For the given 'mode', initialise the...
paul@1 221
paul@2 222
          * width in pixels
paul@2 223
          * colour depth in bits per pixel
paul@2 224
          * number of character rows
paul@2 225
          * character row size in bytes
paul@2 226
          * screen size in bytes
paul@2 227
          * default screen start address
paul@2 228
          * horizontal pixel scaling factor
paul@88 229
          * row height in pixels
paul@88 230
          * display height in pixels
paul@31 231
paul@31 232
        The ULA should be reset after a mode switch in order to cleanly display
paul@31 233
        a full screen.
paul@2 234
        """
paul@1 235
paul@3 236
        self.width, self.depth, rows = self.modes[mode]
paul@3 237
paul@31 238
        columns = (self.width * self.depth) / 8                             # bits read -> bytes read
paul@40 239
        self.access_frequency = 80 / columns                                # cycle frequency for reading bytes
paul@31 240
        row_size = columns * LINES_PER_ROW
paul@2 241
paul@3 242
        # Memory access configuration.
paul@4 243
        # Note the limitation on positioning the screen start.
paul@3 244
paul@4 245
        screen_size = row_size * rows
paul@4 246
        self.screen_start = (SCREEN_LIMIT - screen_size) & 0xff00
paul@4 247
        self.screen_size = SCREEN_LIMIT - self.screen_start
paul@3 248
paul@3 249
        # Scanline configuration.
paul@1 250
paul@22 251
        self.xscale = MAX_WIDTH / self.width                                # pixel width in display pixels
paul@88 252
        self.row_height = MAX_HEIGHT / rows                                 # row height in display pixels
paul@88 253
        self.display_height = rows * self.row_height                        # display height in pixels
paul@3 254
paul@40 255
    def vsync(self, value=0):
paul@40 256
paul@40 257
        "Signal the start of a frame."
paul@40 258
paul@40 259
        self.csync = value
paul@40 260
        self.video.set_csync(value)
paul@40 261
paul@40 262
    def hsync(self, value=0):
paul@40 263
paul@40 264
        "Signal the end of a scanline."
paul@40 265
paul@40 266
        self.hs = value
paul@40 267
        self.video.set_hs(value)
paul@40 268
paul@87 269
    def next_frame(self):
paul@2 270
paul@2 271
        "Signal the start of a frame."
paul@1 272
paul@98 273
        self.line_start = self.pixel_address = self.screen_start
paul@5 274
        self.line = self.line_start % LINES_PER_ROW
paul@31 275
        self.y = 0
paul@40 276
        self.x = 0
paul@2 277
paul@87 278
    def next_horizontal(self):
paul@87 279
paul@87 280
        "Visit the next horizontal position."
paul@87 281
paul@98 282
        self.pixel_address += LINES_PER_ROW
paul@87 283
        self.wrap_address()
paul@87 284
paul@87 285
    def next_vertical(self):
paul@1 286
paul@40 287
        "Reset horizontal state within the active region of the frame."
paul@31 288
paul@31 289
        self.y += 1
paul@40 290
        self.x = 0
paul@40 291
paul@96 292
        if self.inside_frame():
paul@96 293
            self.line += 1
paul@2 294
paul@96 295
            # At the end of a row...
paul@96 296
paul@96 297
            if self.line == self.row_height:
paul@2 298
paul@96 299
                # After the end of the last line in a row, the address should already
paul@96 300
                # have been positioned on the last line of the next column.
paul@1 301
paul@98 302
                self.pixel_address -= LINES_PER_ROW - 1
paul@96 303
                self.wrap_address()
paul@96 304
                self.line = 0
paul@96 305
paul@96 306
                # Record the position of the start of the pixel row.
paul@88 307
paul@98 308
                self.line_start = self.pixel_address
paul@88 309
paul@96 310
            # Before any spacing between character rows...
paul@88 311
paul@96 312
            elif self.in_line():
paul@88 313
paul@96 314
                # If not on a row boundary, move to the next line. Here, the address
paul@96 315
                # needs bringing back to the previous character row.
paul@1 316
paul@98 317
                self.pixel_address = self.line_start + 1
paul@96 318
                self.wrap_address()
paul@3 319
paul@96 320
                # Record the position of the start of the pixel row.
paul@96 321
paul@98 322
                self.line_start = self.pixel_address
paul@1 323
paul@100 324
    def access_cycle(self): return (self.x / 8) % self.access_frequency == 0 
paul@100 325
    def would_access_ram(self): return self.access_cycle() and self.read_pixels() and self.in_line()
paul@100 326
    def access_ram(self): return not self.nmi and self.would_access_ram()
paul@88 327
    def in_line(self): return self.line < LINES_PER_ROW
paul@88 328
    def in_frame(self): return MIN_PIXELLINE <= self.y < (MIN_PIXELLINE + self.display_height)
paul@88 329
    def inside_frame(self): return MIN_PIXELLINE < self.y < (MIN_PIXELLINE + self.display_height)
paul@42 330
    def read_pixels(self): return MIN_PIXELPOS <= self.x < MAX_PIXELPOS and self.in_frame()
paul@94 331
    def write_pixels(self): return self.pcycle != 0
paul@100 332
    def next_pixel(self): return self.xscale == 1 or (self.xscale == 2 and self.cycle & 0b01010101) or (self.xscale == 4 and self.cycle & 0b00010001)
paul@31 333
paul@95 334
    def posedge(self):
paul@1 335
paul@100 336
        "Update the state of the ULA for each clock cycle."
paul@2 337
paul@100 338
        self.posedge_video()
paul@100 339
        self.posedge_ram()
paul@100 340
        self.posedge_pixel()
paul@100 341
paul@100 342
    def posedge_video(self):
paul@100 343
paul@100 344
        "Video signalling."
paul@95 345
paul@40 346
        # Detect the end of the scanline.
paul@40 347
paul@40 348
        if self.x == MAX_SCANPOS:
paul@87 349
            self.next_vertical()
paul@40 350
paul@40 351
        # Detect the end of the frame.
paul@40 352
paul@40 353
        if self.y == MAX_SCANLINE:
paul@87 354
            self.next_frame()
paul@40 355
paul@95 356
        # Detect any sync conditions.
paul@95 357
paul@95 358
        if self.x == 0:
paul@95 359
            self.hsync()
paul@95 360
            if self.y == 0:
paul@95 361
                self.vsync()
paul@95 362
                self.irq_vsync = 0
paul@95 363
            elif self.y == MAX_PIXELLINE:
paul@95 364
                self.irq_vsync = 1
paul@95 365
paul@95 366
        # Detect the end of hsync.
paul@95 367
paul@95 368
        elif self.x == MAX_HSYNC:
paul@95 369
            self.hsync(1)
paul@95 370
paul@95 371
        # Detect the end of vsync.
paul@95 372
paul@95 373
        elif self.y == MAX_CSYNC and self.x == MAX_SCANPOS / 2:
paul@95 374
            self.vsync(1)
paul@95 375
paul@100 376
    def posedge_ram(self):
paul@95 377
paul@101 378
        """
paul@101 379
        RAM signalling.
paul@101 380
paul@101 381
        States handled: * _ * * _ * * _
paul@101 382
        """
paul@95 383
paul@40 384
        # Clock management.
paul@40 385
paul@101 386
        # Read 4 bits (for RAM access only).
paul@100 387
paul@100 388
        if self.cycle == 1:
paul@101 389
paul@101 390
            # Reset addresses.
paul@101 391
paul@100 392
            self.ram.column_deselect()
paul@100 393
            self.ram.row_deselect()
paul@100 394
paul@101 395
            # Either read from a required address or transfer CPU data.
paul@40 396
paul@101 397
            if self.have_pixels:
paul@101 398
                self.data = (self.data & 0xf0) | self.ram.data
paul@101 399
            else:
paul@101 400
                self.cpu_update_clock()
paul@101 401
                self.cpu_transfer_low()
paul@40 402
paul@101 403
        # Latch row address.
paul@40 404
paul@100 405
        elif self.cycle == 4:
paul@40 406
paul@59 407
            # Select an address needed by the ULA or CPU.
paul@59 408
paul@59 409
            self.ram.row_select(self.ram_address)
paul@59 410
paul@40 411
        # Latch column address.
paul@40 412
paul@100 413
        elif self.cycle == 8:
paul@40 414
paul@59 415
            # Select an address needed by the ULA or CPU.
paul@31 416
paul@59 417
            self.ram.column_select(self.ram_address)
paul@40 418
paul@97 419
            # Assert the RAM write enable if appropriate.
paul@97 420
paul@100 421
            if self.access_ram():
paul@97 422
                self.ram.read_select()
paul@97 423
            else:
paul@97 424
                self.cpu_transfer_select()
paul@97 425
paul@101 426
        # Read 4 bits (for RAM access only).
paul@40 427
paul@100 428
        elif self.cycle == 32:
paul@101 429
paul@101 430
            # Prepare to latch column address.
paul@101 431
paul@40 432
            self.ram.column_deselect()
paul@31 433
paul@101 434
            # Either read from a required address or transfer CPU data.
paul@40 435
paul@100 436
            if self.access_ram():
paul@101 437
                self.data = self.ram.data << 4
paul@59 438
            else:
paul@101 439
                self.cpu_transfer_high()
paul@40 440
paul@40 441
        # Latch column address.
paul@40 442
paul@100 443
        elif self.cycle == 64:
paul@40 444
paul@59 445
            # Select an address needed by the ULA or CPU.
paul@40 446
paul@59 447
            self.ram.column_select(self.ram_address)
paul@31 448
paul@100 449
    def posedge_pixel(self):
paul@40 450
paul@100 451
        "Pixel production."
paul@31 452
paul@31 453
        # For pixels within the frame, obtain and output the value.
paul@31 454
paul@95 455
        if self.write_pixels():
paul@92 456
            self.output_colour_value()
paul@1 457
paul@31 458
            # Scale pixels horizontally, only accessing the next pixel value
paul@31 459
            # after the required number of scan positions.
paul@22 460
paul@93 461
            if self.next_pixel():
paul@92 462
                self.next_pixel_value()
paul@31 463
paul@95 464
        # Detect spacing between character rows.
paul@95 465
paul@95 466
        else:
paul@95 467
            self.video.colour = BLANK
paul@95 468
paul@95 469
    def negedge(self):
paul@95 470
paul@99 471
        """
paul@99 472
        Update the state of the device.
paul@99 473
paul@101 474
        States handled: * * * _ _ * _ *
paul@99 475
        """
paul@99 476
paul@99 477
        # Clock management.
paul@99 478
paul@100 479
        # Initialise the pixel buffer if appropriate. Output starts after
paul@100 480
        # this cycle.
paul@100 481
paul@100 482
        if self.cycle == 1 and self.have_pixels:
paul@100 483
            self.pdata = decode(self.data, self.depth)
paul@100 484
            self.pcycle = 1
paul@100 485
            self.have_pixels = 0
paul@99 486
paul@101 487
        # Set row address (for RAM access only).
paul@99 488
paul@101 489
        elif self.cycle == 2:
paul@99 490
paul@101 491
            # Either assert a required address or propagate the CPU address.
paul@99 492
paul@100 493
            if self.access_ram():
paul@101 494
                self.init_row_address(self.pixel_address)
paul@101 495
            else:
paul@101 496
                self.init_row_address(self.cpu_address)
paul@101 497
paul@101 498
        # Latch row address, set column address (for RAM access only).
paul@101 499
paul@101 500
        elif self.cycle == 4:
paul@101 501
paul@101 502
            # Either assert a required address or propagate the CPU address.
paul@101 503
paul@101 504
            if self.access_ram():
paul@101 505
                self.init_column_address(self.pixel_address, 0)
paul@99 506
            else:
paul@101 507
                self.init_column_address(self.cpu_address, 0)
paul@101 508
paul@101 509
        # Set column address (for RAM access only).
paul@101 510
paul@101 511
        elif self.cycle == 32:
paul@99 512
paul@101 513
            # Either assert a required address or propagate the CPU address.
paul@101 514
paul@101 515
            if self.access_ram():
paul@101 516
                self.init_column_address(self.pixel_address, 1)
paul@101 517
            else:
paul@101 518
                self.init_column_address(self.cpu_address, 1)
paul@101 519
paul@101 520
        # Update addresses.
paul@99 521
paul@100 522
        elif self.cycle == 128:
paul@99 523
paul@101 524
            # Advance to the next column even if an NMI is asserted.
paul@101 525
paul@101 526
            if self.would_access_ram():
paul@101 527
                self.next_horizontal()
paul@101 528
paul@101 529
            # If the ULA accessed RAM, indicate that a read needs completing.
paul@99 530
paul@100 531
            if self.access_ram():
paul@99 532
                self.have_pixels = 1
paul@22 533
paul@95 534
        # Start a new cycle.
paul@95 535
paul@95 536
        self.cycle = rotate(self.cycle, 1)
paul@95 537
        self.x += 1
paul@2 538
paul@92 539
    def output_colour_value(self):
paul@1 540
paul@2 541
        """
paul@92 542
        Output the colour value for the current pixel by translating memory
paul@92 543
        content for the current mode.
paul@2 544
        """
paul@1 545
paul@92 546
        value = value_of_bits(self.pdata, self.depth)
paul@92 547
        self.video.colour = self.palette[value]
paul@92 548
paul@92 549
    def next_pixel_value(self):
paul@92 550
        self.pdata = rotate(self.pdata, self.depth)
paul@95 551
        self.pcycle = rotate(self.pcycle, self.depth, zero=True)
paul@2 552
paul@2 553
    def wrap_address(self):
paul@98 554
        if self.pixel_address >= SCREEN_LIMIT:
paul@98 555
            self.pixel_address -= self.screen_size
paul@1 556
paul@59 557
    def init_row_address(self, address):
paul@59 558
        self.ram_address = (address & 0xff80) >> 7
paul@59 559
paul@59 560
    def init_column_address(self, address, offset):
paul@59 561
        self.ram_address = (address & 0x7f) << 1 | offset
paul@59 562
paul@59 563
    def cpu_transfer_high(self):
paul@59 564
        if self.cpu_read:
paul@59 565
            self.cpu_data = self.ram.data << 4
paul@97 566
        else:
paul@97 567
            self.ram.data = self.cpu_data >> 4
paul@59 568
paul@59 569
    def cpu_transfer_low(self):
paul@59 570
        if self.cpu_read:
paul@59 571
            self.cpu_data = self.data | self.ram.data
paul@97 572
        else:
paul@97 573
            self.ram.data = self.cpu_data & 0b00001111
paul@97 574
paul@97 575
    def cpu_read_address(self):
paul@97 576
        self.cpu_address = self.cpu.address
paul@97 577
paul@97 578
    def cpu_transfer_data(self):
paul@97 579
        if self.cpu_read:
paul@97 580
            self.cpu.data = self.cpu_data
paul@97 581
        else:
paul@97 582
            self.cpu_data = self.cpu.data
paul@97 583
paul@97 584
    def cpu_update_clock(self):
paul@97 585
        self.cpu_clock = not self.cpu_clock
paul@97 586
        if self.cpu_clock:
paul@97 587
            self.cpu_transfer_data()
paul@97 588
        else:
paul@97 589
            self.cpu_read_address()
paul@97 590
paul@97 591
    def cpu_transfer_select(self):
paul@97 592
        self.cpu_read = self.cpu.read_not_write
paul@97 593
        if self.cpu_read:
paul@97 594
            self.ram.read_select()
paul@97 595
        else:
paul@97 596
            self.ram.write_select()
paul@59 597
paul@95 598
def rotate(value, depth, width=8, zero=False):
paul@89 599
paul@93 600
    """
paul@95 601
    Return 'value' rotated left by the number of bits given by 'depth', doing so
paul@95 602
    within a value 'width' given in bits. If 'zero' is true, rotate zero bits
paul@95 603
    into the lower bits when rotating.
paul@93 604
    """
paul@93 605
paul@93 606
    field = width - depth
paul@92 607
    top = value >> field
paul@93 608
    mask = 2 ** (width - depth) - 1
paul@92 609
    rest = value & mask
paul@95 610
    return (rest << depth) | (not zero and top or 0)
paul@92 611
paul@92 612
def value_of_bits(value, depth):
paul@92 613
paul@92 614
    """
paul@92 615
    Convert the upper bits of 'value' to a result, using 'depth' to indicate the
paul@92 616
    number of bits involved.
paul@92 617
    """
paul@92 618
paul@92 619
    return value >> (8 - depth)
paul@89 620
paul@1 621
def get_physical_colour(value):
paul@1 622
paul@1 623
    """
paul@1 624
    Return the physical colour as an RGB triple for the given 'value'.
paul@1 625
    """
paul@1 626
paul@1 627
    return value & 1, value >> 1 & 1, value >> 2 & 1
paul@1 628
paul@1 629
def decode(value, depth):
paul@1 630
paul@1 631
    """
paul@1 632
    Decode the given byte 'value' according to the 'depth' in bits per pixel,
paul@1 633
    returning a sequence of pixel values.
paul@1 634
    """
paul@1 635
paul@1 636
    if depth == 1:
paul@92 637
        return value
paul@1 638
    elif depth == 2:
paul@92 639
        return ((value & 128) | ((value & 8) << 3) | ((value & 64) >> 1) | ((value & 4) << 2) |
paul@92 640
                ((value & 32) >> 2) | ((value & 2) << 1) | ((value & 16) >> 3) | (value & 1))
paul@1 641
    elif depth == 4:
paul@92 642
        return ((value & 128) | ((value & 32) << 1) | ((value & 8) << 2) | ((value & 2) << 3) |
paul@92 643
                ((value & 64) >> 3) | ((value & 16) >> 2) | ((value & 4) >> 1) | (value & 1))
paul@1 644
    else:
paul@1 645
        raise ValueError, "Only depths of 1, 2 and 4 are supported, not %d." % depth
paul@1 646
paul@1 647
# Convenience functions.
paul@1 648
paul@1 649
def encode(values, depth):
paul@1 650
paul@1 651
    """
paul@1 652
    Encode the given 'values' according to the 'depth' in bits per pixel,
paul@1 653
    returning a byte value for the pixels.
paul@1 654
    """
paul@1 655
paul@1 656
    result = 0
paul@1 657
paul@1 658
    if depth == 1:
paul@1 659
        for value in values:
paul@1 660
            result = result << 1 | (value & 1)
paul@1 661
    elif depth == 2:
paul@1 662
        for value in values:
paul@1 663
            result = result << 1 | (value & 2) << 3 | (value & 1)
paul@1 664
    elif depth == 4:
paul@1 665
        for value in values:
paul@1 666
            result = result << 1 | (value & 8) << 3 | (value & 4) << 2 | (value & 2) << 1 | (value & 1)
paul@1 667
    else:
paul@1 668
        raise ValueError, "Only depths of 1, 2 and 4 are supported, not %d." % depth
paul@1 669
paul@1 670
    return result
paul@1 671
paul@11 672
def get_ula():
paul@11 673
paul@31 674
    "Return a ULA initialised with a memory array and video."
paul@31 675
paul@97 676
    return ULA(get_cpu(), get_ram(), get_video())
paul@11 677
paul@31 678
def get_video():
paul@31 679
paul@31 680
    "Return a video circuit."
paul@31 681
paul@31 682
    return Video()
paul@11 683
paul@40 684
def get_ram():
paul@10 685
paul@40 686
    "Return an instance representing the computer's RAM hardware."
paul@7 687
paul@40 688
    return RAM()
paul@1 689
paul@97 690
def get_cpu():
paul@97 691
paul@97 692
    "Return an instance representing the CPU."
paul@97 693
paul@97 694
    return CPU()
paul@97 695
paul@7 696
# Test program providing coverage (necessary for compilers like Shedskin).
paul@7 697
paul@7 698
if __name__ == "__main__":
paul@11 699
    ula = get_ula()
paul@7 700
    ula.set_mode(2)
paul@40 701
    ula.reset()
paul@40 702
    ula.ram.fill(0x5800 - 320, 0x8000, encode((2, 7), 4))
paul@7 703
paul@7 704
    # Make a simple two-dimensional array of tuples (three-dimensional in pygame
paul@7 705
    # terminology).
paul@7 706
paul@29 707
    a = update(ula)
paul@7 708
paul@1 709
# vim: tabstop=4 expandtab shiftwidth=4