13

I'm building a Z80-based breadboard computer from scratch to learn more about simple processors, and I'm a bit stumped when it comes to the base concepts of building a text-based terminal.

I've got an LCD screen to which I can send pixel data. Currently I'm trying to use the Z80 itself to interface with the screen; I understand that a separate terminal processor would be doing this heavy lifting in the real world.

My idea was as follows:

  • I have a buffer of ASCII characters in the Z80's RAM.
  • I have a bitmap font stored in the Z80's ROM as a set of six bytes per character.
  • I have a section of RAM set aside for the framebuffer.
  • Every time I press a key, it's added to the buffer.
  • Then, the Z80 loops through each character in the buffer, finds the matching bitmap character, and loops through its rows, writing them into the framebuffer one row at a time at the current location to build up a character. This piece of assembly code is hideously long and seems very slow and bug-prone.
  • The framebuffer is then sent to the screen via a parallel interface.

Is this really how video terminals worked originally, or do I have the basic concept all wrong? I'd appreciate any advice, extra reading, and even old code.

Edit: This question is interesting, but is a bit more high-level than mine.

5
  • 10
    They did not have bitmap frame buffers. they used character buffers and let hardware do what our program is doing. After all, Memory was incredible expensive and ASCII doesn't need bitmap, so why adding it in the first place?
    – Raffzahn
    Mar 3 at 10:41
  • The CP/M-80 computer I started with used an Intel 8275 to do the RAM->image conversion. Mar 3 at 13:29
  • How early? And what exactly do you mean by a 'Video Terminal'? In the Z80's heyday (early to mid-80s) it was unusual to use VT100s/etc. Your approach would then be more-or-less right, except that you don't need to keep a character buffer at all (though it helps in character-based scrolling), and you need to make your LCD panel look like an old CRT, as @SolomonSlow says below. I did a commercial Z80 box in 82/83 which used a 6845 for display timing; it was slightly unusual in that in was bit-mapped. The difficult bit here is sharing the graphics memory between the two; whole other story.
    – EML
    Mar 4 at 19:05
  • True bare-bone "video card" generating VGA output - youtube.com/watch?v=l7rce6IQDWs - while a bit more hardcore than you are looking for in your project it is good educational video to understand concepts. Mar 5 at 5:28
  • 1

5 Answers 5

26

Video Terminals

I understand the term "video terminal" to refer to things like the DEC VT-100 or Lear-Siegler ADM-3A.

They get characters (typically ASCII-encoded) over a serial line from the computer, and they send keystrokes (typically ASCII-endoded as well) to the computer. As your question only applies to the display part, I'll ignore the keyboard-input part.

The terminal has a character-oriented frame buffer, with one byte for each character position on screen (typically 25*80 = 2000 bytes). The terminal also keeps track of the "cursor position" (the place where to put the next character received, and the place to be visualized with e.g. a blinking block).

The typical process done inside the terminal is:

  • Receive a character code from the serial line
  • Place the character code into the frame buffer at the cursor position
  • Advance the cursor position by 1

Besides the visible ASCII characters, terminals understand special control codes, to be handled differently, e.g.

  • CR moves the cursor to the beginning of the line
  • LF moves the cursor down one line (and scrolls the screen by one line if at end of screen)
  • BS moves the cursor left one position.

Now that the frame buffer is filled with the characters for the display, how do they get visible?

A typical CRT needs to refresh every single dot 50 times per second (or so) to avoid nasty flickering. The display is physically driven by an electron beam deflected in a regular "scan lines" pattern that covers the whole screen and is repeated 50 times per second. White and black pixels are produced by switching the beam on or off while passing the pixel position.

If characters are displayed as 10(H) * 8(W) pixel patterns, we get 250 visible scan lines (vertical resolution), each showing 640 "pixels" (horizontal resolution).

To translate the (ASCII) character codes into the pixel patterns, a "character generator" ROM is used that contains the (10-bytes) pixel pattern for every ASCII character.

A continuous readout circuit runs synchronously with the electron beam (synchronized by emitting the appropriate SYNC signals to the CRT).

