Saturday, August 8, 2015

Get Out of My Way!

In case you haven't noticed, I am building a PDP-8.  Notice I didn't say an emulator or a simulator.  I consider what I am doing a new implementation of the PDP-8 architecture.  I won't explain that here, but perhaps another post later will explain why I say that.   Today, I want to talk a little about how all our modern technology can make things harder, and what to do about it.

The PDP-8 is a very simple machine.  That was very much intentional.  Although the technology of the day was primitive compared to what we have now, much larger and more complex machines were being built.  If you doubt that, do a bit of research on the CDC 6600, the first supercomputer, that arrived about the same time as the PDP-8.  Much of what we consider bleeding edge tech was pioneered with that machine. With discrete transistors!  But the PDP-8 was different.

The PDP-8 had a front panel with switches and lights that gave the user/programmer direct access to the internals of the CPU.  If you really want to know what your computer is doing, nothing is better.  You could look at and change all the internal registers.  Even those the assembly language programmer normally has no direct control of.  You could single step: not only by instruction as with a modern debugger, but also by cycles within an instruction!  As useful as the front panel was, it wasn't very convenient for many uses.  So a standard interface was a terminal connected to a serial line.  The normal terminal of the day was a mechanical teletype.  A teletype was much like a typewriter that sent keystrokes over the serial port and printed what was received over the serial port.  They were often used as terminals (even on early microcomputers!) well into the 1980s.

The PDP-8 hardware to interface with a teletype was rather simple as was most of its hardware.  The I/O instructions of the machine are quite simple and the hardware was designed to match.  They teletype hardware had a flag (register bit) to indicate if a character had been received, and another to indicate a character was finished sending.  The IOT (input output transfer) instruction could test one of those flags and skip the next instruction if it was set.  By following the IOT instruction with a jump instruction back to the IOT, you could have a loop that continuously tested the flag, waiting on a character, until a character was received.  When the flag was set indicating the arrival of a character, the jump would be skipped over and the following code would read the incoming character.  Simple, neat, and clean.  A simple flip flop and a general test instruction allowed a two instruction loop to wait on the arrival of a character.

We want to build a new PDP-8 using a microcontroller.  The chosen micro (NXP LPC1114 to start) will have a serial port that is fully compatible with the old teletype interface.  Remarkably, that interface was old when the PDP-8 was created, and we still use it basically unchanged today.  The interface hardware for serial ports like that today is called a UART (Universal Asynchronous Receiver Transmitter) or some variation of that.  Lo and behold, it will have that same flag bit to indicate a recieved (or transmitted) character!  Nice and simple.  That won't present any problems for our emulation code.  But we want to do our main development on a PC using all the power ( and multiple large screens!) to make our lives easier. So, since C language is "portable" and the standard language for embedded (microcontroller) work, we write in C on the PC for most of the work, then move to the micro for the final details.  I prefer Linux, whose history goes back almost as far as the PDP-8.  Alas, Linux gets in the way.

You see, Linux, like Unix before it, is intended to be a general purpose, multi-tasking, multi-user operating system.  In one word, that can be summed up as "abstraction."  In a general purpose operating system, you may want to attach any number of things to the computer and call it a terminal.  But you only want to write code one way instead of writing it for every different type of terminal differently.  So you create an abstract class of devices called "terminal" that defines a preset list of features that all the user programs will use to communicate with whatever is connected as a terminal.  Then, the OS (operating system) has "drivers" for each type of device that makes communicating with them the same and convenient for most user programs.  Notice the word "most."

Generally, most user programs aren't (or weren't in the early 1970s) interested in knowing if a key had been pressed or when, only getting the character that key represented. So Unix (and by virtue of being mostly a clone of Unix, Linux) doesn't by default make that information available.  Instead, you try to read from the terminal and if nothing is available your program "blocks."  That means it just waits for a character.  That is great in a multi-tasking, multi-user OS: the OS can switch to another program and run that while it waits.  But it doesn't work for simulating a PDP-8 hardware: we have no way to simply test for a received character and continue.  Our program will stop and control will go to Linux while it waits for a character to be typed.

Now I must digress and mention that Windows does indeed have by default a method to test for a keystroke: kbhit().  The kbhit function in Windows (console mode) is exactly what we need.  It returns a zero if no key has been pressed, and non-zero if one has.  Windows is a multi-tasking OS similar to Linux, so why does it have kbhit() and Linux doesn't?  Not really by design, I assure you, but by default and compatability.  Windows grew from DOS, which was a single-tasking, single-user, personal computer OS.  DOS was designed much inline with how the PDP-8 was designed.  When Windows was added on top of DOS, it had to bring along the baggage.  That chafed the Windows "big OS" designers a lot.

Now one of the things that made Unix (and Linux) so popular was that it would allow the programmer (and user) to do most anything that made sense.  You may have to work at it a bit, but you can do it. The multitude of creators over the last 45 years have made it possible to get at most low-level details of the hardware in one way or another.  I knew I wasn't the first to face this exact problem.  The kbhit function is well used.  So off to the Googles.  And, sure enough, I found a handful of examples that were nearly the same.  So I copied one.

//////////////////////////////////////////////////////////////////////////////
///   @file kbhit.c
///   @brief from Thantos on StackOverflow
///    http://cboard.cprogramming.com/c-programming/63166-kbhit-linux.html
///
///  find out if a key has been pressed without blocking
///
//////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include "kbhit.h"

void console_nonblock(int dir)
{
   static struct termios oldt, newt;
   static int old_saved = 0;

   if (dir == 1)
   {
      tcgetattr( STDIN_FILENO, &oldt);
      newt = oldt;
      newt.c_lflag &= ~( ICANON | ECHO);
      tcsetattr( STDIN_FILENO, TCSANOW, &newt);
      old_saved = 1;
   }
   else
   {
      if(old_saved == 1)
      {
         tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
         old_saved = 0;
      }
   }
}

int kbhit(void)
{
   struct timeval tv;
   fd_set rdfs;

   tv.tv_sec = 0;
   tv.tv_usec = 0;

   FD_ZERO(&rdfs);
   FD_SET(STDIN_FILENO, &rdfs);

   select(STDIN_FILENO+1, &rdfs, NULL, NULL, &tv);
   return FD_ISSET(STDIN_FILENO, &rdfs);
}

int test_kbhit(void)
{
   int ch;
   console_nonblock(1);  // select kbhit mode
   while( !kbhit())
   {
      putchar('.');
   }
   ch=getchar();
   printf("\nGot %c\n", ch);
   console_nonblock(0);
   return 0;
}

That's an awful lot of code to expose a flip-flop that is already there.  But such is the nature of abstraction.  Having a powerful OS in charge of such low-level matters is the right way to go.  But all too often it makes doing simple things difficult, if not impossible.  I'm glad that the creators of Unix and Linux gave me the option to get the OS out of the way

1 comment:

  1. Interesting. I ran into the kbhit while setting up a pause_until_key_pressed() in VC++. It stuck out since the documentation seemed to say, "This exist;you can use it, but don't! Ever!"

    ReplyDelete