A thought about 40-column screen hardware for the Spectrum ========================================================== http://mdfs.net/Docs/Comp/Spectrum/Hardware/40ColScrn Very crudely, the Spectrum ULA can be though of as a 13-bit counter. It starts at zero in the top-left displayed corner and increments by one for each character cell pixel line. This counter is mapped to memory as follows: 0 1 0 B12 B11 B7 B6 B5 B10 B9 B8 B4 B3 B2 B1 B0 Bitmaps 0 1 0 1 1 0 B12 B11 B10 B9 B8 B4 B3 B2 B1 B0 Attrs | | | | | | | | | | | | | | | | A15 A14 A13 A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0 Memory The Spectrum's display is only 192 lines high, so the counter counts to a maximum of &17FF before resetting to zero. The counter never reaches &1800, so the maximum bitmap address is &57FF. The counter can be thought of as two seperate counters, a column counter that counts from 0 to 31 and a line counter that counts from 0 to 191, incrementing when the column counter wraps from 31 to 0. 0 1 0 R7 R6 R2 R1 R0 R5 R4 R3 C4 C3 C2 C1 C0 Bitmaps 0 1 0 1 1 0 R7 R6 R5 R4 R3 C4 C3 C2 C1 C0 Attrs | | | | | | | | | | | | | | | | A15 A14 A13 A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0 Memory This shows how the familiar display results in thirds with pixel-lines offset by 256 bytes. In reality, the ULA is a 18-bit counter, a 3-bit pixel counter counting from 0 to 7, a 6-bit column counter counting from 0 to 63 across the TV's 64us scan line and a 9-bit line counter counting from 0 to 311. B17 B16 B15 B14 B13 B12 B11 B10 B9 B8 B7 B6 B5 B4 B3 B2 B1 B0 | | | | | | | | | | | | | | | | | | R8 R7 R6 R5 R4 R3 R2 R1 R0 C5 C4 C3 C2 C1 C0 P2 P1 P0 <---------- Pixel line ----------> <- Character Column -> <- pixel -> <-Character Line-> When C5 is high the border is generated and no memory addresses are output. The line counter counts from 0 to 311, half the 625 TV lines. When R6:R7=%11 or R8=1 the border is generated and no memory addresses are output. What could have happened if Sinclair decided to have a hardware 40-column screen? This would certainly have made a software 80-column screen a lot simpler. Instead of the display being output when the column counter is counting from 0 to 31, it can be displayed when counting from 0 to 39. This requires a bit of additional decoding, instead of the border being generated with C5 high, the border would be generated with C0:C5>39. This can be done easily in logic with C5 AND (C4 OR C3). C5----------+ | AND--->border C4----+ | OR----+ C3----+ Part of the row counter needs to be multiplied by 40 as there are 40 characters cells on each line, and then added to the column counter to generate the memory address. This is simple to do in hardware. This result is then combined with the rest of the row counter to generate the memory address. Again, as the column counter is still a 6-bit counter, with overflow incrementing the row counter, the column and row counter can be thought of as a single 14-bit counter. The character and row counters can be combined as follows: 0 1 0 R2 R1 R0 <-- (R3:R7)*40 + (C0:C5) --> Bitmaps 0 1 1 0 0 0 <-- (R3:R7)*40 + (C0:C5) --> Attrs | | | | | | | | | | | | | | | | A15 A14 A13 A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0 Memory Instead of a multiplier and adder, a simpler solution is to have a second counter that counts from 0 to 999, starting at zero at the top left-hand corner, and incrementing by one whenever C0:C5 is incremented by one when less than 40, ie when not within the border. 40 is a multiple of 8, so the bottom three bits are still the bottom three bits of the column counter, and the overflow from M2 to M3 is only carried when not within the border. C5----------+ ^ | ^ AND---+-->border ^ C4----+ | | M4 OR----+ | ^ C3----+ | M3 AND------------^ C2----------------+------------->M2 ^ C1------------------------------>M1 ^ C0------------------------------>M0 These can then be output as memory addresses as follows: 0 1 0 M9 M8 R2 R1 R0 M7 M6 M5 M4 M3 C2 C1 C0 Bitmaps 0 1 1 0 0 0 M9 M8 M7 M6 M5 M4 M3 C2 C1 C0 Attrs | | | | | | | | | | | | | | | | A15 A14 A13 A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0 Memory This gives the familiar arrangement where pixel-lines are offset by 256 bytes within memory. However, if the pixel-line counter in R0:R2 is moved to the top of the output address it gives the following arrangement: 0 1 0 R2 R1 R0 M9 M8 M7 M6 M5 M4 M3 C2 C1 C0 Bitmaps 0 1 1 0 0 0 M9 M8 M7 M6 M5 M4 M3 C2 C1 C0 Attrs | | | | | | | | | | | | | | | | A15 A14 A13 A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0 Memory This has the advantage that the ULA isn't swapping between outputting M9:M8 and R1:R0, it is always outputting M9:M3,C2:C0 in the bottom 10 bits, only swapping between 0:R2:R1:R0 and 1:0:0:0 in the top four bits. With the row counter counting up to 959 this gives 24 lines of 40 characters. Each pixel line within a character line is offset from the next by 1024 bytes. With the normal ULA each pixel line is offset by 256 bytes allowing software to increment the high byte of a register to step along lines. With pixel lines at 1024 byte intervals the same type of code can be used, incrementing the high byte four times instead of once: Normal ULA 40-column ULA LD B,8 LD B,8 LOOP: LD A,(DE) LOOP: LD A,(DE) LD (HL),A LD (HL),A INC DE INC DE INC H INC H DJNZ LOOP INC H INC H INC H DJNZ LOOP In fact, there is actually enough memory space to allow the row counter to count up to 999 to give 25 character lines. This gives a screen memory layout as follows: | X=0 | X=1 | X=2 | X=3 | ... |X=37 |X=38 |X=39 | -----+-----+-----+-----+-----+-----+-----+-----+-----+ |&4000|&4001|&4002|&4003| ... |&4025|&4026|&4027| |&4400|&4401|&4402|&4403| ... |&4425|&4426|&4427| |&4800|&4801|&4802|&4803| ... |&4825|&4826|&4827| Y=0 |&4C00|&4C01|&4C02|&4C03| ... |&4C25|&4C26|&4C27| |&5000|&5001|&5002|&5003| ... |&5025|&5026|&5027| |&5400|&5401|&5402|&5403| ... |&5425|&5426|&5427| |&5800|&5801|&5802|&5803| ... |&5825|&5826|&5827| |&5C00|&5C01|&5C02|&5C03| ... |&5C25|&5C26|&5C27| Attr:|&6000|&6001|&6002|&6003| ... |&6025|&6026|&6027| -----+-----+-----+-----+-----+-----+-----+-----+-----+ |&4028|&4029|&402A|&402B| ... |&404E|&404E|&404F| |&4428|&4429|&442A|&442B| ... |&444E|&444E|&444F| |&4828|&4829|&482A|&482B| ... |&484E|&484E|&484F| Y=1 |&4C28|&4C29|&4C2A|&4C2B| ... |&4C4E|&4C4E|&4C4F| |&5028|&5029|&502A|&502B| ... |&504E|&504E|&504F| |&5428|&5429|&542A|&542B| ... |&544E|&544E|&544F| |&5828|&5829|&582A|&582B| ... |&584E|&584E|&584F| |&5C28|&5C29|&5C2A|&5C2B| ... |&5C4E|&5C4E|&5C4F| Attr:|&6028|&6029|&602A|&602B| ... |&604E|&604E|&604F| -----+-----+-----+-----+-----+-----+-----+-----+-----+ etc... -----+-----+-----+-----+-----+-----+-----+-----+-----+ |&43C0|&43C1|&43C2|&43C3| ... |&43E6|&43E6|&43E7| |&47C0|&47C1|&47C2|&47C3| ... |&47E6|&47E6|&47E7| |&4BC0|&4BC1|&4BC2|&4BC3| ... |&4BE6|&4BE6|&4BE7| Y=24 |&4FC0|&4FC1|&4FC2|&4FC3| ... |&4FE6|&4FE6|&4FE7| |&53C0|&53C1|&53C2|&53C3| ... |&53E6|&53E6|&53E7| |&57C0|&57C1|&57C2|&57C3| ... |&57E6|&57E6|&57E7| |&5BC0|&5BC1|&5BC2|&5BC3| ... |&5BE6|&5BE6|&5BE7| |&5FC0|&5FC1|&5FC2|&5FC3| ... |&5FE6|&5FE6|&5FE7| Attr:|&63C0|&63C1|&63C2|&63C3| ... |&63E6|&63E6|&63E7| -----+-----+-----+-----+-----+-----+-----+-----+-----+ As can be seen, at the end of each run of eight pixel lines there is a block of 24 unused bytes. The screen ends at &63E7, and memory can be used from &63E8 (25576) onwards. Dynamic RAM considerations -------------------------- The bottom 16K of the Spectrum's memory is made up of dynamic RAM which is refreshed by the ULA strobing through it to generate the displayed screen. One important consideration when using dynamic RAM in this way is to ensure that all the RAM is refreshed regardless of the address range strobed. At first sign it looks as though the ULA only strobes memory from &4000 to &5AFF. What keeps the memory from &5B00 to &7FFF refreshed? The usual way of doing this is to map the address lines to the dynamic RAM to ensure that all the RAM is refreshed. In the Spectrum the memory address lines A0-A6 are interleaved with address lines A7-A13 to generate the dynamic RAM address lines A0-A6 and arranged so that the DRAM column containing the pixel data for a character block is in the same DRAM column as the attribute data. This ensures that the whole of the dynamic RAM is strobed to refresh it. Version History --------------- 0.10 12-Sep-2004 First typed draft in response to comp.sys.sinclair discussion. 0.11 15-Dec-2004 Corrected some typos, clarified 40-column counter.