Help with PS2 keyboard interface? - Page 1
Forum section: electronics / general

author: nockieboy
author_rank: Super Contributor
timestamp: January 31, 2020, 06:31:53 pm
content: So I've been learning as I'm going, developing theFPGA 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:module keyboard (input wire clk, // FPGA-based clock (50 MHz)input wire reset,input wire ps2d, ps2c, // PS2 data and clock linesoutput wire [7:0] scan_code, // scan_code received from keyboard to processoutput wire scan_code_ready, // signal to outer control system to sample scan_codeoutput 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 declarationslocalparam BREAK = 8'hF0, // break codeSHIFT1 = 8'h12, // first shift scanSHIFT2 = 8'h59, // second shift scanCAPS = 8'h58; // caps lock// FSM symbolic stateslocalparam [2:0] lowercase = 3'b000, // idle, process lower case lettersignore_break = 3'b001, // ignore repeated scan code after break code -F0- receivedshift = 3'b010, // process uppercase letters for shift key heldignore_shift_break = 3'b011, // check scan code after F0, either idle or go back to uppercasecapslock = 3'b100, // process uppercase letter after capslock button pressedignore_caps_break = 3'b101; // check scan code after F0, either ignore repeat, or decrement caps_num// internal signal declarationsreg [2:0] state_reg, state_next; // FSM state register and next state logicwire [7:0] scan_out; // scan code received from keyboardreg got_code_tick; // asserted to write current scan code received to FIFOwire scan_done_tick; // asserted to signal that ps2_rx has received a scan codereg letter_case; // 0 for lower case, 1 for uppercase, outputed to use when converting scan code to asciireg [7:0] shift_type_reg, shift_type_next; // register to hold scan code for either of the shift keys or caps lockreg [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 registeralways @(posedge clk, posedge reset) beginif (reset) beginstate_reg <= lowercase;shift_type_reg <= 0;caps_num_reg <= 0;endelse beginstate_reg <= state_next;shift_type_reg <= shift_type_next;caps_num_reg <= caps_num_next;endend//FSM next state logicalways @* begin// defaultsgot_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/capslocklowercase: beginif(scan_done_tick) begin // if scan code receivedif(scan_out == SHIFT1 || scan_out == SHIFT2) begin // if code is shiftshift_type_next = scan_out; // record which shift key was pressedstate_next = shift; // go to shift stateendelse if(scan_out == CAPS) begin // if code is capslockcaps_num_next = 2'b11; // set caps_num to 3, num of capslock scan codes to receive before going back to lowecasestate_next = capslock; // go to capslock stateendendelse if (scan_out == BREAK) begin // else if code is break codestate_next = ignore_break; // go to ignore_break stateendelse begin // else if code is none of the above...got_code_tick = 1'b1; // assert got_code_tick to write scan_out to FIFOendend// state to ignore repeated scan code after break code FO received in lowercase stateignore_break: beginif(scan_done_tick) begin // if scan code received,state_next = lowercase; // go back to lowercase stateendend// state to process scan codes after shift received in lowercase stateshift: beginletter_case = 1'b1; // routed out to convert scan code to upper value for a keyif(scan_done_tick) begin // if scan code received,if(scan_out == BREAK) begin // if code is break codestate_next = ignore_shift_break; // go to ignore_shift_break state to ignore repeated scan code after F0endelse if(scan_out != SHIFT1 && scan_out != SHIFT2 && scan_out != CAPS) begin // else if code is not shift/capslockgot_code_tick = 1'b1; // assert got_code_tick to write scan_out to FIFOendendend// state to ignore repeated scan code after break code F0 received in shift stateignore_shift_break: beginif(scan_done_tick) begin // if scan code receivedif(scan_out == shift_type_reg) begin // if scan code is shift key initially pressedstate_next = lowercase; // shift/capslock key unpressed, go back to lowercase stateendelse begin // else repeated scan code received, go back to uppercase statestate_next = shift;endendend// state to process scan codes after capslock code received in lowercase statecapslock: beginletter_case = 1'b1; // routed out to convert scan code to upper value for a keyif(caps_num_reg == 0) begin // if capslock code received 3 times,state_next = lowercase; // go back to lowecase stateendif(scan_done_tick) begin // if scan code receivedif(scan_out == CAPS) begin // if code is capslock,caps_num_next = caps_num_reg - 1; // decrement caps_numendelse if(scan_out == BREAK) begin // else if code is break, go to ignore_caps_break statestate_next = ignore_caps_break;endelse if(scan_out != SHIFT1 && scan_out != SHIFT2) begin // else if code isn't a shift keygot_code_tick = 1'b1; // assert got_code_tick to write scan_out to FIFOendendend// state to ignore repeated scan code after break code F0 received in capslock stateignore_caps_break: beginif(scan_done_tick) begin // if scan code receivedif(scan_out == CAPS) begin // if code is capslockcaps_num_next = caps_num_reg - 1; // decrement caps_numendstate_next = capslock; // return to capslock stateendendendcaseendassign letter_case_out = letter_case; // output, route letter_case to output to use during scan to ascii code conversionassign scan_code_ready = got_code_tick; // output, route got_code_tick to out control circuit to signal when to sample scan_outassign scan_code = scan_out; // route scan code data outendmoduleAnd here's ps2_rx.sv:Code:module ps2_rx(input wire clk,input wire rst,input wire ps2d, ps2c, // ps2 data and clock inputs, receive enable inputoutput reg rx_done_tick, // ps2 receive done tickoutput wire [7:0] rx_data // data received);parameter nDelay = 500000;// 10 millisecond Rx timeoutreg [19:0] timeout_counter;reg timeout = 1'b0;parameter rxDelay = 500000;// 10 millisecond delay before next char can be rx'dreg [19:0] rx_counter;// FSMD state declarationlocalparamidle = 1'b0,rx = 1'b1;// internal signal declarationreg state_reg, state_next; // FSMD state registerreg [7:0] filter_reg; // shift register filter for ps2cwire [7:0] filter_next; // next state value of ps2c filter registerreg f_val_reg; // reg for ps2c filter value, either 1 or 0wire f_val_next; // next state for ps2c filter valuereg [3:0] n_reg, n_next; // register to keep track of bit numberreg [10:0] d_reg, d_next; // register to shift in rx datawire neg_edge; // negative edge of ps2c clock filter valuewire reset = rst || timeout;// register for ps2c filter register and filter valuealways @(posedge clk, posedge reset) beginif (reset) beginfilter_reg<= 0;f_val_reg<= 0;timeout<= 0;timeout_counter<= 20'b0;endelse beginfilter_reg<= filter_next;f_val_reg<= f_val_next;if (neg_edge) begintimeout_counter<= 20'b0;// reset timeout counterendelse begintimeout_counter<= timeout_counter + 1;endif (timeout_counter == nDelay - 1) begintimeout<= 1'b1;endendend// next state value of ps2c filter: right shift in current ps2c value to registerassign 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 changeassign 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 0assign neg_edge = f_val_reg & ~f_val_next;// assign data to the outputassign rx_data = d_reg[8:1]; // output data bits// FSMD state, bit number and data registersalways @(posedge clk, posedge reset) beginif (reset) beginstate_reg<= idle;n_reg<= 0;d_reg<= 0;endelse beginstate_reg<= state_next;n_reg<= n_next;d_reg<= d_next;endend// Rx delay counteralways @(posedge clk) beginif (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 logicalways @* begin// defaultsstate_next= state_reg;rx_done_tick= 1'b0;n_next= n_reg;d_next= d_reg;case (state_reg)idle:beginif (neg_edge) begin // start bit receivedn_next= 4'b1010; // set bit count down to 10state_next= rx; // go to rx stateendendrx: // shift in 8 data, 1 parity, and 1 stop bitbeginif (neg_edge) begin // if ps2c negative edge...d_next = {ps2d, d_reg[10:1]};// sample ps2d, right shift into data registern_next = n_reg - 1; // decrement bit countendif (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 tickstate_next= idle; // go back to idleendendendcaseendendmoduleI 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.

author: nockieboy
author_rank: Super Contributor
timestamp: January 31, 2020, 06:33:08 pm
content: 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?

author: BrianHG
author_rank: Super Contributor
timestamp: January 31, 2020, 07:55:51 pm
content: http://www.eecg.toronto.edu/~jayar/ece241_08F/AudioVideoCores/ps2/ps2.html

author: miken
author_rank: Regular Contributor
timestamp: February 01, 2020, 05:07:01 am
content: 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.)

