# HG changeset patch # User Paul Boddie # Date 1329168337 -3600 # Node ID be29270ef9f68aae9eed2175a0e2fd0ddec64da4 # Parent da771131998bd703e09ab64c2d18c8757bd510dc# Parent 99b0ecdba781b21bdf32c25df7f1a77c33b26b96 Merged RAM and video changes. diff -r da771131998b -r be29270ef9f6 Electron.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Electron.txt Mon Feb 13 22:25:37 2012 +0100 @@ -0,0 +1,60 @@ +Potential Design Improvements for the Acorn Electron +==================================================== + +The Acorn Electron was designed to be a variant of the BBC Microcomputer that +was intended to be simpler, easier and cheaper to produce whilst retaining a +degree of compatibility and offering many of the same features, principally +the wide range of graphics modes, BBC BASIC, and extensible hardware and +software capabilities. Upon its introduction in late 1981, the BBC Micro +competed favourably against its immediate contemporaries, such as the ZX81 and +VIC-20, as well as machines introduced slightly later, such as the ZX Spectrum +and Commodore 64. By producing a less expensive machine that retained certain +key features, the motivation was to bring BBC Micro technology to bear on the +lower end of the home computer market, albeit approximately two years after +its initial introduction. + +Unfortunately, various features were omitted from the Acorn Electron that made +it less competitive than it could have been against a steadily improving range +of competitors: multi-channel sound support, MODE 7 teletext, support for +relatively smooth horizontal hardware scrolling (and other display control +features), and the double-speed bus with interleaved CPU and video access. +More RAM would also have been beneficial, although costly at the prices of the +day. Such deficiencies outweighed the significant benefits of substantial +software compatibility, and some of them effectively curtailed that +compatibility by making even reasonably well-written software titles +effectively unusable, particularly games relying on the BBC Micro's hardware +scrolling capabilities, including "official" Acornsoft titles. + +In hindsight, numerous features could be suggested that would make the +Electron more competitive, but many of these features would incur a +substantial cost. For example, giving the Electron 64K of RAM would have +increased the price substantially. Introducing the double-speed bus and faster +memory may also have increased the price in a prohibitive fashion. Thus, it +becomes worthwhile to consider minimal alterations to the machine's +specification that offer the greatest benefits for the least additional cost. + +Improving System Performance +---------------------------- + +Although RAM is accessed by the CPU at 1MHz, ROM is accessed at 2MHz. Thus, +deploying software that runs from ROM can potentially provide significant +performance benefits. Since the unexpanded Electron provides no convenient +means of installing ROM-based software - the Plus 1 and other expansion units +offered ROM cartridge slots, and various expansions provided ROM sockets - the +improved Electron would ideally need to offer a ROM cartridge slot as part of +the unexpanded machine. A side-benefit of adding this feature to the base +machine would arguably be an increased demand for cartridge-based software, +potentially at a slightly higher price and also offering additional hardware +features if necessary, thus making any cost incurred in the manufacture of the +base unit more bearable. + +The Slogger/Elektuur turbo board modified the system to permit the CPU to +access the bottom 8K of RAM without interruption by the ULA. This feature, +already known at Acorn during the Electron's design period, permitted +substantial improvements to performance and could also be incorporated into an +improved Electron, although it presumably needs motherboard-level changes. +Such turbo boards may have employed an additional RAM chip to avoid +complicated changes to the memory access logic, since the ULA appears to +access four memory chips at once to provide each byte, and it is therefore not +possible to just "borrow" one of the chips in order to isolate 8K of RAM for +direct access by the CPU. diff -r da771131998b -r be29270ef9f6 ULA.txt --- a/ULA.txt Tue Dec 20 01:01:28 2011 +0100 +++ b/ULA.txt Mon Feb 13 22:25:37 2012 +0100 @@ -1,15 +1,141 @@ Timing ------ -According to the above (15.3.2 in the AUG), there are 312 scanlines, 256 of -which are used to generate pixel data. At 50Hz, this means that 128 cycles are -used to produce pixel data (2000000 / 50 = 40000; 40000 / 312 ~= 128). This is -consistent with the observation that each scanline requires at most 80 bytes -of data, and that the ULA is apparently busy for 40 out of 64 microseconds in -each scanline. +According to 15.3.2 in the Advanced User Guide, there are 312 scanlines, 256 +of which are used to generate pixel data. At 50Hz, this means that 128 cycles +are spent on each scanline (2000000 cycles / 50 = 40000 cycles; 40000 cycles / +312 ~= 128 cycles). This is consistent with the observation that each scanline +requires at most 80 bytes of data, and that the ULA is apparently busy for 40 +out of 64 microseconds in each scanline. + +Access to RAM involves accessing four 64Kb dynamic RAM devices (IC4 to IC7, +each providing two bits of each byte) using two cycles within the 500ns period +of the 2MHz clock to complete each access operation. Since the CPU and ULA +have to take turns in accessing the RAM in MODE 4, 5 and 6, the CPU must +effectively run at 1MHz (since every other 500ns period involves the ULA +accessing RAM). The CPU is driven by an external clock (IC8) whose 16MHz +frequency is divided by the ULA (IC1) depending on the screen mode in use. + +Each 16MHz cycle is approximately 62.5ns. To access the memory, the following +patterns corresponding to 16MHz cycles are required: + + Time (ns): 0-------------- 500------------ ... + 2 MHz cycle: 0 1 ... + 16 MHz cycle: 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 ... + ~RAS: 0 1 0 1 ... + ~CAS: 0 1 0 1 0 1 0 1 ... + A B B A B B ... + F S F S ... + a b b a b b ... + +Here, "A" indicates the row and column addresses being latched into the RAM +(on a negative edge for ~RAS and ~CAS respectively), and "B" indicates the +second column address being latched into the RAM. Presumably, the first and +second half-bytes can be read at "F" and "S" respectively, and the row and +column addresses must be made available at "a" and "b" respectively at the +latest. + +Note that the Service Manual refers to the negative edge of RAS and CAS, but +the datasheet for the similar TM4164EC4 product shows latching on the negative +edge of ~RAS and ~CAS. It is possible that the Service Manual also intended to +communicate the latter behaviour. In the TM4164EC4 datasheet, it appears that +"page mode" provides the appropriate behaviour for that particular product. + +Video Timing +------------ + +According to 8.7 in the Service Manual, and the PAL Wikipedia page, +approximately 4.7µs is used for the sync pulse, 5.7µs for the "back porch" +(including the "colour burst"), and 1.65µs for the "front porch", totalling +12.05µs and thus leaving 51.95µs for the active video signal for each +scanline. As the Service Manual suggests in the oscilloscope traces, the +display information is transmitted more or less centred within the active +video period since the ULA will only be providing pixel data for 40µs in each +scanline. + +Each 62.5ns cycle happens to correspond to 64µs divided by 1024, meaning that +each scanline can be divided into 1024 cycles, although only 640 at most are +actively used to provide pixel data. Pixel data production should only occur +within a certain period on each scanline, approximately 262 cycles after the +start of hsync: + + active video period = 51.95µs + pixel data period = 40µs + total silent period = 51.95µs - 40µs = 11.95µs + silent periods (before and after) = 11.95µs / 2 = 5.975µs + hsync and back porch period = 4.7µs + 5.7µs = 10.4µs + time before pixel data period = 10.4µs + 5.975µs = 16.375µs + pixel data period start cycle = 16.375µs / 62.5ns = 262 + +By choosing a number divisible by 8, the RAM access mechanism can be +synchronised with the pixel production. Thus, 264 is a more appropriate start +cycle. + +The "vertical blanking period", meaning the period before picture information +in each field is 25 lines out of 312 (strictly 312.5) and thus lasts for +1.6ms. Of this, 2.5 lines occur before the vsync (field sync) which also lasts +for 2.5 lines. Thus, the first visible scanline on the first field of a frame +occurs half way through the 23rd scanline period measured from the start of +vsync: + + 10 20 23 + 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 + Line from 1: 0 22 3 + Line on screen: .:::::VVVVV::::: 12233445566 + |_________________________________________________| + 25 line vertical blanking period + +In the second field of a frame, the first visible scanline coincides with the +24th scanline period measured from the start of line 313 in the frame: + + 310 336 + 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 + Line from 313: 0 23 + Line on screen: 88:::::VVVVV:::: 11223344 + 288 | | + |_________________________________________________| + 25 line vertical blanking period + +In order to consider only full lines, we might consider the start of each +frame to occur 23 lines after the start of vsync. + +Again, it is likely that pixel data production should only occur on scanlines +within a certain period on each frame. The "625/50" document indicates that +only a certain region is "safe" to use, suggesting a vertically centred region +with approximately 15 blank lines above and below the picture. Thus, the start +of the picture could be chosen as 38 lines after the start of vsync. See: Acorn Electron Advanced User Guide See: http://mdfs.net/Docs/Comp/Electron/Techinfo.htm +See: http://en.wikipedia.org/wiki/PAL +See: http://en.wikipedia.org/wiki/Analog_television#Structure_of_a_video_signal +See: The 625/50 PAL Video Signal and TV Compatible Graphics Modes + http://lipas.uwasa.fi/~f76998/video/modes/ +See: PAL TV timing and voltages + http://www.retroleum.co.uk/electronics-articles/pal-tv-timing-and-voltages/ +See: Line Standards + http://www.pembers.freeserve.co.uk/World-TV-Standards/Line-Standards.html +See: TM4164EC4 65,536 by 4-Bit Dynamic RAM Module + http://www.datasheetarchive.com/dl/Datasheets-112/DSAP0051030.pdf +See: Acorn Electron Service Manual + http://acorn.chriswhy.co.uk/docs/Acorn/Manuals/Acorn_ElectronSM.pdf + +Shadow/Expanded Memory +---------------------- + +The Electron exposes all sixteen address lines and all eight data lines +through the expansion bus. Using such lines, it is possible to provide +additional memory - typically sideways ROM and RAM - on expansion cards and +through cartridges, although the official cartridge specification provides +fewer address lines and only seeks to provide access to memory in 16K units. + +Various modifications and upgrades were developed to offer "turbo" +capabilities to the Electron, permitting the CPU to access a separate 8K of +RAM at 2MHz, presumably preventing access to the low 8K of RAM accessible via +the ULA through additional logic. However, an enhanced ULA might support +independent CPU access to memory over the expansion bus by allowing itself to +be discharged from providing access to memory, potentially for a range of +addresses, and for the CPU to communicate with external memory uninterrupted. Hardware Scrolling ------------------ @@ -151,27 +277,28 @@ that hardware to reduce the load on the system CPU which was responsible for producing the video output. -Hardware Sprites and Colour Planes ----------------------------------- +Hardware Sprites +---------------- An enhanced ULA might provide hardware sprites, but this would be done in an way that is incompatible with the standard ULA, since no &FE*X locations are -available for allocation. In a special ULA mode, one might allocate a pair of -locations (for example, &FE20 and &FE21) as a pair of registers referencing a -region of memory from which a sprite might be found and potentially copied -into internal RAM, with other locations (for example, &FE22 and &FE23) -providing the size of the region. Alternatively, one might write the region -location and size through a single ULA location, with the ULA being put into a -particular state after each write. For example: read LSB of region, read MSB -of region, read size, read height. +available for allocation. To keep the facility simple, hardware sprites would +have a standard byte width and height. + +The specification of sprites could involve the reservation of 16 locations +(for example, &FE20-F) specifying a fixed number of eight sprites, with each +location pair referring to the sprite data. By limiting the ULA to dealing +with a fixed number of sprites, the work required inside the ULA would be +reduced since it would avoid having to deal with arbitrary numbers of sprites. -Providing hardware sprites can be awkward without having some kind of working -area, since the ULA would need to remember where each sprite is to be plotted -and then deduce which sprites would be contributing to any given pixel. An -alternative is to use memory into which the sprites would be plotted, and this -memory would be combined with the main screen memory, taking a particular -colour as the "colourkey" which is to be considered transparent, and only -overwriting the main screen pixels with pixel values for other colours. +The principal limitation on providing hardware sprites is that of having to +obtain sprite data, given that the ULA is usually required to retrieve screen +data, and given the lack of memory bandwidth available to retrieve sprite data +(particularly from multiple sprites supposedly at the same position) and +screen data simultaneously. Although the ULA could potentially read sprite +data and screen data in alternate memory accesses in screen modes where the +bandwidth is not already fully utilised, this would result in a degradation of +performance. Additional Screen Mode Configurations ------------------------------------- @@ -243,6 +370,13 @@ to apply attribute data to the first column, the initial 8 cycles might be configured to not produce pixel values. +For an entire character, attribute data need only be read for the first row of +pixels for a character. The subsequent rows would have attribute information +applied to them, although this would require the attribute data to be stored +in some kind of buffer. Thus, the following access pattern would be observed: + + Cycle: A B ... _ B ... _ B ... _ B ... _ B ... _ B ... _ B ... _ B ... + A whole byte used for colour information for a whole character would result in a choice of 256 colours, and this might be somewhat excessive. By only reading attribute bytes at every other opportunity, a choice of 16 colours could be @@ -252,31 +386,23 @@ Reads: B A B - 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 +Further reductions in attribute data access, offering 4 colours for every +character in a four character block, for example, might also be worth +considering. + Consider the following configurations for screen modes with a colour depth of 1 bit per pixel for bitmap information: - Screen width Columns Scaling Bytes (B) Bytes (A) Colours - ------------ ------- ------- --------- --------- ------- - 320 40 x2 40 40 256 - 320 40 x2 40 20 16 - 208 26 x3 26 26 256 - 208 26 x3 26 13 16 + Screen width Columns Scaling Bytes (B) Bytes (A) Colours Screen start + ------------ ------- ------- --------- --------- ------- ------------ + 320 40 x2 40 40 256 &5300 + 320 40 x2 40 20 16 &5580 -> &5500 + 320 40 x2 40 10 4 &56C0 -> &5600 + 208 26 x3 26 26 256 &62C0 -> &6200 + 208 26 x3 26 13 16 &6460 -> &6400 -Here, a mode resembling MODE 4 would occupy the same amount of space as MODE 1 -if 40 attribute (A) bytes were read in addition to the 40 bitmap (B) bytes. -This would offer limited benefit over the mode with the higher colour depth, -especially if palette definition lists were also available. However, if only -20 attribute bytes were read, the screen memory would be only 150% of the -original. - -Similarly, if an additional configuration pixel-tripled mode were to require -as many attribute bytes as bitmap bytes, it would occupy as much space as its -equivalent with twice the colour depth. However, by requiring only 13 -attribute bytes for every 26 bitmap bytes, it would actually be more efficient -than MODE 6 (a screen start address of &6600 versus MODE 6's &6000). - -MODE 7 Emulation ----------------- +MODE 7 Emulation using Character Attributes +------------------------------------------- If the scheme of applying attributes to character regions were employed to emulate MODE 7, in conjunction with the MODE 6 display technique, the @@ -284,11 +410,13 @@ Screen width Columns Rows Bytes (B) Bytes (A) Colours Screen start ------------ ------- ---- --------- --------- ------- ------------ - 320 40 25 40 20 16 &5120 -> &5100 + 320 40 25 40 20 16 &5ECC -> &5E00 + 320 40 25 40 10 4 &5FC6 -> &5F00 -Although this requires much more memory than MODE 7 (12000 bytes versus -MODE 7's 1000 bytes) and more memory than even MODE 6, it would at least make -a limited 40-column multicolour mode available as a substitute for MODE 7. +Although this requires much more memory than MODE 7 (8500 bytes versus MODE +7's 1000 bytes), it does not need much more memory than MODE 6, and it would +at least make a limited 40-column multicolour mode available as a substitute +for MODE 7. Enhanced Graphics and Mode Layouts ---------------------------------- @@ -362,9 +490,11 @@ RA0...RA7 (address lines for sending both row and column addresses to the RAM) - RAS (row address strobe setting the row address on a negative edge) + RAS (row address strobe setting the row address on a negative edge - see the + timing notes) - CAS (column address strobe setting the column address on a negative edge) + CAS (column address strobe setting the column address on a negative edge - + see the timing notes) WE (sets write enable with logic 0, read with logic 1) diff -r da771131998b -r be29270ef9f6 main.py --- a/main.py Tue Dec 20 01:01:28 2011 +0100 +++ b/main.py Mon Feb 13 22:25:37 2012 +0100 @@ -52,30 +52,34 @@ ula.set_mode(2); ula.reset() - ula.fill(0x3000, 0x5800 - 320, encode((1, 6), 4)) - ula.fill(0x5800 - 320, 0x8000, encode((2, 7), 4)) + ula.ram.fill(0x3000, 0x5800 - 320, encode((1, 6), 4)) + ula.ram.fill(0x5800 - 320, 0x8000, encode((2, 7), 4)) ula_screen = update(ula) update_screen(screen, ula_screen) + print "Screen updated." mainloop() ula.screen_start = 0x3000 + 2 ula_screen = update(ula) update_screen(screen, ula_screen) + print "Screen updated." mainloop() # Test MODE 6. ula.set_mode(6); ula.reset() - ula.fill(0x6000, 0x6f00 + 160, encode((1, 0, 1, 1, 0, 0, 1, 1), 1)) - ula.fill(0x6f00 + 160, 0x7f40, encode((1, 0, 1, 0, 1, 0, 1, 0), 1)) + ula.ram.fill(0x6000, 0x6f00 + 160, encode((1, 0, 1, 1, 0, 0, 1, 1), 1)) + ula.ram.fill(0x6f00 + 160, 0x7f40, encode((1, 0, 1, 0, 1, 0, 1, 0), 1)) ula_screen = update(ula) update_screen(screen, ula_screen) + print "Screen updated." mainloop() ula.screen_start = 0x6f00 + 160 ula_screen = update(ula) update_screen(screen, ula_screen) + print "Screen updated." mainloop() # vim: tabstop=4 expandtab shiftwidth=4 diff -r da771131998b -r be29270ef9f6 ula.py --- a/ula.py Tue Dec 20 01:01:28 2011 +0100 +++ b/ula.py Mon Feb 13 22:25:37 2012 +0100 @@ -9,11 +9,23 @@ LINES_PER_ROW = 8 # the number of pixel lines per character row MAX_HEIGHT = 256 # the height of the screen in pixels +MAX_WIDTH = 640 # the width of the screen in pixels + +MAX_CSYNC = 2 # the scanline during which vsync ends +MIN_PIXELLINE = 38 # the first scanline involving pixel generation MAX_SCANLINE = 312 # the number of scanlines in each frame -MAX_WIDTH = 640 # the width of the screen in pixels -MAX_SCANPOS = 1024 # the number of positions in each scanline + +MAX_PIXELLINE = MIN_PIXELLINE + MAX_HEIGHT + +MAX_HSYNC = 75 # the number of cycles in each hsync period +MIN_PIXELPOS = 264 # the first cycle involving pixel generation +MAX_SCANPOS = 1024 # the number of cycles in each scanline + +MAX_PIXELPOS = MIN_PIXELPOS + MAX_WIDTH + SCREEN_LIMIT = 0x8000 # the first address after the screen memory MAX_MEMORY = 0x10000 # the number of addressable memory locations +MAX_RAM = 0x10000 # the number of addressable RAM locations (64Kb in each IC) BLANK = (0, 0, 0) def update(ula): @@ -43,19 +55,70 @@ self.colour = BLANK self.csync = 1 self.hs = 1 - self.reset() + self.x = 0 + self.y = 0 - def reset(self): - self.pos = 0 + def set_csync(self, value): + if self.csync and not value: + self.y = 0 + self.pos = 0 + self.csync = value + + def set_hs(self, value): + if self.hs and not value: + self.x = 0 + self.y += 1 + self.hs = value def update(self): - if self.csync: - if self.hs: + if MIN_PIXELLINE <= self.y < MAX_PIXELLINE: + if MIN_PIXELPOS <= self.x < MAX_PIXELPOS: self.screen[self.pos] = self.colour[0]; self.pos += 1 self.screen[self.pos] = self.colour[1]; self.pos += 1 self.screen[self.pos] = self.colour[2]; self.pos += 1 - else: - self.pos = 0 + self.x += 1 + +class RAM: + + """ + A class representing the RAM circuits (IC4 to IC7). Each circuit + traditionally holds 64 kilobits, with two accesses required to read 2 bits + from each in order to obtain a whole byte. Here, we model the circuits with + a list of 65536 half-bytes with each bit representing a bit stored on a + separate IC. + """ + + def __init__(self): + + "Initialise the RAM circuits." + + self.memory = [0] * MAX_RAM + self.row_address = 0 + self.column_address = 0 + self.data = 0 + + def row_select(self, address): + self.row_address = address + + def row_deselect(self): + pass + + def column_select(self, address): + self.column_address = address + + # Read the data. + + self.data = self.memory[self.row_address << 8 | self.column_address] + + def column_deselect(self): + pass + + # Convenience methods. + + def fill(self, start, end, value): + for i in xrange(start, end): + self.memory[i << 1] = value >> 4 + self.memory[i << 1 | 0x1] = value & 0xf class ULA: @@ -74,25 +137,31 @@ palette = range(0, 8) * 2 - def __init__(self, memory, video): + def __init__(self, ram, video): - "Initialise the ULA with the given 'memory' and 'video'." + "Initialise the ULA with the given 'ram' and 'video' instances." - self.memory = memory + self.ram = ram self.video = video self.set_mode(6) # Internal state. - self.buffer = [(0, 0, 0)] * 8 - self.reset() def reset(self): "Reset the ULA." - self.vsync() + # Internal state. + + self.cycle = 0 # counter within each 2MHz period + self.access = 0 # counter used to determine whether a byte needs reading + self.ram_address = 0 # address given to the RAM + self.data = 0 # data read from the RAM + self.buffer = [BLANK]*8 # pixel buffer for decoded RAM data + + self.reset_vertical() def set_mode(self, mode): @@ -116,6 +185,7 @@ self.width, self.depth, rows = ULA.modes[mode] columns = (self.width * self.depth) / 8 # bits read -> bytes read + self.access_frequency = 80 / columns # cycle frequency for reading bytes row_size = columns * LINES_PER_ROW # Memory access configuration. @@ -139,7 +209,21 @@ self.buffer_limit = 8 / self.depth - def vsync(self): + def vsync(self, value=0): + + "Signal the start of a frame." + + self.csync = value + self.video.set_csync(value) + + def hsync(self, value=0): + + "Signal the end of a scanline." + + self.hs = value + self.video.set_hs(value) + + def reset_vertical(self): "Signal the start of a frame." @@ -147,18 +231,17 @@ self.line = self.line_start % LINES_PER_ROW self.ssub = 0 self.y = 0 - self.reset_horizontal() - - # Signal the video circuit. + self.x = 0 - self.csync = self.video.csync = 1 + def reset_horizontal(self): - def hsync(self): - - "Signal the end of a scanline." + "Reset horizontal state within the active region of the frame." self.y += 1 - self.reset_horizontal() + self.x = 0 + + if not self.inside_frame(): + return # Support spacing between character rows. @@ -193,70 +276,176 @@ self.line_start = self.address - def reset_horizontal(self): - - "Reset horizontal state." - - self.x = 0 - self.buffer_index = self.buffer_limit # need refill - - # Signal the video circuit. - - self.hs = self.video.hs = 1 + def in_frame(self): return MIN_PIXELLINE <= self.y < MAX_PIXELLINE + def inside_frame(self): return MIN_PIXELLINE < self.y < MAX_PIXELLINE + def read_pixels(self): return MIN_PIXELPOS - 8 <= self.x < MAX_PIXELPOS - 8 and self.in_frame() + def make_pixels(self): return MIN_PIXELPOS <= self.x < MAX_PIXELPOS and self.in_frame() def update(self): """ - Update the pixel colour by reading from the pixel buffer. + Update the state of the ULA for each clock cycle. This involves updating + the pixel colour by reading from the pixel buffer. """ - # Detect the end of the line. + # Detect the end of the scanline. + + if self.x == MAX_SCANPOS: + self.reset_horizontal() + + # Detect the end of the frame. + + if self.y == MAX_SCANLINE: + self.reset_vertical() + + + + # Clock management. + + access_ram = self.access == 0 and self.read_pixels() and not self.ssub + + # Set row address (for ULA access only). + + if self.cycle == 0: + + # NOTE: Propagate CPU address here. + + if access_ram: + self.ram_address = (self.address & 0xff80) >> 7 + + # Latch row address, set column address (for ULA access only). + + elif self.cycle == 1: + + # NOTE: Permit CPU access here. - if self.x >= MAX_WIDTH: - if self.x == MAX_WIDTH: - self.hs = self.video.hs = 0 + if access_ram: + self.ram.row_select(self.ram_address) + + # NOTE: Propagate CPU address here. + + if access_ram: + self.ram_address = (self.address & 0x7f) << 1 + + # Latch column address. + + elif self.cycle == 2: + + # NOTE: Permit CPU access here. - # Detect the end of the scanline. + if access_ram: + self.ram.column_select(self.ram_address) + + # Read 4 bits (for ULA access only). + # NOTE: Perhaps map alternate bits, not half-bytes. + + elif self.cycle == 3: + + # NOTE: Propagate CPU data here. + + if access_ram: + self.data = self.ram.data << 4 + + # Set column address (for ULA access only). + + elif self.cycle == 4: + self.ram.column_deselect() - elif self.x == MAX_SCANPOS: - self.hsync() + # NOTE: Propagate CPU address here. + + if access_ram: + self.ram_address = (self.address & 0x7f) << 1 | 0x1 + + # Latch column address. + + elif self.cycle == 5: + + # NOTE: Permit CPU access here. + + if access_ram: + self.ram.column_select(self.ram_address) - # Detect the end of the frame. + # Read 4 bits (for ULA access only). + # NOTE: Perhaps map alternate bits, not half-bytes. + + elif self.cycle == 6: + + # NOTE: Propagate CPU data here. + + if access_ram: + self.data = self.data | self.ram.data + + # Advance to the next column. + + self.address += LINES_PER_ROW + self.wrap_address() + + # Reset addresses. - if self.y == MAX_SCANLINE: - self.vsync() + elif self.cycle == 7: + self.ram.column_deselect() + self.ram.row_deselect() + + # Update the RAM access controller. + + self.access = (self.access + 1) % self.access_frequency + + self.cycle = (self.cycle + 1) % 8 + + + + # Video signalling. + + # Detect any sync conditions. - # Detect the end of the screen. + if self.x == 0: + self.hsync() + if self.y == 0: + self.vsync() + + # Detect the end of hsync. - elif self.y == MAX_HEIGHT: - self.csync = self.video.csync = 0 + elif self.x == MAX_HSYNC: + self.hsync(1) + + # Detect the end of vsync. + + elif self.y == MAX_CSYNC and self.x == MAX_SCANPOS / 2: + self.vsync(1) + + + + # Pixel production. # Detect spacing between character rows. - if self.ssub: + if not self.make_pixels() or self.ssub: self.video.colour = BLANK - # Detect horizontal and vertical sync conditions. - - elif not self.hs or not self.csync: - pass - # For pixels within the frame, obtain and output the value. else: + # Detect the start of the pixel generation. + + if self.x == MIN_PIXELPOS: + self.xcounter = self.xscale + self.buffer_index = 0 + self.fill_pixel_buffer() # Scale pixels horizontally, only accessing the next pixel value # after the required number of scan positions. - if self.x % self.xscale == 0: + elif self.xcounter == 0: + self.xcounter = self.xscale self.buffer_index += 1 - # Fill the buffer once all values have been read. + # Fill the pixel buffer, assuming that data is available. - if self.buffer_index >= self.buffer_limit: - self.buffer_index = 0 - self.fill_pixel_buffer() + if self.buffer_index >= self.buffer_limit: + self.buffer_index = 0 + self.fill_pixel_buffer() + self.xcounter -= 1 self.video.colour = self.buffer[self.buffer_index] self.x += 1 @@ -268,27 +457,17 @@ mode. """ - byte_value = self.memory[self.address] + byte_value = self.data # which should have been read automatically i = 0 for colour in decode(byte_value, self.depth): self.buffer[i] = get_physical_colour(ULA.palette[colour]) i += 1 - # Advance to the next column. - - self.address += LINES_PER_ROW - self.wrap_address() - def wrap_address(self): if self.address >= SCREEN_LIMIT: self.address -= self.screen_size - # Convenience methods. - - def fill(self, start, end, value): - fill(self.memory, start, end, value) - def get_physical_colour(value): """ @@ -345,7 +524,7 @@ "Return a ULA initialised with a memory array and video." - return ULA(get_memory(), get_video()) + return ULA(get_ram(), get_video()) def get_video(): @@ -353,24 +532,19 @@ return Video() -def get_memory(): - - "Return an array representing the computer's memory." - - return [0] * MAX_MEMORY +def get_ram(): -def fill(memory, start, end, value): - i = start - while i < end: - memory[i] = value - i += 1 + "Return an instance representing the computer's RAM hardware." + + return RAM() # Test program providing coverage (necessary for compilers like Shedskin). if __name__ == "__main__": ula = get_ula() ula.set_mode(6) - ula.fill(0x6000, 0x8000, encode((1, 0, 1, 0, 1, 0, 1, 0), 1)) + ula.reset() + ula.ram.fill(0x6000, 0x8000, encode((1, 0, 1, 0, 1, 0, 1, 0), 1)) # Make a simple two-dimensional array of tuples (three-dimensional in pygame # terminology).