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 row 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 column counter effectively counts from 0 to 63. When C5 is high the border is generated and no memory addresses are output. The row counter counts from 0 to some number higher than 191. When R6:R7=%11 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 column counter counting from 0 to 31, it can count from 0 to 39. When it gets past 39 it wraps back to zero. This requires a bit of additional decoding as 31 can wrap round to zero purely due to the fact that it is a 5-bit counter. Effectively, as a television screen scanline is 64us long, it would be the same 6-bit counter as the normal ULA. 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 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 Instead of a multiplier and adder, there could be 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. Remember, the row counter is only counting from 0 to 191, so that is 24*40 bytes for each screen of pixel lines, 960 bytes. 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 as 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 space to allow the row counter to count up to 199 to give 25 character lines. This gives a screen memory layout as follows: | X=0 | X=1 | X=2 | X=3 | ... |X=38 |X=39 | -----+-----+-----+-----+-----+-----+-----+-----+ |&4000|&4001|&4002|&4003| ... |&4026|&4027| |&4400|&4401|&4402|&4403| ... |&4426|&4427| |&4800|&4801|&4802|&4803| ... |&4826|&4827| Y=0 |&4C00|&4C01|&4C02|&4C03| ... |&4C26|&4C27| |&5000|&5001|&5002|&5003| ... |&5026|&5027| |&5400|&5401|&5402|&5403| ... |&5426|&5427| |&5800|&5801|&5802|&5803| ... |&5826|&5827| |&5C00|&5C01|&5C02|&5C03| ... |&5C26|&5C27| Attr:|&6000|&6001|&6002|&6003| ... |&6026|&6027| -----+-----+-----+-----+-----+-----+-----+-----+ |&4028|&4029|&402A|&402B| ... |&404E|&404F| |&4428|&4429|&442A|&442B| ... |&444E|&444F| |&4828|&4829|&482A|&482B| ... |&484E|&484F| Y=1 |&4C28|&4C29|&4C2A|&4C2B| ... |&4C4E|&4C4F| |&5028|&5029|&502A|&502B| ... |&504E|&504F| |&5428|&5429|&542A|&542B| ... |&544E|&544F| |&5828|&5829|&582A|&582B| ... |&584E|&584F| |&5C28|&5C29|&5C2A|&5C2B| ... |&5C4E|&5C4F| Attr:|&6028|&6029|&602A|&602B| ... |&604E|&604F| -----+-----+-----+-----+-----+-----+-----+-----+ etc... -----+-----+-----+-----+-----+-----+-----+-----+ |&43C0|&43C1|&43C2|&43C3| ... |&43E6|&43E7| |&47C0|&47C1|&47C2|&47C3| ... |&47E6|&47E7| |&4BC0|&4BC1|&4BC2|&4BC3| ... |&4BE6|&4BE7| Y=24 |&4FC0|&4FC1|&4FC2|&4FC3| ... |&4FE6|&4FE7| |&53C0|&53C1|&53C2|&53C3| ... |&53E6|&53E7| |&57C0|&57C1|&57C2|&57C3| ... |&57E6|&57E7| |&5BC0|&5BC1|&5BC2|&5BC3| ... |&5BE6|&5BE7| |&5FC0|&5FC1|&5FC2|&5FC3| ... |&5FE6|&5FE7| Attr:|&63C0|&63C1|&63C2|&63C3| ... |&63E6|&63E7| -----+-----+-----+-----+-----+-----+-----+-----+ As can be seen, at the end of each run of 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 synamic 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 interleafed with address lines A7-A13 to generate the dynamic RAM address lines A0-A6. This ensures that the whole of the dynamic RAM is strobed to refresh it. Dynamic RAM can bit a bit fiddly to understand, and it is well worth the time time reading references on dynamic RAM memory addressing. Version History --------------- 0.10 12-Sep-2004 First typed draft in response to comp.sys.sinclair discussion.