Author Topic: Help with PS2 keyboard interface?  (Read 3546 times)

0 Members and 1 Guest are viewing this topic.

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Help with PS2 keyboard interface?
« on: January 31, 2020, 06:31:53 pm »
So I've been learning as I'm going, developing the FPGA GPU with BrianHG, but my Verilog-fu is failing me now.  :-\

I've found a PS2 keyboard interface somewhere online (yes, I know that's a terrible way to start a sentence) and have used it to try and provide some form of keyboard input via the FPGA to my DIY computer.

The problem I'm having is that I'm getting duplicated characters - if I press A, I get AA appear on the screen.  This seems to be because the keyboard.sv module is ignoring the BREAK keycode but not the following keycode for the key that is released.  I can hold a key down and just get one character on the screen (until the repeat timer kicks in, obviously).

Sometimes I get three characters appear for one keypress, too, which is causing me real stress, messing around with latches and debouncers, so I thought I'd focus on this one main problem first.

Here's the code for the main keyboard.sv module - it instantiates a ps2_rx.sv which is included below:

Code: [Select]
module keyboard (
   input wire clk,                  // FPGA-based clock (50 MHz)
   input wire reset,
   input wire ps2d, ps2c,           // PS2 data and clock lines
   output wire [7:0] scan_code,     // scan_code received from keyboard to process
   output wire scan_code_ready,     // signal to outer control system to sample scan_code
   output wire letter_case_out      // output to determine if scan code is converted to lower or upper ascii code for a key
);

parameter TIMEOUT = 500000;
parameter RXDELAY = 500000;
   
// constant declarations
localparam  BREAK    = 8'hF0, // break code
            SHIFT1   = 8'h12, // first shift scan
            SHIFT2   = 8'h59, // second shift scan
            CAPS     = 8'h58; // caps lock

// FSM symbolic states
localparam [2:0] lowercase          = 3'b000, // idle, process lower case letters
                 ignore_break       = 3'b001, // ignore repeated scan code after break code -F0- received
                 shift              = 3'b010, // process uppercase letters for shift key held
                 ignore_shift_break = 3'b011, // check scan code after F0, either idle or go back to uppercase
                 capslock           = 3'b100, // process uppercase letter after capslock button pressed
                 ignore_caps_break  = 3'b101; // check scan code after F0, either ignore repeat, or decrement caps_num

// internal signal declarations
reg  [2:0] state_reg, state_next;             // FSM state register and next state logic
wire [7:0] scan_out;                          // scan code received from keyboard
reg        got_code_tick;                     // asserted to write current scan code received to FIFO
wire       scan_done_tick;                    // asserted to signal that ps2_rx has received a scan code
reg        letter_case;                       // 0 for lower case, 1 for uppercase, outputed to use when converting scan code to ascii
reg  [7:0] shift_type_reg, shift_type_next;   // register to hold scan code for either of the shift keys or caps lock
reg  [1:0] caps_num_reg, caps_num_next;       // keeps track of number of capslock scan codes received in capslock state (3 before going back to lowecase state)
   

// **************  instantiate PS2 receiver  **************
ps2_rx ps2_rx_unit (
   .clk(clk),
   .rst(reset),
   .ps2d(ps2d),
   .ps2c(ps2c),
   .rx_done_tick(scan_done_tick),
   .rx_data(scan_out)
);

defparam ps2_rx_unit.nDelay = TIMEOUT,
         ps2_rx_unit.rxDelay = RXDELAY;
// ********************************************************


// FSM stat, shift_type, caps_num register
always @(posedge clk, posedge reset) begin

   if (reset) begin
     
      state_reg      <= lowercase;
      shift_type_reg <= 0;
      caps_num_reg   <= 0;
     
   end
   else begin
     
      state_reg      <= state_next;
      shift_type_reg <= shift_type_next;
      caps_num_reg   <= caps_num_next;
     
   end
   
end

//FSM next state logic
always @* begin

   // defaults
   got_code_tick   = 1'b0;
   letter_case     = 1'b0;
   caps_num_next   = caps_num_reg;
   shift_type_next = shift_type_reg;
   state_next      = state_reg;
   
   case(state_reg)
     