For every scan line, it loops over the 80 characters in the corresponding text line, wiring the character code output from the frame buffer to the character generator ROM input, to have the ROM output produce the pixels. Typically, the ROM delivers 8 pixels as parallel outputs, and a shift register first sends the leftmost pixel to the CRT, then the second, and so on for all 8 pixels.

As a text line consists of 10 scan lines, the very same text line is scanned 10 times, and sent to the character generator ROM 10 times. As these 10 lines have to look different (from top to bottom row of the character pattern), the character generator ROM needs more than just the charater-code inputs. It also has scanline-number inputs, and these get counted up from 0 to 9 over the 10 scan lines, before the readout circuit switches to the next text line and resets the scanline-number inputs to 0.

One special feature is scrolling. When your cursor advances past the last row, you don't want it to show up at the first row, and have the screen unchanged. You want a fresh, empty line at the bottom, want every text line to move up one row, and forget the text that previously occupied the first row.

Instead of really moving every character up one position, this is done by having a variable screen start address, which is just incremented by one row when scrolling.

Your Approach

Your approach is fundamentally different, but for a very valid reason. LCDs don't follow the same model as CRTs, there is no electron beam that has to switched on or off at a very high frequency. Instead, they contain their own intelligence so you can send commands from your CPU, e.g. to fill some area with a given bit pattern (assuming it's a graphic LCD). So, your CPU needs to translate character codes into bit patterns and then command the LCD to draw these patterns at a specific location.

Your current approach seems to always transfer the whole frame buffer to the LCD. If that's the only operation supported, it will become quite tricky to achieve a reasonable performance (send at regular intervals, only if something really changed). If the LCD also supports partial transfers (ideally with the size of your character cell), you can greatly improve the process by just sending the area just modified.

6
  • 3
    All the answers here are wonderfully helpful and illuminating - this one goes right to the heart of 'how does a terminal go from ASCII codes to pixels on a screen', thank you very much for the detailed explanation! Mar 3 at 12:59
  • 10
    Re, "LDCs don't follow the same model..." Sounds like you are talking about an LCD display module that has it's own built-in microprocessor and frame buffer. A bare, passive-matrix, LCD screen has very little intelligence: You have to feed it a continuous stream of precisely timed pixel values, just as if it were a CRT; and God (or co-workers) help you if you have to write the X-Window System configuration files to make it work. (Don't ask me how I know!) Mar 3 at 23:44
  • Older CRTs only redrew the screen 25 times a second, rather than 50.
    – Vikki
    Mar 6 at 3:40
  • @Vikki That's only half-true, if the CRT was driven in "interlaced" mode (see en.wikipedia.org/wiki/Interlaced_video). Then the frame rate still was 50 frames per second, only the first frame contained the "even-numbered" scan lines, and the second frame the "odd-numbered". Interlaced mode completely was a feature of the driving circuit and its timing parameters. In interlaced mode, the frame rate was 50/sec, but each individual dot only refreshed 25 times per second. AFAIK, this was never used in video terminals as per this question. Mar 7 at 8:07
  • 1
    @Vikki Retrocomputing comments are much too limited to give a course on CRT fundamentals. The CRT knows nothing about "interlacing". The vertical deflection moves the electron beam down the screen continuously, 50 times per second. If in the second frame, you produce the HSYNCs "half a line" later (e.g. 32µs), then beam has proceeded down by "half a line", and the scan lines get placed a little bit further down the screen, so they fill the gaps between the previous-frame scan lines. Interlaced mode was mainly a TV convention, mostly avoided for computer graphics. Mar 7 at 8:21
11

Translating characters to their bitmap representation is usually handled by something other than the main system CPU.

In terminal-oriented systems (e.g. typical CP/M setups), the main system only sends characters (including escape codes) to the connected terminal, usually over a serial connection; the terminal takes care of displaying the characters. There might not even be a local framebuffer; all the main CPU needs to handle is a stream of characters to and from the terminal.

On micros producing the video signal themselves, there’s dedicated circuitry handling both the video signal itself, and the character generation. Understanding the Apple IIe includes a detailed description of the setup there. On 8-bit Ataris, this is handled by the ANTIC chip. On MDA or CGA-equipped PCs, character generation is taken care of by dedicated “alpha-serializer” circuitry (which IBM also refers to as a “ROS character generator”, i.e. a read-only storage character generator, which uses each display character as an index into the font ROM). In all these systems, the framebuffer contains either individual pixels, or individual characters (possibly with attributes), or a mixture.

