Sanity check: Ps/2 Keyboard on an fpga

Hi,

I’m in need of some sanity check on my myHDL code. I’ve written a PS/2 interface with the intent on emulating a Keyboard on an FPGA.

Bare in mind I am a novice in HDL and FPGA, playing around as a hobby.

My testbench are behaving as expected, but when I put it on the FPGA, I have some trouble.

My ps2 core is the following (hopefully this is easy to read):


from myhdl import block, always, always_seq, modbv, intbv, enum, Signal, now

@block
def ps2_ctrl(clock, ps2_clk, ps2_dat, data_in, data_out, send_data, data_sent, data_received, parity_error):

    t_state = enum('IDLE', 'DTH_START', 'DTH_BYTE', 'DTH_PARITY', 'DTH_STOP', 'DTH_SENT', 'HTD_START', 'HTD_BYTE', 'HTD_PARITY', 'ACK', 'HTD_END')
    t_clock_state = enum('RISE', 'HIGH', 'FALL', 'LOW')

    state = Signal(t_state.IDLE)
    clock_state = Signal(t_clock_state.LOW)

    parity = Signal(bool(0))

    bit_idx = Signal(intbv(val=0, min=0, max=8))

    wait_interupt = Signal(intbv(val=0, min=0, max=2**5))

    ps2_clk_d = ps2_clk.driver()
    ps2_dat_d = ps2_dat.driver()
    
    @always(clock.posedge)
    def proc():
        if state != t_state.IDLE:
            # Generate ps/2 clock when not idling
            if clock_state == t_clock_state.HIGH:
                ps2_clk_d.next = None
                clock_state.next = t_clock_state.FALL
            elif clock_state == t_clock_state.FALL:
                ps2_clk_d.next = False
                clock_state.next = t_clock_state.LOW
            elif clock_state == t_clock_state.LOW:
                ps2_clk_d.next = False
                clock_state.next = t_clock_state.RISE
            elif clock_state == t_clock_state.RISE:
                ps2_clk_d.next = None
                clock_state.next = t_clock_state.HIGH

        # Host request the line to send data
        if clock_state == t_clock_state.HIGH and ps2_clk == False:
            ps2_clk_d.next = None
            ps2_dat_d.next = None
            state.next = t_state.HTD_START

        # idle state if nothing is happening
        elif state == t_state.IDLE:
            ps2_clk_d.next = None
            ps2_dat_d.next = None
            data_received.next = 0
            data_sent.next = 0
            wait_interupt.next = 0
            clock_state.next = t_clock_state.HIGH
            parity_error.next = 0
            if send_data == 1:
                # Stop idling and start sending a message
                parity.next = 1
                data_sent.next = 0
                data_received.next = 0
                data_out.next = 0x00
                state.next = t_state.DTH_START

        # *********************************************************************
        # ********************* Device To Host ********************************
        # *********************************************************************

        # IDLE => DTH_START (bit 0) => DTH_BYTE (8bit of data_in) => DTH_PARITY (parity bit) => DTH_STOP (bit 1) => DTH_SENT (signaling) => IDLE

        elif state == t_state.DTH_START:
            if clock_state == t_clock_state.HIGH:
                ps2_dat_d.next = False
                bit_idx.next = 0
                state.next = t_state.DTH_BYTE
                

        elif state == t_state.DTH_BYTE:
            if clock_state == t_clock_state.HIGH:
                ps2_dat_d.next = False if not data_in[bit_idx] else None
                parity.next = not parity and data_in[bit_idx]
                if bit_idx == 7:
                    state.next = t_state.DTH_PARITY
                else:
                    bit_idx.next = bit_idx + 1

        elif state == t_state.DTH_PARITY:
            if clock_state == t_clock_state.HIGH:
                ps2_dat_d.next = False if not parity else None
                state.next = t_state.DTH_STOP

        elif state == t_state.DTH_STOP:
            if clock_state == t_clock_state.HIGH:
                ps2_dat_d.next = None
                state.next = t_state.DTH_SENT

        elif state == t_state.DTH_SENT:
            if clock_state == t_clock_state.HIGH:
                data_sent.next = 1
                ps2_dat_d.next = None
                clock_state.next = t_clock_state.HIGH
                if send_data == 0:
                    state.next = t_state.IDLE

        # *********************************************************************
        # ********************** Host to Device *******************************
        # *********************************************************************

        # ANY => HTD_START (receive bit 0) => HTD_BYTE (8 bit goes to data_out) => HTD_PARITY (check parity error) => ACK => HTD_END => IDLE

        elif state == t_state.HTD_START:
            ps2_clk_d.next = None
            ps2_dat_d.next = None
            clock_state.next = t_clock_state.RISE
            if ps2_dat == False:
                state.next = t_state.HTD_BYTE
                bit_idx.next = 0
                clock_state.next = t_clock_state.HIGH
            else:
                if wait_interupt == 31:
                    state.next = t_state.IDLE
                else:
                    wait_interupt.next = wait_interupt + 1

        elif state == t_state.HTD_BYTE:
            if clock_state == t_clock_state.HIGH:
                data_out.next[bit_idx] = bool(ps2_dat == None)
                parity.next = not parity and bool(ps2_dat == None)
                if bit_idx == 7:
                    state.next = t_state.HTD_PARITY
                else:
                    bit_idx.next = bit_idx + 1

        elif state == t_state.HTD_PARITY:
            if clock_state == t_clock_state.HIGH:
                if parity != ps2_dat:
                    parity_error.next = 1
                state.next = t_state.ACK

        elif state == t_state.ACK:
            if clock_state == t_clock_state.HIGH:
                ps2_dat_d.next = False
                data_received.next = 1
            if data_received == 1:
                data_received.next = 0
                state.next = t_state.HTD_END
        
        elif state == t_state.HTD_END:
            if clock_state == t_clock_state.HIGH:
                state.next = t_state.IDLE

    return proc