author: nockieboy
author_rank: Super Contributor
timestamp: February 01, 2020, 04:01:12 pm
content: Quote from: BrianHG on January 31, 2020, 07:55:51 pmThere 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..

author: nockieboy
author_rank: Super Contributor
timestamp: February 01, 2020, 04:04:11 pm
content: Quote from: miken on February 01, 2020, 05:07:01 amHi 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.

author: BrianHG
author_rank: Super Contributor
timestamp: February 01, 2020, 05:56:47 pm
content: 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!

author: hamster_nz
author_rank: Super Contributor
timestamp: February 02, 2020, 06:43:29 am
content: Are com_clk and clk in the diagram the same clock?

author: Wiljan
author_rank: Regular Contributor
timestamp: February 02, 2020, 08:30:15 am
content: 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

author: nockieboy
author_rank: Super Contributor
timestamp: February 02, 2020, 08:36:12 am
content: Quote from: hamster_nz on February 02, 2020, 06:43:29 amNo -com_clkis 50 MHz,clkis 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.

author: nockieboy
author_rank: Super Contributor
timestamp: February 02, 2020, 08:46:34 am
content: Quote from: Wiljan on February 02, 2020, 08:30:15 amHi 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.

author: nockieboy
author_rank: Super Contributor
timestamp: February 02, 2020, 05:48:35 pm
content: Quote from: BrianHG on February 01, 2020, 05:56:47 pmThis is something that's on my to-do list.Quote from: BrianHG on February 01, 2020, 05:56:47 pmWell, 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?Quote from: BrianHG on February 01, 2020, 05:56:47 pmThere's a parity bit sent with each 11-bit packet from the PS2 keyboard. That's about it for data integrity checking, though.Quote from: BrianHG on February 01, 2020, 05:56:47 pmI'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.Quote from: BrianHG on February 01, 2020, 05:56:47 pmWell, 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.Quote from: BrianHG on February 01, 2020, 05:56:47 pmThat'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.

author: hamster_nz
author_rank: Super Contributor
timestamp: February 02, 2020, 08:01:52 pm
content: Quote from: nockieboy on February 02, 2020, 08:36:12 amGiven 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.

author: nockieboy
author_rank: Super Contributor
timestamp: February 03, 2020, 09:59:46 am
content: Quote from: hamster_nz on February 02, 2020, 08:01:52 pmMostly 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.

author: Wiljan
author_rank: Regular Contributor
timestamp: February 04, 2020, 03:22:11 am
content: Here is a verilog example you could try outhttps://www.google.com/amp/s/embeddedthoughts.com/2016/07/05/fpga-keyboard-interface/amp/