sy2002 / QNICE-FPGA

QNICE-FPGA is a 16-bit computer system for recreational programming built as a fully-fledged System-on-a-Chip in portable VHDL.
http://qnice-fpga.com
Other
69 stars 15 forks source link

Feature request: Add ability to poll current keyboard status #142

Closed MJoergen closed 3 years ago

MJoergen commented 4 years ago

In real-time games, it is useful to poll whether a given key is currently pressed down or not. Currently, the keyboard interface is a FIFO, which gives information of which keys have been pressed, but not whether they are still pressed down or have been released.

For gaming it is sufficient with a single read-only register:

This feature will not work over UART, so it is only applicable to the PS2 keyboard interface.

Modifier keys like SHIFT, CTRL, ALT, ALT_GR are used to determine the ASCII code of the key, so can not be independently polled. In other words, if only a SHIFT key is pressed (and no other key), then the register just reads as zero.

If more than one ASCII-key is pressed simultaneously, it is TBD what should happen. I suggest it should be determined by whatever is easiest to implement (in hardware and in emulator).

bernd-ulmann commented 4 years ago

Concerning the UART: Do we already have a function that checks if there is a character in the receive buffer? This would be quite useful, too, what do you think?

sy2002 commented 4 years ago

@bernd-ulmann Yes, we do have such a bit: You are checking for bit 0 to be set in IO$UART_SRA. Example code in UART$GETCHARin monitor/uart_library.asm.

@MJoergen:

Good point! And spot-on for our V1.7 vision "Make QNICE-FPGA a retro multimedia machine" 😄 🕹ī¸ We will for sure need that as soon as we write an action game. For Q-Tris, this was not necessary, yet but I assume it will be for PaqNice (issue #141) or for the space shooter etc.

Being able to get simultaneous keys would be very helpful, too, so that we can create one of my favorite childhood games called "MRON" (a self-programmed TRON variant): Up to 8 players are sitting around one keyboard, every player has two keys ;-) So we might want to be able to receive an array of currently pressed keys.

Simplest way to do it I think would be:

IO$KBD_RT               .EQU         0xFF06

"RT" stands for realtime. If this register is not zero, then a key is pressed and the semantics whch key is pressed is exactly the same as IO$KBD_DATA. But of course in contrast to IO$KBD_DATA this is "realtime" and therefore the "new key" flag handling is ignored. The modifiers can already today be read in realtime from IO$KBD_STATE:

;    Bits 5..7 (read only):   Modifiers: 5 = shift, 6 = alt, 7 = ctrl
;                             Only valid, when bits 0 and/or 1 are '1'

This mechanism does not cater for multi-keypresses. We could solve that by having an internal buffer in hardware and returning round-robin all the keys that are pressed in parallel. I think this is how PC keyboards work? As we have a pretty fast CPU, for the player this would still feel like "simultaneously".

So I guess this is the minimum thing we'd like to have @MJoergen ?

When we take the BRK instruction into consideration that Bernd mentioned the other day: BRK would hand over the address and data bus to a device and the CPU pauses until the device is returning from the BRK situation:

IO$KBD_RTMATRIX         .EQU         0xFF06

IO$KBD_RTMATRIX contains an address. If it is non-zero then: BRK IO$KBD_RTMATRIX, 8 will make the keyboard controller to fill 8 words in memory with 8 keys that are pressed simultaneously.

P.S. (and off-topic): We could also use this for VGA for simple DMA: BRK VGA$READRAM, 1024 would read 1024 words from RAM starting at the value that has been written into register VGA$READRAM and VGA$WRITERAM the same for writing...

MJoergen commented 4 years ago

Concerning the UART: Do we already have a function that checks if there is a character in the receive buffer? This would be quite useful, too, what do you think?

There is no public function, but it can be polled in the UART status register.

But yes, it would be nice to have a non-blocking function that returns the character in the FIFO (if there is one), and otherwise returns 0. We could call it cpeek() unless there is a better name.

MJoergen commented 3 years ago

This mechanism does not cater for multi-keypresses. We could solve that by having an internal buffer in hardware and returning round-robin all the keys that are pressed in parallel. I think this is how PC keyboards work? As we have a pretty fast CPU, for the player this would still feel like "simultaneously".

So in a typical game there would be a loop something like this:

while (1)
{
   int key = MMIO(IO_KBD_RT);
   if (!key)
      break;
   handle_key_pressed(key);
}

The hardware implementation maintains an internal state of all the current keys. Each read returns the next pressed-down key, or zero if no more.

If this correctly understood?

One additional question: If the user pressed, say, <SHIFT> and <A>, should that be considered two separate keys, i.e. something like scan codes, or should it be a single key "shifted A"?

sy2002 commented 3 years ago

Let's think for a second about the use cases: For "standard use cases" we already have our existing keyboard functions, so this "realtime" use case we are talking about here is more the use case for games etc. Given that this is true: Do we want to give up this prerequisite mentioned in my comment above:

If this register is not zero, then a key is pressed and the semantics whch key is pressed is exactly the same as IO$KBD_DATA

I think we should. Because then we could really allow the programmer to have full control over the keyboard and build completely own semantics and even use special keys such as the "MEGA" key (on the MEGA65 keyboard) or the Windows key or others to be used in games, etc.

If we want to go that route, then each and every key should have its own code and we also should distinguish in the hardware's FIFO buffer between a key is initally pressed (make code) and a key is released (break code).

We might also think about using the lower byte of the word for the keys themselves (including special codes for special keys) and the upper byte to signal, if this key was actually pressed or if this key was actually released.

How do we handle different keyboard layouts? (US keyboard, German keyboard, Danish keyboard, ...)?

These are just some thoughts to spark a discussion / brainstorm. I have no strong opinion about this whole topic and would gladly leave any decision how to proceed up to you @MJoergen (as Bernd is not that much into any keyboards other than serial terminals 😄 )

And/or we can make a (virtual) meeting about it, if you want.

MJoergen commented 3 years ago

If we want to go that route, then each and every key should have its own code

I agree, We can just use the PS2 keyboard scancode for that. This makes us completely ignorant of keyboard layouts.

we also should distinguish in the hardware's FIFO buffer between a key is initally pressed (make code) and a key is released (break code).

Hmm. The distinction is between an event-based or a state-based interface.

If we provide an event-based interface that will greatly simplify the hardware, since the hardware itself receives events from the keyboard, and these events can then just be placed into a FIFO, almost without modification. From a user perspective, the user can just repeatedly poll this FIFO and act on any events the user is interested in, and ignore all others. This seems like a simple interface for the user too. The user is though required to maintain state for the keys of interest.

So the interface to this FIFO is just a single register IO_KBD_EVENT, where bits 7-0 is the scancode, and bit 15 indicates whether it is a "make" code or a "break" code.

Sample code could look like this (where KBD_BREAK = 0x8000):

while (1)
{
   switch (MMIO(IO_KBD_EVENT))
   {
      case KBD_KEY_UP :             up = true; break;
      case KBD_KEY_UP | KBD_BREAK : up = false; break;
   }
   ...
}

This seems reasonable to me, what do you think ?

sy2002 commented 3 years ago

Sounds like an excellent plan. We could even use bit 0 to 14 for the scancode for truely huge keyboards and bit 15 for make or break :-)

MJoergen commented 3 years ago

Added support in emulator.

MJoergen commented 3 years ago

I've added support in hardware too now. But now I just notice a problem.

The emulator reports the ASCII key code (e.g. 0x31 if I press the '1' key), whereas on the hardware the scan code is reported (e.g. 0x16 for the '1' key).

So what shall we choose? Perhaps we should follow the emulator and report the ASCII code. The reason for this would be that different keyboard locales map keys to different characters. E.g. the German keyboard layout has swapped 'y' and 'z' compared to the US keyboard layout.

So I suggest the value read from IO_KBD_EVENT should be the ASCII code and not the scan code. We then need to define reasonable codes for arrow keys, etc, but I suppose we could just make the hardware give the same values as the emulator?

Any thoughts, ideas, suggestions, objections, etc ?

MJoergen commented 3 years ago

Come to think of it, perhaps it's better to use the same encoding in IO_KBD_DATA and IO_KBD_EVENT. Argh, then both emulator and hardware need changing!

MJoergen commented 3 years ago

The hardware is now updated. The functionality (in the hardware) is that the registers IO_KBD_DATA and IO_KBD_EVENT are almost identical. The first one only records key presses (i.e. "make" events), whereas the second records both key presses and key releases.

So this now leaves me wondering, whether we really want both? I mean, a program could just read from IO_KBD_EVENT and then ignore the "break" events, i.e. all values with 15 = 1.

I've started working on a simple tennis game, which makes use of the new IO_KBD_EVENT register, see tennis.c. Right now, it is just possible to move a ball around the screen using the arrow keys.

sy2002 commented 3 years ago

Hi Michael, I consider backward compatibility a huge topic as I do not want to re-write all the apps that we have. Given that, I think an idea to merge both is having a flag in our keyboard Control and Status Register (CSR):

  1. Scratch IO_KBD_EVENT
  2. Introduce a new flag to the CSR IO_KBD_STATE: Bit 15 for example: This bit controls the behaviour: if it is zero then IO_KBD_DATA behaves like it always did. If it is 1 then IO_KBD_DATA behaves like thew new IO_KBD_EVENT

This way we just have one keyboard register...

MJoergen commented 3 years ago

I agree, backward compatibility is very valuable.

However, I don't like having a register with multiple different behaviors and I don't like having a "mode" bit.

Instead, I propose we stick with the latest implementation:

This is simple to understand and the two registers work independently, i.e. reading from one register does not change the value in the other register.

The above is already implemented in hardware, but not yet (correctly) in the emulator.

There is one thing to note: Using this scheme we do not get events when pressing the modifier keys (e.g. SHIFT, CTRL, ALT). This is a limitation in the current implementation. So if the user presses <SHIFT> and <1> then the user only gets a single event with the value 0x21.

TODO:

Anything else ?

sy2002 commented 3 years ago

Your latest https://github.com/sy2002/QNICE-FPGA/issues/142#issuecomment-713334658 sounds like a good, simple and (q)nice plan forward. I can live with the modifier key "problem", because you can always use the status register to read them in realtime.

MJoergen commented 3 years ago

Documentation now updated.