PS/2 keyboard and FPGA Computer
Added PS/2 keyboard to the FPGA Computer
This is a follow-up of the FPGA computer post. 
I have added a keyboard port to the FPGA Computer. The port is PS/2 because it is easier to work with the PS/2 than with the USB HID protocol. The final look is here (you will recognize the purple PS/2 keyboard connector):
        I have added a keyboard port to the FPGA Computer. The port is PS/2 because it is easier to work with the PS/2 than with the USB HID protocol. The final look is here (you will recognize the purple PS/2 keyboard connector):
            The hardware part of this project is simple - add four resistors and a
            PS/2 connector:
            
PS/2 connector is connected to the GPIO ports of the DE0-NANO board:
- Data is connected to the GPIO31 (PIN_D11) port
- Clock is connected to the GPIO33 (PIN_B12) port.
            
            
            
            
This could be interpreted as: A pressed (1C), A released (F0 1C)
Unfortunately, it is all not that simple. First of all, if you quickly press A and C, one after another, you will get the following sequence:
1C 1B F0 1B F0 1C
This could be interpreted as: make code of "A", make code of "S", break code of "S" and break code of "A".
When you press Shift + A, you will get the following sequence:
12 1C F0 1C F0 12
Shift pressed, A pressed, A released, Shift released
When you press A for a long time (autorepeat will occur):
1C 1C 1C 1C 1C F0 1C
A pressed, A pressed, A pressed, A pressed, A pressed, A released (F0 1C)
To make things more complicated, extended key codes (both make and break) have been introduced, for some keys. For example, the Cursor Down (Arrow Down) key produces the following sequence:
E0 72 E0 F0 72
Cursor down pressed (E0 72), Cursor down released (E0 F0 72).
And so on... All this makes parsing a bit complicated, but eventually you will be able to figure it out.
The next step was to add the support for the keyboard within the FPGA Computer.
                
                
                
                
                
In the cpu.v module, I have added a support for the new interrupt:
                
So, to receive bytes from the PS/2 keyboard, a programmer
                must register the IRQ#2 handler:
; set the IRQ handler for keyboard to our own IRQ handler
mov r0, 1 ; JUMP instruction opcode
mov r1, IRQ2_ADDR ; IRQ#2 vector address
st [r1], r0
mov r0, irq_triggered
mov r1, IRQ2_ADDR + 2
st [r1], r0
Since this is raw PS/2 handling, the programmer must write the complete make/break code handling. I have done that in this example.
Unfortunately, the code is quite long since it has to deal with the raw PS/2 protocol. The code demonstrates parsing the raw PS/2 protocol and it looks like those vintage screen editors:
            
            
            
            
            
            
            
            
            
            
            
    Now the
                    board has three connectors: PS/2, VGA and UART.
PS/2 connector is connected to the GPIO ports of the DE0-NANO board:
- Data is connected to the GPIO31 (PIN_D11) port
- Clock is connected to the GPIO33 (PIN_B12) port.
The communication between keyboard and computer is a clocked serial. Clock
                pulses appear on the Clock pin, while data is on the Data pin, synchronized with the Clock on the
                falling edge. There is one start bit, one parity bit and one stop bit. Here are oscilloscope snapshots
                of the "A" key being pressed (and then released):
The waveform below is the make code of the "A" key
                (1C hex)
The waveform below is the first byte of the
                "A" break code (F0 hex)
            
            The waveform below is the second byte of the
                "A" break code (1C hex)
            
            Keyboards work by sending the make and the break codes for each key. Make
                code is sent when the key is pressed, while the break code is sent when the key is released. For
                example, when we press and then release the "A" key, we get the following sequence:
1C F0
                1CThis could be interpreted as: A pressed (1C), A released (F0 1C)
