1.1 --- a/ULA.txt Mon Feb 13 22:25:37 2012 +0100
1.2 +++ b/ULA.txt Sun Feb 26 23:19:22 2012 +0100
1.3 @@ -120,6 +120,42 @@
1.4 See: Acorn Electron Service Manual
1.5 http://acorn.chriswhy.co.uk/docs/Acorn/Manuals/Acorn_ElectronSM.pdf
1.6
1.7 +Interrupts
1.8 +----------
1.9 +
1.10 +The ULA generates IRQs (maskable interrupts) according to certain conditions
1.11 +and these conditions are controlled by location &FE00:
1.12 +
1.13 + * Vertical sync (bottom of displayed screen)
1.14 + * 50MHz real time clock
1.15 + * Transmit data empty
1.16 + * Receive data full
1.17 + * High tone detect
1.18 +
1.19 +The ULA is also used to clear interrupt conditions through location &FE05. Of
1.20 +particular significance is bit 7, which must be set if an NMI (non-maskable
1.21 +interrupt) has occurred and has thus suspended ULA access to memory, restoring
1.22 +the normal function of the ULA.
1.23 +
1.24 +ROM Paging
1.25 +----------
1.26 +
1.27 +Accessing different ROMs involves bits 0 to 3 of &FE05. Some special ROM
1.28 +mappings exist:
1.29 +
1.30 + 8 keyboard
1.31 + 9 keyboard (duplicate)
1.32 + 10 BASIC ROM
1.33 + 11 BASIC ROM (duplicate)
1.34 +
1.35 +Paging in a ROM involves the following procedure:
1.36 +
1.37 + 1. Assert ROM page enable (bit 3) together with a ROM number n in bits 0 to
1.38 + 2, corresponding to ROM number 8+n, such that one of ROMs 12 to 15 is
1.39 + selected.
1.40 + 2. Where a ROM numbered from 0 to 7 is to be selected, set bit 3 to zero
1.41 + whilst writing the desired ROM number n in bits 0 to 2.
1.42 +
1.43 Shadow/Expanded Memory
1.44 ----------------------
1.45
2.1 --- a/ula.py Mon Feb 13 22:25:37 2012 +0100
2.2 +++ b/ula.py Sun Feb 26 23:19:22 2012 +0100
2.3 @@ -18,7 +18,7 @@
2.4 MAX_PIXELLINE = MIN_PIXELLINE + MAX_HEIGHT
2.5
2.6 MAX_HSYNC = 75 # the number of cycles in each hsync period
2.7 -MIN_PIXELPOS = 264 # the first cycle involving pixel generation
2.8 +MIN_PIXELPOS = 256 # the first cycle involving pixel generation
2.9 MAX_SCANPOS = 1024 # the number of cycles in each scanline
2.10
2.11 MAX_PIXELPOS = MIN_PIXELPOS + MAX_WIDTH
2.12 @@ -72,7 +72,7 @@
2.13
2.14 def update(self):
2.15 if MIN_PIXELLINE <= self.y < MAX_PIXELLINE:
2.16 - if MIN_PIXELPOS <= self.x < MAX_PIXELPOS:
2.17 + if MIN_PIXELPOS + 8 <= self.x < MAX_PIXELPOS + 8:
2.18 self.screen[self.pos] = self.colour[0]; self.pos += 1
2.19 self.screen[self.pos] = self.colour[1]; self.pos += 1
2.20 self.screen[self.pos] = self.colour[2]; self.pos += 1
2.21 @@ -153,6 +153,11 @@
2.22
2.23 "Reset the ULA."
2.24
2.25 + # General state.
2.26 +
2.27 + self.nmi = 0 # no NMI asserted initially
2.28 + self.irq_vsync = 0 # no IRQ asserted initially
2.29 +
2.30 # Internal state.
2.31
2.32 self.cycle = 0 # counter within each 2MHz period
2.33 @@ -160,6 +165,8 @@
2.34 self.ram_address = 0 # address given to the RAM
2.35 self.data = 0 # data read from the RAM
2.36 self.buffer = [BLANK]*8 # pixel buffer for decoded RAM data
2.37 + self.have_pixels = 0 # whether pixel data has been read
2.38 + self.writing_pixels = 0 # whether pixel data can be written
2.39
2.40 self.reset_vertical()
2.41
2.42 @@ -278,8 +285,7 @@
2.43
2.44 def in_frame(self): return MIN_PIXELLINE <= self.y < MAX_PIXELLINE
2.45 def inside_frame(self): return MIN_PIXELLINE < self.y < MAX_PIXELLINE
2.46 - def read_pixels(self): return MIN_PIXELPOS - 8 <= self.x < MAX_PIXELPOS - 8 and self.in_frame()
2.47 - def make_pixels(self): return MIN_PIXELPOS <= self.x < MAX_PIXELPOS and self.in_frame()
2.48 + def read_pixels(self): return MIN_PIXELPOS <= self.x < MAX_PIXELPOS and self.in_frame()
2.49
2.50 def update(self):
2.51
2.52 @@ -302,7 +308,7 @@
2.53
2.54 # Clock management.
2.55
2.56 - access_ram = self.access == 0 and self.read_pixels() and not self.ssub
2.57 + access_ram = not self.nmi and self.access == 0 and self.read_pixels() and not self.ssub
2.58
2.59 # Set row address (for ULA access only).
2.60
2.61 @@ -313,6 +319,14 @@
2.62 if access_ram:
2.63 self.ram_address = (self.address & 0xff80) >> 7
2.64
2.65 + # Initialise the pixel buffer if appropriate.
2.66 +
2.67 + if not self.writing_pixels and self.have_pixels:
2.68 + self.xcounter = self.xscale
2.69 + self.buffer_index = 0
2.70 + self.fill_pixel_buffer()
2.71 + self.writing_pixels = 1
2.72 +
2.73 # Latch row address, set column address (for ULA access only).
2.74
2.75 elif self.cycle == 1:
2.76 @@ -374,6 +388,7 @@
2.77
2.78 if access_ram:
2.79 self.data = self.data | self.ram.data
2.80 + self.have_pixels = 1
2.81
2.82 # Advance to the next column.
2.83
2.84 @@ -402,6 +417,9 @@
2.85 self.hsync()
2.86 if self.y == 0:
2.87 self.vsync()
2.88 + self.irq_vsync = 0
2.89 + elif self.y == MAX_PIXELLINE:
2.90 + self.irq_vsync = 1
2.91
2.92 # Detect the end of hsync.
2.93
2.94 @@ -419,34 +437,27 @@
2.95
2.96 # Detect spacing between character rows.
2.97
2.98 - if not self.make_pixels() or self.ssub:
2.99 + if not self.writing_pixels or self.ssub:
2.100 self.video.colour = BLANK
2.101
2.102 # For pixels within the frame, obtain and output the value.
2.103
2.104 else:
2.105 - # Detect the start of the pixel generation.
2.106
2.107 - if self.x == MIN_PIXELPOS:
2.108 - self.xcounter = self.xscale
2.109 - self.buffer_index = 0
2.110 - self.fill_pixel_buffer()
2.111 + self.xcounter -= 1
2.112 + self.video.colour = self.buffer[self.buffer_index]
2.113
2.114 # Scale pixels horizontally, only accessing the next pixel value
2.115 # after the required number of scan positions.
2.116
2.117 - elif self.xcounter == 0:
2.118 + if self.xcounter == 0:
2.119 self.xcounter = self.xscale
2.120 self.buffer_index += 1
2.121
2.122 - # Fill the pixel buffer, assuming that data is available.
2.123 + # Handle the buffer empty condition.
2.124
2.125 if self.buffer_index >= self.buffer_limit:
2.126 - self.buffer_index = 0
2.127 - self.fill_pixel_buffer()
2.128 -
2.129 - self.xcounter -= 1
2.130 - self.video.colour = self.buffer[self.buffer_index]
2.131 + self.writing_pixels = 0
2.132
2.133 self.x += 1
2.134