// state to process lowercase key strokes, go to uppercase state to process shift/capslock
      lowercase: begin
         
         if(scan_done_tick) begin                                        // if scan code received
           
            if(scan_out == SHIFT1 || scan_out == SHIFT2) begin           // if code is shift   
               
               shift_type_next = scan_out;                               // record which shift key was pressed
               state_next = shift;                                       // go to shift state
               
            end
            else if(scan_out == CAPS) begin                              // if code is capslock
               
               caps_num_next = 2'b11;                                    // set caps_num to 3, num of capslock scan codes to receive before going back to lowecase
               state_next = capslock;                                    // go to capslock state
               
            end
           
         end   
         else if (scan_out == BREAK) begin                               // else if code is break code
           
            state_next = ignore_break;                                   // go to ignore_break state
           
         end
         else begin                                                      // else if code is none of the above...           
           
            got_code_tick = 1'b1;                                        // assert got_code_tick to write scan_out to FIFO
           
         end
         
      end   
     
      // state to ignore repeated scan code after break code FO received in lowercase state
      ignore_break: begin
         
         if(scan_done_tick) begin                                        // if scan code received,
           
            state_next = lowercase;                                      // go back to lowercase state
           
         end
         
      end
     
      // state to process scan codes after shift received in lowercase state
      shift: begin
         
         letter_case = 1'b1;                                             // routed out to convert scan code to upper value for a key
         
         if(scan_done_tick) begin                                        // if scan code received,
           
            if(scan_out == BREAK) begin                                  // if code is break code                                           
               
               state_next = ignore_shift_break;                          // go to ignore_shift_break state to ignore repeated scan code after F0
               
            end
            else if(scan_out != SHIFT1 && scan_out != SHIFT2 && scan_out != CAPS) begin            // else if code is not shift/capslock
               
               got_code_tick = 1'b1;                                     // assert got_code_tick to write scan_out to FIFO
               
            end
           
         end
         
      end
     
      // state to ignore repeated scan code after break code F0 received in shift state
      ignore_shift_break: begin
         
         if(scan_done_tick) begin                                        // if scan code received
           
            if(scan_out == shift_type_reg) begin                         // if scan code is shift key initially pressed
               
               state_next = lowercase;                                   // shift/capslock key unpressed, go back to lowercase state
               
            end
            else begin                                                   // else repeated scan code received, go back to uppercase state
               
               state_next = shift;
               
            end
           
         end
         
      end 
     
      // state to process scan codes after capslock code received in lowercase state
      capslock: begin
         
         letter_case = 1'b1;                                             // routed out to convert scan code to upper value for a key
         
         if(caps_num_reg == 0) begin                                     // if capslock code received 3 times,
           
            state_next = lowercase;                                      // go back to lowecase state
           
         end
         
         if(scan_done_tick) begin                                        // if scan code received
           
            if(scan_out == CAPS) begin                                   // if code is capslock,
               
               caps_num_next = caps_num_reg - 1;                         // decrement caps_num
               
            end
            else if(scan_out == BREAK) begin                             // else if code is break, go to ignore_caps_break state
               
               state_next = ignore_caps_break;
               
            end
            else if(scan_out != SHIFT1 && scan_out != SHIFT2) begin      // else if code isn't a shift key
               
               got_code_tick = 1'b1;                                     // assert got_code_tick to write scan_out to FIFO
               
            end
           
         end
         
      end
     
      // state to ignore repeated scan code after break code F0 received in capslock state
      ignore_caps_break: begin
         
         if(scan_done_tick) begin                                        // if scan code received
           
            if(scan_out == CAPS) begin                                   // if code is capslock
               
               caps_num_next = caps_num_reg - 1;                         // decrement caps_num
               
            end
           
            state_next = capslock;                                       // return to capslock state
           
         end
         
      end
     
   endcase
   
end

assign letter_case_out = letter_case;                                    // output, route letter_case to output to use during scan to ascii code conversion
assign scan_code_ready = got_code_tick;                                  // output, route got_code_tick to out control circuit to signal when to sample scan_out
assign scan_code = scan_out;                                             // route scan code data out
   
endmodule


And here's ps2_rx.sv:
Code: [Select]
module ps2_rx (

input wire clk,
input wire rst,
input wire ps2d, ps2c,    // ps2 data and clock inputs, receive enable input
output reg rx_done_tick,         // ps2 receive done tick
output wire [7:0] rx_data        // data received

);

parameter nDelay = 500000; // 10 millisecond Rx timeout
reg [19:0] timeout_counter;
reg timeout = 1'b0;

parameter rxDelay = 500000; // 10 millisecond delay before next char can be rx'd
reg [19:0] rx_counter;

// FSMD state declaration
localparam
idle = 1'b0,
rx   = 1'b1;