Unfortunately, it is all not that simple. First of all, if you quickly press A and C, one after another, you will get the following sequence:
1C 1B F0 1B F0 1C
This could be interpreted as: make code of "A", make code of "S", break code of "S" and break code of "A".
When you press Shift + A, you will get the following sequence:
12 1C F0 1C F0 12
Shift pressed, A pressed, A released, Shift released
When you press A for a long time (autorepeat will occur):
1C 1C 1C 1C 1C F0 1C
A pressed, A pressed, A pressed, A pressed, A pressed, A released (F0 1C)
To make things more complicated, extended key codes (both make and break) have been introduced, for some keys. For example, the Cursor Down (Arrow Down) key produces the following sequence:
E0 72 E0 F0 72
Cursor down pressed (E0 72), Cursor down released (E0 F0 72).
And so on... All this makes parsing a bit complicated, but eventually you will be able to figure it out.
The next step was to add the support for the keyboard within the FPGA Computer.
Introducing the keyboard interrupt
I have introduced a new interrupt for the keyboard - the IRQ#2. This
                    IRQ is triggered when a byte from PS/2 keyboard arrives. The CPU then jumps to the address of 24
                    decimal, where the raw PS/2 keyboard handling routine should be. Actually, at that address should be
                    one JUMP instruction which will jump to the handling routine.
                In the main computer module, I have instantiated the PS/2 module:
                // ####################################
                // PS/2 keyboard instance
                // ####################################
                wire [7:0] ps2_data;
                wire ps2_received;
                reg [7:0] ps2_data_r;
                ps2_read ps2(
                  CLOCK_50,
                  reset,
                  gpio0[31],  // Input
                        pin - PS/2 data line
                  gpio0[33],  // Input
                        pin - PS/2 clock line
                  ps2_data,   // here we
                        will receive a character
                  ps2_received  // if something came from PS/2, this goes
                        high
                ); 
                Then I have detected the byte being received from the PS/2 module and
                    triggered the IRQ:
 always @ (posedge CLOCK_50) begin
                 // ######### IRQ2 -
                        keyboard ######
                 if (ps2_received)
                        begin
                 ps2_data_r <=
                        ps2_data;
                 // if we have received a
                        byte from 
                 // the keyboard, we will
                        trigger the IRQ#2
                 irq[2] <=
                        1'b1;
                 end 
                 ...
                In the cpu.v module, I have added a support for the new interrupt:
if (irq_r[2]) begin
                
 `ifdef DEBUG
                
                
 LED[7] <= 1;
                
                 $display("3.1 JUMP TO
                        IRQ #2 SERVICE");
                 `endif
                
 pc <= 16'd24;
                
                 addr <=
                        16'd12;
                end
                ; set the IRQ handler for keyboard to our own IRQ handler
mov r0, 1 ; JUMP instruction opcode
mov r1, IRQ2_ADDR ; IRQ#2 vector address
st [r1], r0
mov r0, irq_triggered
mov r1, IRQ2_ADDR + 2
st [r1], r0
Since this is raw PS/2 handling, the programmer must write the complete make/break code handling. I have done that in this example.
Unfortunately, the code is quite long since it has to deal with the raw PS/2 protocol. The code demonstrates parsing the raw PS/2 protocol and it looks like those vintage screen editors:
How to use the keyboard? First of all, two
                callbacks should be registered - one for the key pressed, and the other one for the key released:
            
                    mov r0, 1 ; JUMP instruction opcode
            
                    mov r1, KEY_PRESSED_HANDLER_ADDR
            
                    st [r1], r0
            
                    mov r0, pressed ; key pressed routine address
            
            
                    mov r1, KEY_PRESSED_HANDLER_ADDR + 2
            
                    st [r1], r0
            
                    mov r0, 1 ; JUMP instruction opcode
            
                    mov r1, KEY_RELEASED_HANDLER_ADDR
            
                    st [r1], r0
            
                    mov r0, released ; key released routine address
            
            
                    mov r1, KEY_RELEASED_HANDLER_ADDR + 2
            
                    st [r1], r0
            Both callbacks will then need to obtain the
                virtual key code of the key pressed (or released) by reading from the location 48
                (VIRTUAL_KEY_ADDR):
            pressed: 
            
                    ld r0, [VIRTUAL_KEY_ADDR]
            
                    cmp r0, VK_F1 
            ...
            released: 
            
                    ld r1, [VIRTUAL_KEY_ADDR]
            ...
            What is the Virtual Key Code? It is a
                number assigned to each key, so all the programs would get the same number when a key is pressed, or
                released. In the code above, VK_F1 is the constant assigned to the F1 key, so the programmer can
                determine if the F1 was pressed by writing cmp r0, VK_F1.
            Then, if needed, programmer can call the
                vk_to_char function which translates a virtual key to  the actual character, if possible
                (not all keys produce characters; F1 key does not produce character, for example):
            ;
                    ###############################
            
; r1 = function vk_to_char(r1)
            
            ; translates virtual key to
                    character
            ; if shift is pressed, does the
                    uppercase
            ;
                    ###############################
            vk_to_char:
            
                    push r0
            
                    push r2
            ...
            Conclusion
Most examples for keyboard support on the net use PS/2 keyboards, since
                USB HID protocol is quite complex and PS/2 isn't. I went the same path. I have couple of spare
                keyboards, some of them are PS/2, so I have soldered the PS/2 female connector and those four resistors
                from the schematics above. From that point on, everything was programming - a little bit of Verilog
                programming, and much more of assembler programming.






Comments
Comments powered by Disqus