1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/Electron.txt Mon Feb 13 22:25:37 2012 +0100
1.3 @@ -0,0 +1,60 @@
1.4 +Potential Design Improvements for the Acorn Electron
1.5 +====================================================
1.6 +
1.7 +The Acorn Electron was designed to be a variant of the BBC Microcomputer that
1.8 +was intended to be simpler, easier and cheaper to produce whilst retaining a
1.9 +degree of compatibility and offering many of the same features, principally
1.10 +the wide range of graphics modes, BBC BASIC, and extensible hardware and
1.11 +software capabilities. Upon its introduction in late 1981, the BBC Micro
1.12 +competed favourably against its immediate contemporaries, such as the ZX81 and
1.13 +VIC-20, as well as machines introduced slightly later, such as the ZX Spectrum
1.14 +and Commodore 64. By producing a less expensive machine that retained certain
1.15 +key features, the motivation was to bring BBC Micro technology to bear on the
1.16 +lower end of the home computer market, albeit approximately two years after
1.17 +its initial introduction.
1.18 +
1.19 +Unfortunately, various features were omitted from the Acorn Electron that made
1.20 +it less competitive than it could have been against a steadily improving range
1.21 +of competitors: multi-channel sound support, MODE 7 teletext, support for
1.22 +relatively smooth horizontal hardware scrolling (and other display control
1.23 +features), and the double-speed bus with interleaved CPU and video access.
1.24 +More RAM would also have been beneficial, although costly at the prices of the
1.25 +day. Such deficiencies outweighed the significant benefits of substantial
1.26 +software compatibility, and some of them effectively curtailed that
1.27 +compatibility by making even reasonably well-written software titles
1.28 +effectively unusable, particularly games relying on the BBC Micro's hardware
1.29 +scrolling capabilities, including "official" Acornsoft titles.
1.30 +
1.31 +In hindsight, numerous features could be suggested that would make the
1.32 +Electron more competitive, but many of these features would incur a
1.33 +substantial cost. For example, giving the Electron 64K of RAM would have
1.34 +increased the price substantially. Introducing the double-speed bus and faster
1.35 +memory may also have increased the price in a prohibitive fashion. Thus, it
1.36 +becomes worthwhile to consider minimal alterations to the machine's
1.37 +specification that offer the greatest benefits for the least additional cost.
1.38 +
1.39 +Improving System Performance
1.40 +----------------------------
1.41 +
1.42 +Although RAM is accessed by the CPU at 1MHz, ROM is accessed at 2MHz. Thus,
1.43 +deploying software that runs from ROM can potentially provide significant
1.44 +performance benefits. Since the unexpanded Electron provides no convenient
1.45 +means of installing ROM-based software - the Plus 1 and other expansion units
1.46 +offered ROM cartridge slots, and various expansions provided ROM sockets - the
1.47 +improved Electron would ideally need to offer a ROM cartridge slot as part of
1.48 +the unexpanded machine. A side-benefit of adding this feature to the base
1.49 +machine would arguably be an increased demand for cartridge-based software,
1.50 +potentially at a slightly higher price and also offering additional hardware
1.51 +features if necessary, thus making any cost incurred in the manufacture of the
1.52 +base unit more bearable.
1.53 +
1.54 +The Slogger/Elektuur turbo board modified the system to permit the CPU to
1.55 +access the bottom 8K of RAM without interruption by the ULA. This feature,
1.56 +already known at Acorn during the Electron's design period, permitted
1.57 +substantial improvements to performance and could also be incorporated into an
1.58 +improved Electron, although it presumably needs motherboard-level changes.
1.59 +Such turbo boards may have employed an additional RAM chip to avoid
1.60 +complicated changes to the memory access logic, since the ULA appears to
1.61 +access four memory chips at once to provide each byte, and it is therefore not
1.62 +possible to just "borrow" one of the chips in order to isolate 8K of RAM for
1.63 +direct access by the CPU.
2.1 --- a/ULA.txt Tue Dec 20 01:01:28 2011 +0100
2.2 +++ b/ULA.txt Mon Feb 13 22:25:37 2012 +0100
2.3 @@ -1,15 +1,141 @@
2.4 Timing
2.5 ------
2.6
2.7 -According to the above (15.3.2 in the AUG), there are 312 scanlines, 256 of
2.8 -which are used to generate pixel data. At 50Hz, this means that 128 cycles are
2.9 -used to produce pixel data (2000000 / 50 = 40000; 40000 / 312 ~= 128). This is
2.10 -consistent with the observation that each scanline requires at most 80 bytes
2.11 -of data, and that the ULA is apparently busy for 40 out of 64 microseconds in
2.12 -each scanline.
2.13 +According to 15.3.2 in the Advanced User Guide, there are 312 scanlines, 256
2.14 +of which are used to generate pixel data. At 50Hz, this means that 128 cycles
2.15 +are spent on each scanline (2000000 cycles / 50 = 40000 cycles; 40000 cycles /
2.16 +312 ~= 128 cycles). This is consistent with the observation that each scanline
2.17 +requires at most 80 bytes of data, and that the ULA is apparently busy for 40
2.18 +out of 64 microseconds in each scanline.
2.19 +
2.20 +Access to RAM involves accessing four 64Kb dynamic RAM devices (IC4 to IC7,
2.21 +each providing two bits of each byte) using two cycles within the 500ns period
2.22 +of the 2MHz clock to complete each access operation. Since the CPU and ULA
2.23 +have to take turns in accessing the RAM in MODE 4, 5 and 6, the CPU must
2.24 +effectively run at 1MHz (since every other 500ns period involves the ULA
2.25 +accessing RAM). The CPU is driven by an external clock (IC8) whose 16MHz
2.26 +frequency is divided by the ULA (IC1) depending on the screen mode in use.
2.27 +
2.28 +Each 16MHz cycle is approximately 62.5ns. To access the memory, the following
2.29 +patterns corresponding to 16MHz cycles are required:
2.30 +
2.31 + Time (ns): 0-------------- 500------------ ...
2.32 + 2 MHz cycle: 0 1 ...
2.33 + 16 MHz cycle: 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 ...
2.34 + ~RAS: 0 1 0 1 ...
2.35 + ~CAS: 0 1 0 1 0 1 0 1 ...
2.36 + A B B A B B ...
2.37 + F S F S ...
2.38 + a b b a b b ...
2.39 +
2.40 +Here, "A" indicates the row and column addresses being latched into the RAM
2.41 +(on a negative edge for ~RAS and ~CAS respectively), and "B" indicates the
2.42 +second column address being latched into the RAM. Presumably, the first and
2.43 +second half-bytes can be read at "F" and "S" respectively, and the row and
2.44 +column addresses must be made available at "a" and "b" respectively at the
2.45 +latest.
2.46 +
2.47 +Note that the Service Manual refers to the negative edge of RAS and CAS, but
2.48 +the datasheet for the similar TM4164EC4 product shows latching on the negative
2.49 +edge of ~RAS and ~CAS. It is possible that the Service Manual also intended to
2.50 +communicate the latter behaviour. In the TM4164EC4 datasheet, it appears that
2.51 +"page mode" provides the appropriate behaviour for that particular product.
2.52 +
2.53 +Video Timing
2.54 +------------
2.55 +
2.56 +According to 8.7 in the Service Manual, and the PAL Wikipedia page,
2.57 +approximately 4.7µs is used for the sync pulse, 5.7µs for the "back porch"
2.58 +(including the "colour burst"), and 1.65µs for the "front porch", totalling
2.59 +12.05µs and thus leaving 51.95µs for the active video signal for each
2.60 +scanline. As the Service Manual suggests in the oscilloscope traces, the
2.61 +display information is transmitted more or less centred within the active
2.62 +video period since the ULA will only be providing pixel data for 40µs in each
2.63 +scanline.
2.64 +
2.65 +Each 62.5ns cycle happens to correspond to 64µs divided by 1024, meaning that
2.66 +each scanline can be divided into 1024 cycles, although only 640 at most are
2.67 +actively used to provide pixel data. Pixel data production should only occur
2.68 +within a certain period on each scanline, approximately 262 cycles after the
2.69 +start of hsync:
2.70 +
2.71 + active video period = 51.95µs
2.72 + pixel data period = 40µs
2.73 + total silent period = 51.95µs - 40µs = 11.95µs
2.74 + silent periods (before and after) = 11.95µs / 2 = 5.975µs
2.75 + hsync and back porch period = 4.7µs + 5.7µs = 10.4µs
2.76 + time before pixel data period = 10.4µs + 5.975µs = 16.375µs
2.77 + pixel data period start cycle = 16.375µs / 62.5ns = 262
2.78 +
2.79 +By choosing a number divisible by 8, the RAM access mechanism can be
2.80 +synchronised with the pixel production. Thus, 264 is a more appropriate start
2.81 +cycle.
2.82 +
2.83 +The "vertical blanking period", meaning the period before picture information
2.84 +in each field is 25 lines out of 312 (strictly 312.5) and thus lasts for
2.85 +1.6ms. Of this, 2.5 lines occur before the vsync (field sync) which also lasts
2.86 +for 2.5 lines. Thus, the first visible scanline on the first field of a frame
2.87 +occurs half way through the 23rd scanline period measured from the start of
2.88 +vsync:
2.89 +
2.90 + 10 20 23
2.91 + Line in frame: 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
2.92 + Line from 1: 0 22 3
2.93 + Line on screen: .:::::VVVVV::::: 12233445566
2.94 + |_________________________________________________|
2.95 + 25 line vertical blanking period
2.96 +
2.97 +In the second field of a frame, the first visible scanline coincides with the
2.98 +24th scanline period measured from the start of line 313 in the frame:
2.99 +
2.100 + 310 336
2.101 + Line in frame: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
2.102 + Line from 313: 0 23
2.103 + Line on screen: 88:::::VVVVV:::: 11223344
2.104 + 288 | |
2.105 + |_________________________________________________|
2.106 + 25 line vertical blanking period
2.107 +
2.108 +In order to consider only full lines, we might consider the start of each
2.109 +frame to occur 23 lines after the start of vsync.
2.110 +
2.111 +Again, it is likely that pixel data production should only occur on scanlines
2.112 +within a certain period on each frame. The "625/50" document indicates that
2.113 +only a certain region is "safe" to use, suggesting a vertically centred region
2.114 +with approximately 15 blank lines above and below the picture. Thus, the start
2.115 +of the picture could be chosen as 38 lines after the start of vsync.
2.116
2.117 See: Acorn Electron Advanced User Guide
2.118 See: http://mdfs.net/Docs/Comp/Electron/Techinfo.htm
2.119 +See: http://en.wikipedia.org/wiki/PAL
2.120 +See: http://en.wikipedia.org/wiki/Analog_television#Structure_of_a_video_signal
2.121 +See: The 625/50 PAL Video Signal and TV Compatible Graphics Modes
2.122 + http://lipas.uwasa.fi/~f76998/video/modes/
2.123 +See: PAL TV timing and voltages
2.124 + http://www.retroleum.co.uk/electronics-articles/pal-tv-timing-and-voltages/
2.125 +See: Line Standards
2.126 + http://www.pembers.freeserve.co.uk/World-TV-Standards/Line-Standards.html
2.127 +See: TM4164EC4 65,536 by 4-Bit Dynamic RAM Module
2.128 + http://www.datasheetarchive.com/dl/Datasheets-112/DSAP0051030.pdf
2.129 +See: Acorn Electron Service Manual
2.130 + http://acorn.chriswhy.co.uk/docs/Acorn/Manuals/Acorn_ElectronSM.pdf
2.131 +
2.132 +Shadow/Expanded Memory
2.133 +----------------------
2.134 +
2.135 +The Electron exposes all sixteen address lines and all eight data lines
2.136 +through the expansion bus. Using such lines, it is possible to provide
2.137 +additional memory - typically sideways ROM and RAM - on expansion cards and
2.138 +through cartridges, although the official cartridge specification provides
2.139 +fewer address lines and only seeks to provide access to memory in 16K units.
2.140 +
2.141 +Various modifications and upgrades were developed to offer "turbo"
2.142 +capabilities to the Electron, permitting the CPU to access a separate 8K of
2.143 +RAM at 2MHz, presumably preventing access to the low 8K of RAM accessible via
2.144 +the ULA through additional logic. However, an enhanced ULA might support
2.145 +independent CPU access to memory over the expansion bus by allowing itself to
2.146 +be discharged from providing access to memory, potentially for a range of
2.147 +addresses, and for the CPU to communicate with external memory uninterrupted.
2.148
2.149 Hardware Scrolling
2.150 ------------------
2.151 @@ -151,27 +277,28 @@
2.152 that hardware to reduce the load on the system CPU which was responsible for
2.153 producing the video output.
2.154
2.155 -Hardware Sprites and Colour Planes
2.156 -----------------------------------
2.157 +Hardware Sprites
2.158 +----------------
2.159
2.160 An enhanced ULA might provide hardware sprites, but this would be done in an
2.161 way that is incompatible with the standard ULA, since no &FE*X locations are
2.162 -available for allocation. In a special ULA mode, one might allocate a pair of
2.163 -locations (for example, &FE20 and &FE21) as a pair of registers referencing a
2.164 -region of memory from which a sprite might be found and potentially copied
2.165 -into internal RAM, with other locations (for example, &FE22 and &FE23)
2.166 -providing the size of the region. Alternatively, one might write the region
2.167 -location and size through a single ULA location, with the ULA being put into a
2.168 -particular state after each write. For example: read LSB of region, read MSB
2.169 -of region, read size, read height.
2.170 +available for allocation. To keep the facility simple, hardware sprites would
2.171 +have a standard byte width and height.
2.172 +
2.173 +The specification of sprites could involve the reservation of 16 locations
2.174 +(for example, &FE20-F) specifying a fixed number of eight sprites, with each
2.175 +location pair referring to the sprite data. By limiting the ULA to dealing
2.176 +with a fixed number of sprites, the work required inside the ULA would be
2.177 +reduced since it would avoid having to deal with arbitrary numbers of sprites.
2.178
2.179 -Providing hardware sprites can be awkward without having some kind of working
2.180 -area, since the ULA would need to remember where each sprite is to be plotted
2.181 -and then deduce which sprites would be contributing to any given pixel. An
2.182 -alternative is to use memory into which the sprites would be plotted, and this
2.183 -memory would be combined with the main screen memory, taking a particular
2.184 -colour as the "colourkey" which is to be considered transparent, and only
2.185 -overwriting the main screen pixels with pixel values for other colours.
2.186 +The principal limitation on providing hardware sprites is that of having to
2.187 +obtain sprite data, given that the ULA is usually required to retrieve screen
2.188 +data, and given the lack of memory bandwidth available to retrieve sprite data
2.189 +(particularly from multiple sprites supposedly at the same position) and
2.190 +screen data simultaneously. Although the ULA could potentially read sprite
2.191 +data and screen data in alternate memory accesses in screen modes where the
2.192 +bandwidth is not already fully utilised, this would result in a degradation of
2.193 +performance.
2.194
2.195 Additional Screen Mode Configurations
2.196 -------------------------------------
2.197 @@ -243,6 +370,13 @@
2.198 to apply attribute data to the first column, the initial 8 cycles might be
2.199 configured to not produce pixel values.
2.200
2.201 +For an entire character, attribute data need only be read for the first row of
2.202 +pixels for a character. The subsequent rows would have attribute information
2.203 +applied to them, although this would require the attribute data to be stored
2.204 +in some kind of buffer. Thus, the following access pattern would be observed:
2.205 +
2.206 + Cycle: A B ... _ B ... _ B ... _ B ... _ B ... _ B ... _ B ... _ B ...
2.207 +
2.208 A whole byte used for colour information for a whole character would result in
2.209 a choice of 256 colours, and this might be somewhat excessive. By only reading
2.210 attribute bytes at every other opportunity, a choice of 16 colours could be
2.211 @@ -252,31 +386,23 @@
2.212 Reads: B A B -
2.213 Pixels: 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7
2.214
2.215 +Further reductions in attribute data access, offering 4 colours for every
2.216 +character in a four character block, for example, might also be worth
2.217 +considering.
2.218 +
2.219 Consider the following configurations for screen modes with a colour depth of
2.220 1 bit per pixel for bitmap information:
2.221
2.222 - Screen width Columns Scaling Bytes (B) Bytes (A) Colours
2.223 - ------------ ------- ------- --------- --------- -------
2.224 - 320 40 x2 40 40 256
2.225 - 320 40 x2 40 20 16
2.226 - 208 26 x3 26 26 256
2.227 - 208 26 x3 26 13 16
2.228 + Screen width Columns Scaling Bytes (B) Bytes (A) Colours Screen start
2.229 + ------------ ------- ------- --------- --------- ------- ------------
2.230 + 320 40 x2 40 40 256 &5300
2.231 + 320 40 x2 40 20 16 &5580 -> &5500
2.232 + 320 40 x2 40 10 4 &56C0 -> &5600
2.233 + 208 26 x3 26 26 256 &62C0 -> &6200
2.234 + 208 26 x3 26 13 16 &6460 -> &6400
2.235
2.236 -Here, a mode resembling MODE 4 would occupy the same amount of space as MODE 1
2.237 -if 40 attribute (A) bytes were read in addition to the 40 bitmap (B) bytes.
2.238 -This would offer limited benefit over the mode with the higher colour depth,
2.239 -especially if palette definition lists were also available. However, if only
2.240 -20 attribute bytes were read, the screen memory would be only 150% of the
2.241 -original.
2.242 -
2.243 -Similarly, if an additional configuration pixel-tripled mode were to require
2.244 -as many attribute bytes as bitmap bytes, it would occupy as much space as its
2.245 -equivalent with twice the colour depth. However, by requiring only 13
2.246 -attribute bytes for every 26 bitmap bytes, it would actually be more efficient
2.247 -than MODE 6 (a screen start address of &6600 versus MODE 6's &6000).
2.248 -
2.249 -MODE 7 Emulation
2.250 -----------------
2.251 +MODE 7 Emulation using Character Attributes
2.252 +-------------------------------------------
2.253
2.254 If the scheme of applying attributes to character regions were employed to
2.255 emulate MODE 7, in conjunction with the MODE 6 display technique, the
2.256 @@ -284,11 +410,13 @@
2.257
2.258 Screen width Columns Rows Bytes (B) Bytes (A) Colours Screen start
2.259 ------------ ------- ---- --------- --------- ------- ------------
2.260 - 320 40 25 40 20 16 &5120 -> &5100
2.261 + 320 40 25 40 20 16 &5ECC -> &5E00
2.262 + 320 40 25 40 10 4 &5FC6 -> &5F00
2.263
2.264 -Although this requires much more memory than MODE 7 (12000 bytes versus
2.265 -MODE 7's 1000 bytes) and more memory than even MODE 6, it would at least make
2.266 -a limited 40-column multicolour mode available as a substitute for MODE 7.
2.267 +Although this requires much more memory than MODE 7 (8500 bytes versus MODE
2.268 +7's 1000 bytes), it does not need much more memory than MODE 6, and it would
2.269 +at least make a limited 40-column multicolour mode available as a substitute
2.270 +for MODE 7.
2.271
2.272 Enhanced Graphics and Mode Layouts
2.273 ----------------------------------
2.274 @@ -362,9 +490,11 @@
2.275
2.276 RA0...RA7 (address lines for sending both row and column addresses to the RAM)
2.277
2.278 - RAS (row address strobe setting the row address on a negative edge)
2.279 + RAS (row address strobe setting the row address on a negative edge - see the
2.280 + timing notes)
2.281
2.282 - CAS (column address strobe setting the column address on a negative edge)
2.283 + CAS (column address strobe setting the column address on a negative edge -
2.284 + see the timing notes)
2.285
2.286 WE (sets write enable with logic 0, read with logic 1)
2.287
3.1 --- a/main.py Tue Dec 20 01:01:28 2011 +0100
3.2 +++ b/main.py Mon Feb 13 22:25:37 2012 +0100
3.3 @@ -52,30 +52,34 @@
3.4
3.5 ula.set_mode(2); ula.reset()
3.6
3.7 - ula.fill(0x3000, 0x5800 - 320, encode((1, 6), 4))
3.8 - ula.fill(0x5800 - 320, 0x8000, encode((2, 7), 4))
3.9 + ula.ram.fill(0x3000, 0x5800 - 320, encode((1, 6), 4))
3.10 + ula.ram.fill(0x5800 - 320, 0x8000, encode((2, 7), 4))
3.11 ula_screen = update(ula)
3.12 update_screen(screen, ula_screen)
3.13 + print "Screen updated."
3.14 mainloop()
3.15
3.16 ula.screen_start = 0x3000 + 2
3.17 ula_screen = update(ula)
3.18 update_screen(screen, ula_screen)
3.19 + print "Screen updated."
3.20 mainloop()
3.21
3.22 # Test MODE 6.
3.23
3.24 ula.set_mode(6); ula.reset()
3.25
3.26 - ula.fill(0x6000, 0x6f00 + 160, encode((1, 0, 1, 1, 0, 0, 1, 1), 1))
3.27 - ula.fill(0x6f00 + 160, 0x7f40, encode((1, 0, 1, 0, 1, 0, 1, 0), 1))
3.28 + ula.ram.fill(0x6000, 0x6f00 + 160, encode((1, 0, 1, 1, 0, 0, 1, 1), 1))
3.29 + ula.ram.fill(0x6f00 + 160, 0x7f40, encode((1, 0, 1, 0, 1, 0, 1, 0), 1))
3.30 ula_screen = update(ula)
3.31 update_screen(screen, ula_screen)
3.32 + print "Screen updated."
3.33 mainloop()
3.34
3.35 ula.screen_start = 0x6f00 + 160
3.36 ula_screen = update(ula)
3.37 update_screen(screen, ula_screen)
3.38 + print "Screen updated."
3.39 mainloop()
3.40
3.41 # vim: tabstop=4 expandtab shiftwidth=4
4.1 --- a/ula.py Tue Dec 20 01:01:28 2011 +0100
4.2 +++ b/ula.py Mon Feb 13 22:25:37 2012 +0100
4.3 @@ -9,11 +9,23 @@
4.4
4.5 LINES_PER_ROW = 8 # the number of pixel lines per character row
4.6 MAX_HEIGHT = 256 # the height of the screen in pixels
4.7 +MAX_WIDTH = 640 # the width of the screen in pixels
4.8 +
4.9 +MAX_CSYNC = 2 # the scanline during which vsync ends
4.10 +MIN_PIXELLINE = 38 # the first scanline involving pixel generation
4.11 MAX_SCANLINE = 312 # the number of scanlines in each frame
4.12 -MAX_WIDTH = 640 # the width of the screen in pixels
4.13 -MAX_SCANPOS = 1024 # the number of positions in each scanline
4.14 +
4.15 +MAX_PIXELLINE = MIN_PIXELLINE + MAX_HEIGHT
4.16 +
4.17 +MAX_HSYNC = 75 # the number of cycles in each hsync period
4.18 +MIN_PIXELPOS = 264 # the first cycle involving pixel generation
4.19 +MAX_SCANPOS = 1024 # the number of cycles in each scanline
4.20 +
4.21 +MAX_PIXELPOS = MIN_PIXELPOS + MAX_WIDTH
4.22 +
4.23 SCREEN_LIMIT = 0x8000 # the first address after the screen memory
4.24 MAX_MEMORY = 0x10000 # the number of addressable memory locations
4.25 +MAX_RAM = 0x10000 # the number of addressable RAM locations (64Kb in each IC)
4.26 BLANK = (0, 0, 0)
4.27
4.28 def update(ula):
4.29 @@ -43,19 +55,70 @@
4.30 self.colour = BLANK
4.31 self.csync = 1
4.32 self.hs = 1
4.33 - self.reset()
4.34 + self.x = 0
4.35 + self.y = 0
4.36
4.37 - def reset(self):
4.38 - self.pos = 0
4.39 + def set_csync(self, value):
4.40 + if self.csync and not value:
4.41 + self.y = 0
4.42 + self.pos = 0
4.43 + self.csync = value
4.44 +
4.45 + def set_hs(self, value):
4.46 + if self.hs and not value:
4.47 + self.x = 0
4.48 + self.y += 1
4.49 + self.hs = value
4.50
4.51 def update(self):
4.52 - if self.csync:
4.53 - if self.hs:
4.54 + if MIN_PIXELLINE <= self.y < MAX_PIXELLINE:
4.55 + if MIN_PIXELPOS <= self.x < MAX_PIXELPOS:
4.56 self.screen[self.pos] = self.colour[0]; self.pos += 1
4.57 self.screen[self.pos] = self.colour[1]; self.pos += 1
4.58 self.screen[self.pos] = self.colour[2]; self.pos += 1
4.59 - else:
4.60 - self.pos = 0
4.61 + self.x += 1
4.62 +
4.63 +class RAM:
4.64 +
4.65 + """
4.66 + A class representing the RAM circuits (IC4 to IC7). Each circuit
4.67 + traditionally holds 64 kilobits, with two accesses required to read 2 bits
4.68 + from each in order to obtain a whole byte. Here, we model the circuits with
4.69 + a list of 65536 half-bytes with each bit representing a bit stored on a
4.70 + separate IC.
4.71 + """
4.72 +
4.73 + def __init__(self):
4.74 +
4.75 + "Initialise the RAM circuits."
4.76 +
4.77 + self.memory = [0] * MAX_RAM
4.78 + self.row_address = 0
4.79 + self.column_address = 0
4.80 + self.data = 0
4.81 +
4.82 + def row_select(self, address):
4.83 + self.row_address = address
4.84 +
4.85 + def row_deselect(self):
4.86 + pass
4.87 +
4.88 + def column_select(self, address):
4.89 + self.column_address = address
4.90 +
4.91 + # Read the data.
4.92 +
4.93 + self.data = self.memory[self.row_address << 8 | self.column_address]
4.94 +
4.95 + def column_deselect(self):
4.96 + pass
4.97 +
4.98 + # Convenience methods.
4.99 +
4.100 + def fill(self, start, end, value):
4.101 + for i in xrange(start, end):
4.102 + self.memory[i << 1] = value >> 4
4.103 + self.memory[i << 1 | 0x1] = value & 0xf
4.104
4.105 class ULA:
4.106
4.107 @@ -74,25 +137,31 @@
4.108
4.109 palette = range(0, 8) * 2
4.110
4.111 - def __init__(self, memory, video):
4.112 + def __init__(self, ram, video):
4.113
4.114 - "Initialise the ULA with the given 'memory' and 'video'."
4.115 + "Initialise the ULA with the given 'ram' and 'video' instances."
4.116
4.117 - self.memory = memory
4.118 + self.ram = ram
4.119 self.video = video
4.120 self.set_mode(6)
4.121
4.122 # Internal state.
4.123
4.124 - self.buffer = [(0, 0, 0)] * 8
4.125 -
4.126 self.reset()
4.127
4.128 def reset(self):
4.129
4.130 "Reset the ULA."
4.131
4.132 - self.vsync()
4.133 + # Internal state.
4.134 +
4.135 + self.cycle = 0 # counter within each 2MHz period
4.136 + self.access = 0 # counter used to determine whether a byte needs reading
4.137 + self.ram_address = 0 # address given to the RAM
4.138 + self.data = 0 # data read from the RAM
4.139 + self.buffer = [BLANK]*8 # pixel buffer for decoded RAM data
4.140 +
4.141 + self.reset_vertical()
4.142
4.143 def set_mode(self, mode):
4.144
4.145 @@ -116,6 +185,7 @@
4.146 self.width, self.depth, rows = ULA.modes[mode]
4.147
4.148 columns = (self.width * self.depth) / 8 # bits read -> bytes read
4.149 + self.access_frequency = 80 / columns # cycle frequency for reading bytes
4.150 row_size = columns * LINES_PER_ROW
4.151
4.152 # Memory access configuration.
4.153 @@ -139,7 +209,21 @@
4.154
4.155 self.buffer_limit = 8 / self.depth
4.156
4.157 - def vsync(self):
4.158 + def vsync(self, value=0):
4.159 +
4.160 + "Signal the start of a frame."
4.161 +
4.162 + self.csync = value
4.163 + self.video.set_csync(value)
4.164 +
4.165 + def hsync(self, value=0):
4.166 +
4.167 + "Signal the end of a scanline."
4.168 +
4.169 + self.hs = value
4.170 + self.video.set_hs(value)
4.171 +
4.172 + def reset_vertical(self):
4.173
4.174 "Signal the start of a frame."
4.175
4.176 @@ -147,18 +231,17 @@
4.177 self.line = self.line_start % LINES_PER_ROW
4.178 self.ssub = 0
4.179 self.y = 0
4.180 - self.reset_horizontal()
4.181 -
4.182 - # Signal the video circuit.
4.183 + self.x = 0
4.184
4.185 - self.csync = self.video.csync = 1
4.186 + def reset_horizontal(self):
4.187
4.188 - def hsync(self):
4.189 -
4.190 - "Signal the end of a scanline."
4.191 + "Reset horizontal state within the active region of the frame."
4.192
4.193 self.y += 1
4.194 - self.reset_horizontal()
4.195 + self.x = 0
4.196 +
4.197 + if not self.inside_frame():
4.198 + return
4.199
4.200 # Support spacing between character rows.
4.201
4.202 @@ -193,70 +276,176 @@
4.203
4.204 self.line_start = self.address
4.205
4.206 - def reset_horizontal(self):
4.207 -
4.208 - "Reset horizontal state."
4.209 -
4.210 - self.x = 0
4.211 - self.buffer_index = self.buffer_limit # need refill
4.212 -
4.213 - # Signal the video circuit.
4.214 -
4.215 - self.hs = self.video.hs = 1
4.216 + def in_frame(self): return MIN_PIXELLINE <= self.y < MAX_PIXELLINE
4.217 + def inside_frame(self): return MIN_PIXELLINE < self.y < MAX_PIXELLINE
4.218 + def read_pixels(self): return MIN_PIXELPOS - 8 <= self.x < MAX_PIXELPOS - 8 and self.in_frame()
4.219 + def make_pixels(self): return MIN_PIXELPOS <= self.x < MAX_PIXELPOS and self.in_frame()
4.220
4.221 def update(self):
4.222
4.223 """
4.224 - Update the pixel colour by reading from the pixel buffer.
4.225 + Update the state of the ULA for each clock cycle. This involves updating
4.226 + the pixel colour by reading from the pixel buffer.
4.227 """
4.228
4.229 - # Detect the end of the line.
4.230 + # Detect the end of the scanline.
4.231 +
4.232 + if self.x == MAX_SCANPOS:
4.233 + self.reset_horizontal()
4.234 +
4.235 + # Detect the end of the frame.
4.236 +
4.237 + if self.y == MAX_SCANLINE:
4.238 + self.reset_vertical()
4.239 +
4.240 +
4.241 +
4.242 + # Clock management.
4.243 +
4.244 + access_ram = self.access == 0 and self.read_pixels() and not self.ssub
4.245 +
4.246 + # Set row address (for ULA access only).
4.247 +
4.248 + if self.cycle == 0:
4.249 +
4.250 + # NOTE: Propagate CPU address here.
4.251 +
4.252 + if access_ram:
4.253 + self.ram_address = (self.address & 0xff80) >> 7
4.254 +
4.255 + # Latch row address, set column address (for ULA access only).
4.256 +
4.257 + elif self.cycle == 1:
4.258 +
4.259 + # NOTE: Permit CPU access here.
4.260
4.261 - if self.x >= MAX_WIDTH:
4.262 - if self.x == MAX_WIDTH:
4.263 - self.hs = self.video.hs = 0
4.264 + if access_ram:
4.265 + self.ram.row_select(self.ram_address)
4.266 +
4.267 + # NOTE: Propagate CPU address here.
4.268 +
4.269 + if access_ram:
4.270 + self.ram_address = (self.address & 0x7f) << 1
4.271 +
4.272 + # Latch column address.
4.273 +
4.274 + elif self.cycle == 2:
4.275 +
4.276 + # NOTE: Permit CPU access here.
4.277
4.278 - # Detect the end of the scanline.
4.279 + if access_ram:
4.280 + self.ram.column_select(self.ram_address)
4.281 +
4.282 + # Read 4 bits (for ULA access only).
4.283 + # NOTE: Perhaps map alternate bits, not half-bytes.
4.284 +
4.285 + elif self.cycle == 3:
4.286 +
4.287 + # NOTE: Propagate CPU data here.
4.288 +
4.289 + if access_ram:
4.290 + self.data = self.ram.data << 4
4.291 +
4.292 + # Set column address (for ULA access only).
4.293 +
4.294 + elif self.cycle == 4:
4.295 + self.ram.column_deselect()
4.296
4.297 - elif self.x == MAX_SCANPOS:
4.298 - self.hsync()
4.299 + # NOTE: Propagate CPU address here.
4.300 +
4.301 + if access_ram:
4.302 + self.ram_address = (self.address & 0x7f) << 1 | 0x1
4.303 +
4.304 + # Latch column address.
4.305 +
4.306 + elif self.cycle == 5:
4.307 +
4.308 + # NOTE: Permit CPU access here.
4.309 +
4.310 + if access_ram:
4.311 + self.ram.column_select(self.ram_address)
4.312
4.313 - # Detect the end of the frame.
4.314 + # Read 4 bits (for ULA access only).
4.315 + # NOTE: Perhaps map alternate bits, not half-bytes.
4.316 +
4.317 + elif self.cycle == 6:
4.318 +
4.319 + # NOTE: Propagate CPU data here.
4.320 +
4.321 + if access_ram:
4.322 + self.data = self.data | self.ram.data
4.323 +
4.324 + # Advance to the next column.
4.325 +
4.326 + self.address += LINES_PER_ROW
4.327 + self.wrap_address()
4.328 +
4.329 + # Reset addresses.
4.330
4.331 - if self.y == MAX_SCANLINE:
4.332 - self.vsync()
4.333 + elif self.cycle == 7:
4.334 + self.ram.column_deselect()
4.335 + self.ram.row_deselect()
4.336 +
4.337 + # Update the RAM access controller.
4.338 +
4.339 + self.access = (self.access + 1) % self.access_frequency
4.340 +
4.341 + self.cycle = (self.cycle + 1) % 8
4.342 +
4.343 +
4.344 +
4.345 + # Video signalling.
4.346 +
4.347 + # Detect any sync conditions.
4.348
4.349 - # Detect the end of the screen.
4.350 + if self.x == 0:
4.351 + self.hsync()
4.352 + if self.y == 0:
4.353 + self.vsync()
4.354 +
4.355 + # Detect the end of hsync.
4.356
4.357 - elif self.y == MAX_HEIGHT:
4.358 - self.csync = self.video.csync = 0
4.359 + elif self.x == MAX_HSYNC:
4.360 + self.hsync(1)
4.361 +
4.362 + # Detect the end of vsync.
4.363 +
4.364 + elif self.y == MAX_CSYNC and self.x == MAX_SCANPOS / 2:
4.365 + self.vsync(1)
4.366 +
4.367 +
4.368 +
4.369 + # Pixel production.
4.370
4.371 # Detect spacing between character rows.
4.372
4.373 - if self.ssub:
4.374 + if not self.make_pixels() or self.ssub:
4.375 self.video.colour = BLANK
4.376
4.377 - # Detect horizontal and vertical sync conditions.
4.378 -
4.379 - elif not self.hs or not self.csync:
4.380 - pass
4.381 -
4.382 # For pixels within the frame, obtain and output the value.
4.383
4.384 else:
4.385 + # Detect the start of the pixel generation.
4.386 +
4.387 + if self.x == MIN_PIXELPOS:
4.388 + self.xcounter = self.xscale
4.389 + self.buffer_index = 0
4.390 + self.fill_pixel_buffer()
4.391
4.392 # Scale pixels horizontally, only accessing the next pixel value
4.393 # after the required number of scan positions.
4.394
4.395 - if self.x % self.xscale == 0:
4.396 + elif self.xcounter == 0:
4.397 + self.xcounter = self.xscale
4.398 self.buffer_index += 1
4.399
4.400 - # Fill the buffer once all values have been read.
4.401 + # Fill the pixel buffer, assuming that data is available.
4.402
4.403 - if self.buffer_index >= self.buffer_limit:
4.404 - self.buffer_index = 0
4.405 - self.fill_pixel_buffer()
4.406 + if self.buffer_index >= self.buffer_limit:
4.407 + self.buffer_index = 0
4.408 + self.fill_pixel_buffer()
4.409
4.410 + self.xcounter -= 1
4.411 self.video.colour = self.buffer[self.buffer_index]
4.412
4.413 self.x += 1
4.414 @@ -268,27 +457,17 @@
4.415 mode.
4.416 """
4.417
4.418 - byte_value = self.memory[self.address]
4.419 + byte_value = self.data # which should have been read automatically
4.420
4.421 i = 0
4.422 for colour in decode(byte_value, self.depth):
4.423 self.buffer[i] = get_physical_colour(ULA.palette[colour])
4.424 i += 1
4.425
4.426 - # Advance to the next column.
4.427 -
4.428 - self.address += LINES_PER_ROW
4.429 - self.wrap_address()
4.430 -
4.431 def wrap_address(self):
4.432 if self.address >= SCREEN_LIMIT:
4.433 self.address -= self.screen_size
4.434
4.435 - # Convenience methods.
4.436 -
4.437 - def fill(self, start, end, value):
4.438 - fill(self.memory, start, end, value)
4.439 -
4.440 def get_physical_colour(value):
4.441
4.442 """
4.443 @@ -345,7 +524,7 @@
4.444
4.445 "Return a ULA initialised with a memory array and video."
4.446
4.447 - return ULA(get_memory(), get_video())
4.448 + return ULA(get_ram(), get_video())
4.449
4.450 def get_video():
4.451
4.452 @@ -353,24 +532,19 @@
4.453
4.454 return Video()
4.455
4.456 -def get_memory():
4.457 -
4.458 - "Return an array representing the computer's memory."
4.459 -
4.460 - return [0] * MAX_MEMORY
4.461 +def get_ram():
4.462
4.463 -def fill(memory, start, end, value):
4.464 - i = start
4.465 - while i < end:
4.466 - memory[i] = value
4.467 - i += 1
4.468 + "Return an instance representing the computer's RAM hardware."
4.469 +
4.470 + return RAM()
4.471
4.472 # Test program providing coverage (necessary for compilers like Shedskin).
4.473
4.474 if __name__ == "__main__":
4.475 ula = get_ula()
4.476 ula.set_mode(6)
4.477 - ula.fill(0x6000, 0x8000, encode((1, 0, 1, 0, 1, 0, 1, 0), 1))
4.478 + ula.reset()
4.479 + ula.ram.fill(0x6000, 0x8000, encode((1, 0, 1, 0, 1, 0, 1, 0), 1))
4.480
4.481 # Make a simple two-dimensional array of tuples (three-dimensional in pygame
4.482 # terminology).