One notable exception is early Amstrad computers, where character generation is handled by the CP/M XBIOS.

The various answers to Which computers had redefinable character sets? provide lots of information on the different approaches to character generation seen in micros.

4
  • The first Amstrad CPC464 had a 6845 too.
    – airman
    Mar 3 at 13:04
  • The Sinclair ZX81 did much of the character generation on the main CPU. This made it very slow, as the CPU could only execute BASIC during the blank areas at the top and bottom. You could disable video output to speed it up (so-called FAST mode).
    – Neil
    Mar 5 at 10:55
  • The 6485 merely generates addresses to fetch data from the display memory, and supplies a line-within-character counter output which may be used in conjunction with data fetched from the display to produce a rasterized video output. The 6485 has no involvement in the character-to-shape translation process.
    – supercat
    Mar 6 at 19:37
  • @supercat indeed, I’ve corrected that. Mar 6 at 20:11
9

Currently I'm trying to use the Z80 itself to interface with the screen [...] I have a section of RAM set aside for the framebuffer.

While there are actually some systems that directly "interface to the screen" in the sense of producing the output signal itself (which becomes even more interesting with the rigid timing required for TVs), the approach of "have a framebuffer, and let something else read out the framebuffer, so the CPU can do useful work" is a lot better.

So assuming your LCD screen needs a sequence of bytes describing the color of each pixel, you'd typically have a bunch of counters that control this timing, read out the framebuffer, do the font lookup, and send out the signal. The MC6845 and derivatives were popular chips for that, and even in today's graphics cards we still have the same concept.

Every time I press a key, it's added to the buffer.

This might be fine for a proof-of-concept, but that's not the general principle.

Is this really how video terminals worked originally, or do I have the basic concept all wrong?

For "I'm building a Z80-based breadboard computer from scratch to learn" I'd say it's a lot easier to just pick a cheap chip to drive your LCD from the framebuffer.

3

Before analog video interfaces pretty much disappeared entirely, one could find a handful of microprocessor-only video generators, mostly built for fun. My favorite was a recreation of the classic Pong game. Back then, a 4MHz PIC16 (with no frame-buffer) could keep up with a low-resolution, monochrome 15kHz raster display (e.g. VGA, NTSC, PAL, SECAM) if it did almost nothing else. Practically no modern CPU can "bit bang" the proper timing signals to drive a digital display like an active-matrix, raster LCD, unless they have a dedicated LCD interface built-in (which many systems-on-a-chip do). [Addendum: okay, maybe you could in a pinch, but accurate "cycle counting" is a thing of the past, so there will be glitches.]

Back to your "how would I build a video terminal," many early computer terminals contained no microprocessor at all. The video signal generator and font rendering were done entirely in hardware, first using a font-ROM and lots of simple chips, and later with a single video chip. Studying vintage computers will tell you how these video chips were used. Though many such video chips are themselves considered vintage, you'd be surprised what you can find on eBay.

If you want to do everything in software, consider building a light show out of an XY galvo laser scanner. A 25kpps unit can be purchased for under $65 on AliExpress, or double that on eBay. Lots of fun to show off. Sometimes even "bugs" will look cool.

2

I think you might have got it backwards.

If you consider your font bitmaps as a textures, the text location as spacial location, then you have a texture mapping problem, just like what GPU does today.

But GPU (and virtually everything else) implements this via reverse-mapping, i.e. iterate through each pixel, calculate which text location it is (of which you can easily keep a record while incrementing pixel row and column index) and what character it is, lookup its font bitmap, then output that pixel.

If you follow this procedure you'll find it's exactly what Ralf has described in his answer but optimized/vectorized.

Another issue of your issue with your approach is, you didn't make good use of the display's hidden memory. Any display device is a two-port RAM, with the 2D ramdom-access reading port facing us, the user, and the 1D sequential-access writing side facing the computer, the video cable. The operations described by Ralf and me are purely sequential, so no additional frame buffer is needed. The display itself is the frame buffer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.