Sanity check: Ps/2 Keyboard on an fpga (original) (raw)

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 ?