I instanciate it like this:

    # Actual FPGA pin
    clock = Signal(bool(0)) # 50 MHz
    ps2_clk = TristateSignal(bool(0))
    ps2_dat = TristateSignal(bool(0))

    # inside signals
    ps2_data_in = Signal(intbv(val=0, min=0, max=2**8))
    ps2_data_out = Signal(intbv(val=0, min=0, max=2**8))
    send_data = Signal(bool(0))
    data_sent = Signal(bool(0))
    data_received = Signal(bool(0))
    parity_error = Signal(bool(0))

    clock_i = Signal(bool(0))

    clock_i_driver = clock_reducer(clock, clock_i, 1000) # 50 MHz clock reduced to 50 kHz

    ps2_interface = ps2_ctrl(clock_i, ps2_clk, ps2_dat, ps2_data_in, ps2_data_out, send_data, data_sent, data_received, parity_error)

    #########
    # .....Code elipsis to avoid cluttering the post....
    #########

I’m basing the implementation on multiple source but in particular this one: Wayback Machine

I have additional code to drive it and get output back

I convert the myHDL code to VHDL and then synthesize to a DE0-nano (cyclone IV) through JTAG

I plug my gpios into a ‘Ps/2 to USB adapter’, my understanding is that there is a ps/2 controller inside the adapter (I don’t know which one)

I manage to send data, correct key presses are registered on the pc but are spammed forever (even though I send them only once followed by the break codes)

When I receive data from the adapter, it’s always 0x00, sometimes there are parity error in the message. This led me to believe I must do something wrong in my host-to-device implementation.

Any idea what I did wrong ? What can I do to further test/fix this ?

Hi,

There are missing pieces of code to give a definitive answer.
When simulation works but not the implementation, either the simulation test vectors are not correct (not modeling the real signals) or you have meta-stability/synchronization problems. Meta-stability/synchronization problems do not arise in simulation since they can’t be modeled.

You can read about meta-stability/synchronization problems here.

And here is The link exploring many CDC techniques in details. The language used in this paper is System Verilog but it is easy to transpose to MyHDL. On the same site, there are many other interesting papers to explore.

Hi - I have some verilog that works if you want it, but not mapped to myhdl yet - I know it doesn’t answer your question, but be aware of clean (pref synch) resets on power up and metastability on all sampled asynch inputs, oh and debounce them if necessary - Metastability in an FPGA