// internal signal declaration
reg state_reg, state_next;          // FSMD state register
reg [7:0] filter_reg;               // shift register filter for ps2c
wire [7:0] filter_next;             // next state value of ps2c filter register
reg f_val_reg;                      // reg for ps2c filter value, either 1 or 0
wire f_val_next;                    // next state for ps2c filter value
reg [3:0] n_reg, n_next;            // register to keep track of bit number
reg [10:0] d_reg, d_next;           // register to shift in rx data
wire neg_edge;                      // negative edge of ps2c clock filter value

wire reset = rst || timeout;

// register for ps2c filter register and filter value
always @(posedge clk, posedge reset) begin

if (reset) begin

filter_reg <= 0;
f_val_reg  <= 0;
timeout <= 0;
timeout_counter <= 20'b0;

end
else begin

filter_reg <= filter_next;
f_val_reg  <= f_val_next;

if (neg_edge) begin

timeout_counter<= 20'b0; // reset timeout counter

end
else begin

timeout_counter<= timeout_counter + 1;

end

if (timeout_counter == nDelay - 1) begin

timeout <= 1'b1;

end

end

end

// next state value of ps2c filter: right shift in current ps2c value to register
assign filter_next = {ps2c, filter_reg[7:1]};

// filter value next state, 1 if all bits are 1, 0 if all bits are 0, else no change
assign f_val_next = (filter_reg == 8'b11111111) ? 1'b1 : (filter_reg == 8'b00000000) ? 1'b0 : f_val_reg;

// negative edge of filter value: if current value is 1, and next state value is 0
assign neg_edge = f_val_reg & ~f_val_next;

// assign data to the output
assign rx_data = d_reg[8:1]; // output data bits

// FSMD state, bit number and data registers
always @(posedge clk, posedge reset) begin

if (reset) begin

state_reg <= idle;
n_reg <= 0;
d_reg <= 0;

end
else begin

state_reg <= state_next;
n_reg <= n_next;
d_reg <= d_next;

end

end

// Rx delay counter
always @(posedge clk) begin

if (rx_counter > 0)
rx_counter <= rx_counter - 1'b1;

if (rx_done_tick == 1'b1 && rx_counter < 1'b1)
rx_counter <= rxDelay;

end

// FSMD next state logic
always @* begin

// defaults
state_next = state_reg;
rx_done_tick = 1'b0;
n_next = n_reg;
d_next = d_reg;

case (state_reg)

idle:
begin
if (neg_edge) begin             // start bit received

n_next = 4'b1010;       // set bit count down to 10
state_next = rx;            // go to rx state

end
end

rx:                                // shift in 8 data, 1 parity, and 1 stop bit
begin
if (neg_edge) begin             // if ps2c negative edge...

d_next = {ps2d, d_reg[10:1]};// sample ps2d, right shift into data register
n_next = n_reg - 1;          // decrement bit count

end

if (n_reg == 0) begin           // after 10 bits shifted in, go to done state

//if (rx_counter == 0)
rx_done_tick = 1'b1;      // assert dat received done tick

state_next = idle;       // go back to idle

end
end

endcase

end

endmodule


I know this is probably a big ask, but if anyone can spot any glaring errors in the code that may be causing the state machine to mess up on the BREAK keycodes, I'd be pleased to hear from you!

All these modules are running on the FPGA's 50 MHz clock, including the debouncers on PS2_DATA and PS2_CLK inputs.
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: Help with PS2 keyboard interface?
« Reply #1 on: January 31, 2020, 06:33:08 pm »
Here's the diagram:



(Are there any workarounds for the bug in these forums that don't allow you to attach an image in a post if it has [ code] tags in it?
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 8019
  • Country: ca
« Last Edit: January 31, 2020, 07:59:33 pm by BrianHG »
 

Offline miken

  • Regular Contributor
  • *
  • Posts: 102
  • Country: us
Re: Help with PS2 keyboard interface?
« Reply #3 on: February 01, 2020, 05:07:01 am »
My suggestions on how to attack this: One, 'compile in your head' by drawing out the functionality on paper. Two, put in the integrated logic analyzer (SignalTap) to see what's going on. Iterate as necessary. (Forgive me for not being more directly helpful, as I've been doing precisely the aforementioned at work recently.)
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: Help with PS2 keyboard interface?
« Reply #4 on: February 01, 2020, 04:01:12 pm »
http://www.eecg.toronto.edu/~jayar/ece241_08F/AudioVideoCores/ps2/ps2.html

There I was creating a new post trying not to bother you as you're busy with your new job, and the first reply I get is from you!  ;)

Yes, I'm already aware of the site you've linked and the PS2 core.  It produces the same results, although I'm using the same state machine I posted in my OP to try to remove duplicates.  It must be somehow faulty and I can't see what's going wrong.

Another thing, if I don't filter out the data from the keyboard interface in the GPU, I get a steady stream of 240's (and even more zeros, but they're indicating no key has been pressed so I'm not worried about them). 240 = F0, which is the BREAK code.  Not sure why I'd be receiving a constant stream of them, and they're likely why the state machine in the keycode->ASCII conversion module is failing to ignore the keycode after the BREAK code.  I wonder if the keyboard isn't happy..  :horse:
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: Help with PS2 keyboard interface?
« Reply #5 on: February 01, 2020, 04:04:11 pm »
My suggestions on how to attack this: One, 'compile in your head' by drawing out the functionality on paper. Two, put in the integrated logic analyzer (SignalTap) to see what's going on. Iterate as necessary. (Forgive me for not being more directly helpful, as I've been doing precisely the aforementioned at work recently.)

Hi miken!  Thanks for the advice - although I'm a little ahead of the design and iterate stage, I'm well into debugging now and writing out my previous post has given me some inspiration into why the state machine might be failing; it's getting confused by the constant stream of BREAK codes.  :-BROKE
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 8019
  • Country: ca
Re: Help with PS2 keyboard interface?
« Reply #6 on: February 01, 2020, 05:56:47 pm »
Try making a dumb stand alone project.  Tie the PS2 logic to a UART and display the results on a PC to see what is happening.
Are you getting multiple clocks cyclyes of the scan code ready?
Maybe you need a one shot?


Sorry, I've never done a PS2 keyboard design before...

Are you sure the debouncers belong on the serial stream?
Might that not mess up the data as the stream is already at around 10Mhz?
Without any checksums, how do you know there are data in the serial port layer?
What does the scope shot look like?
Do you get the errors on both of your development boards?

If data errors truly exist on the PS2 data and clock, try changing the terminator resistors, add filter caps to GND, and try changing the length of the software filter.  Use an oscilloscope to see what's happening in real time.

Also, TRY A DIFFERENT KEYBOARD!
« Last Edit: February 01, 2020, 06:36:17 pm by BrianHG »
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2812
  • Country: nz
Re: Help with PS2 keyboard interface?
« Reply #7 on: February 02, 2020, 06:43:29 am »
Are com_clk and clk in the diagram the same clock?

Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 
The following users thanked this post: BrianHG

Offline Wiljan

  • Regular Contributor
  • *
  • Posts: 230
  • Country: dk
Re: Help with PS2 keyboard interface?
« Reply #8 on: February 02, 2020, 08:30:15 am »
Here is a good explanation on PS2 keyboard and also source code, hope it helps (looks a lot as yours)
https://www.digikey.com/eewiki/pages/viewpage.action?pageId=28278929
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: Help with PS2 keyboard interface?
« Reply #9 on: February 02, 2020, 08:36:12 am »
Are com_clk and clk in the diagram the same clock?

No - com_clk is 50 MHz, clk is 125 MHz.  I tried using latches to synchronise the signals coming out of the PS2_Code_Converter module into the key2ascii module, but that seemed to cause more issues than it solved for some reason (i.e. it stopped working).  I've since connected the key2ascii module up to com_clk as well, but its output goes to another module (not shown) which runs at 125 MHz, so the clock boundary issue remains - so I've tried the below setup to handle the two clock domains (similar to something I recall BrianHG using for the RS232 module output of the GPU):



I'm not sure it's 100% wired correctly, and have since found a web page talking about clock domain crossing and using three sets of flip flops, like this:



I'm still getting output, but it's hardly an improvement on what I had before, if anything some chars are being dropped now.
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: Help with PS2 keyboard interface?
« Reply #10 on: February 02, 2020, 08:46:34 am »
Here is a good explanation on PS2 keyboard and also source code, hope it helps (looks a lot as yours)
https://www.digikey.com/eewiki/pages/viewpage.action?pageId=28278929

Hi Wiljan!  I had initially discounted that site when I was looking for examples originally as it was written in VHDL - I barely understand Verilog, so was trying to find examples in that HDL.  Looks like I will have to revisit this site as it is a good resource of information, as you've stated, with some interesting source code.
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: Help with PS2 keyboard interface?
« Reply #11 on: February 02, 2020, 05:48:35 pm »
Try making a dumb stand alone project.  Tie the PS2 logic to a UART and display the results on a PC to see what is happening.
Are you getting multiple clocks cyclyes of the scan code ready?
Maybe you need a one shot?

This is something that's on my to-do list.

Are you sure the debouncers belong on the serial stream?

Might that not mess up the data as the stream is already at around 10Mhz?

Well, according to the few projects I've found online where they are used, it seems so, yes.  They don't seem to make a lot of difference, with or without though.  I think the data stream should be in the 30 KHz ballpark, though?

Without any checksums, how do you know there are data in the serial port layer?

There's a parity bit sent with each 11-bit packet from the PS2 keyboard.  That's about it for data integrity checking, though.

What does the scope shot look like?

I'm not convinced it's an error in the data transmission itself, per se - my biggest concern is the constant stream of F0 BREAK codes being sent when I'm not even touching the keyboard.  My next step is to modify the state machine tomorrow to ignore BREAK codes that don't follow a valid keycode.

Do you get the errors on both of your development boards?

If data errors truly exist on the PS2 data and clock, try changing the terminator resistors, add filter caps to GND, and try changing the length of the software filter.  Use an oscilloscope to see what's happening in real time.

Well, I'm using the EP4CE6 dev board, with the built-in PS2 connector.  It appears from the schematics that correct pullups are being used...  The other 'dev board' with the Cyclone II on it is literally just a bare board with IO ports on it, so no - I haven't tested on that yet but if it gets to the point I've exhausted everything else, I will give it a try.

Also, TRY A DIFFERENT KEYBOARD!

That's one of my main priorities at the moment, but I only have the one PS2 keyboard currently - another one is in the post.  This one worked fine on its host computer a few years ago, however I have no way of testing it now as my desktops don't have PS2 connectors anymore and I don't have a PS2->USB adapter.
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2812
  • Country: nz
Re: Help with PS2 keyboard interface?
« Reply #12 on: February 02, 2020, 08:01:52 pm »
Are com_clk and clk in the diagram the same clock?

No - com_clk is 50 MHz, clk is 125 MHz.  I tried using latches to synchronise the signals coming out of the PS2_Code_Converter module into the key2ascii module, but that seemed to cause more issues than it solved for some reason (i.e. it stopped working).  I've since connected the key2ascii module up to com_clk as well, but its output goes to another module (not shown) which runs at 125 MHz, so the clock boundary issue remains - so I've tried the below setup to handle the two clock domains (similar to something I recall BrianHG using for the RS232 module output of the GPU):

Given that your fast clock is 2.5x faster than your slow clock, and you get sometimes two or three key presses, I think that this is likely your problem, rather than the PS/2 interface itself.

The "technically most correct" answer would be to put a dual-clock FIFO between the two clock domains. The less correct answer is to look for the rising edge of the scan_code_ready signal, and then take the data on the *next* clock edge - because the data should available for 2.5 cycles.
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: Help with PS2 keyboard interface?
« Reply #13 on: February 03, 2020, 09:59:46 am »
Are com_clk and clk in the diagram the same clock?

No - com_clk is 50 MHz, clk is 125 MHz.  I tried using latches to synchronise the signals coming out of the PS2_Code_Converter module into the key2ascii module, but that seemed to cause more issues than it solved for some reason (i.e. it stopped working).  I've since connected the key2ascii module up to com_clk as well, but its output goes to another module (not shown) which runs at 125 MHz, so the clock boundary issue remains - so I've tried the below setup to handle the two clock domains (similar to something I recall BrianHG using for the RS232 module output of the GPU):

Given that your fast clock is 2.5x faster than your slow clock, and you get sometimes two or three key presses, I think that this is likely your problem, rather than the PS/2 interface itself.

The "technically most correct" answer would be to put a dual-clock FIFO between the two clock domains. The less correct answer is to look for the rising edge of the scan_code_ready signal, and then take the data on the *next* clock edge - because the data should available for 2.5 cycles.

Mostly the duplicate codes are when I release the key, so the state machine is hiding the BREAK code but not the keycode that comes after it, but there still the odd 'third' keycode slipping through in places.  In any case, I think I need to sort this clock domain issue out before I start analysing the state machine or contemplating tackling any other issues.  :-+
 

Offline Wiljan

  • Regular Contributor
  • *
  • Posts: 230
  • Country: dk
Re: Help with PS2 keyboard interface?
« Reply #14 on: February 04, 2020, 03:22:11 am »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf