Close

BASIC running! And keyboard input now works!

A project log for RC2016/99: TI-99/4A clone using TMS99105 CPU

Retrochallenge 2016/10 entry and winner: try to build a TI-99/4A clone using a TMS99105 CPU and an FPGA. In a month...

erik-piehlErik Piehl 10/16/2016 at 17:520 Comments

I am in the process of uploading a video to youtube to show that now my project is at the point where it can run Basic! Unfortunately my internet upload speed is very slow, so it is going to take a while.

Earlier I had problems entering Basic due to interrupts being stuck - the reason turned out to be a bug in the VHDL code for the FPGA. The TI expects to find the VDP interrupt status in CRU bit 2, but I had accidentally made that information visible CRU bit 1. Due to this the interrupt service routine did not see that the interrupt was coming from the VDP, instead it assumed it was coming from the expansion bus.

After fixing the bug I got the Basic successfully booted up! It was time to add keyboard input.

Keyboard input

I have written a program called "memloader" which can transfer data from the PC's disk to the memory on the FPGA board. The transfers happen over a RS-232 serial bus (through USB). On the receiving side the FPGA includes a state machine, which implements the communication computer protocol. The output of the state machine are memory write and read commands.

I implemented an additional 8 byte memory region in the FPGA, at memory address 0x100000, i.e. at 1 megabyte. This memory is implemented as registers in the FPGA, it is not backed by the external SRAM. The 8 bytes (8 bits/byte) correspond to the keyboard matrix, so there is 1 bit per key, including joystick buttons.

On the PC side I added to the memory loader program the ability to monitor the PC's keyboard. The key states are mapped to a bitmap which corresponds to the TI keyboard matrix:

   printf("Sending keyboard state codes to TI994A\n");
    OpenSerialPort();
    ClearSerialPortBuffers();
    char keybuf[8];
    while(1) {
      memset(keybuf, 0xff,sizeof(keybuf));
      if (GetAsyncKeyState(VK_LSHIFT) & 0x8000) {
        keybuf[0] &= ~0x20;
      }
      if (GetAsyncKeyState(VK_LCONTROL) & 0x8000) {
        keybuf[0] &= ~0x40;
      }
     // code removed for clarity
      if (GetAsyncKeyState('0') & 0x8000) { keybuf[5] &= ~0x08; }
      if (GetAsyncKeyState('1') & 0x8000) { keybuf[5] &= ~0x10; }
      if (GetAsyncKeyState('2') & 0x8000) { keybuf[1] &= ~0x10; }
      if (GetAsyncKeyState('3') & 0x8000) { keybuf[2] &= ~0x10; }
      if (GetAsyncKeyState('4') & 0x8000) { keybuf[3] &= ~0x10; }
      if (GetAsyncKeyState('5') & 0x8000) { keybuf[4] &= ~0x10; }
      if (GetAsyncKeyState('6') & 0x8000) { keybuf[4] &= ~0x08; }
      if (GetAsyncKeyState('7') & 0x8000) { keybuf[3] &= ~0x08; }
      if (GetAsyncKeyState('8') & 0x8000) { keybuf[2] &= ~0x08; }
      if (GetAsyncKeyState('9') & 0x8000) { keybuf[1] &= ~0x08; }

      if (GetAsyncKeyState('A') & 0x8000) { keybuf[5] &= ~0x20; }
      if (GetAsyncKeyState('B') & 0x8000) { keybuf[4] &= ~0x80; }
      if (GetAsyncKeyState('C') & 0x8000) { keybuf[2] &= ~0x80; }
      if (GetAsyncKeyState('D') & 0x8000) { keybuf[2] &= ~0x20; }
      if (GetAsyncKeyState('E') & 0x8000) { keybuf[2] &= ~0x40; }

// a section removed
      WriteMemoryBlock(keybuf, 0x100000, 8);
    }
On the side of the FPGA, there is an implementation that maps these 8 bytes to CRU bits used for keyboard input:
-- declaration section
type keyboard_array is array (7 downto 0, 7 downto 0) of std_logic;
signal keyboard : keyboard_array;

-- later in the code the two dimensional array "keyboard" is mapped
-- to CRU bit like so:
if cpu_addr(15 downto 1) & '0' >= 6 and cpu_addr(15 downto 1) & '0' < 22 then
                                        ki := to_integer(unsigned(cpu_addr(3 downto 1))) - 3; -- row select on address
                                        cru_read_bit <= keyboard(to_integer(unsigned(cru9901(20 downto 18))), ki); -- column select on multiplexor select
                                elsif cpu_addr(15 downto 1) & '0' >= 6 and cpu_addr(15 downto 1) & '0' < 22 then

                                        ki := to_integer(unsigned(cpu_addr(3 downto 1))) - 3; -- row select on address
                                        cru_read_bit <= keyboard(to_integer(unsigned(cru9901(20 downto 18))), ki); -- column select on multiplexor select
end if;

Discussions