§ Описание

Эта программа была написана для того, чтобы принимать и передавать данные на клавиатуру. Прием осуществляется подключением ps_clk и ps_dat к физическому интерфейсу.
Чтобы отослать команду, для начала, надо дождаться статуса ready=1 и только после этого установить cmd=1 одновременно установить значение dat. Даже если сейчас происходит приём данных от устройства, то в этом случае, сначала закончится прием и только после отошлётся команда на устройство.
Частота clock должна быть 25 мгц. Можно и другую, но для этого придется исправлять локальную константу PERIOD.
Прием данных с устройства происходит по входящему стробу hit, который появляется раз в 1 такт, так что его нельзя пропускать и приоритет должен быть самый максимальный. Входящие данные будут в регистре kbd. В случае ошибки, появится err=1.

§ Программа

module keyboard
(
    // 25 MHZ
    input               clock,
    input               reset_n,

    // Рассылка команд
    input               cmd,            // =1 строб команды
    input       [7:0]   dat,            // Отсылаемый байт

    // Ввод-вывод на клавиатуру и наоборот
    inout               ps_clk,
    inout               ps_dat,

    // Исходящие данные от клавиатуры
    output  reg [7:0]   kbd,            // Данные от клавиатуры
    output  reg         hit,            // =1 Одиночный хит на получение данных от контроллера
    output  reg         err,            // =1 Ошибка обработки или таймаут
    output              ready           // =1 Устройство готово к приему команды cmd=1, dat
);

localparam
    PERIOD = 124,           // 100 kHz
    CWAIT  = 20;            // Ожидание перед отправкой команды x 5 мкс

localparam
    IDLE        = 0,
    RECEIVE     = 1,
    TRANSMIT    = 2;

assign ready  = (CMD == 0);
assign ps_clk = we_clk ? PS_CLK : 1'bz;
assign ps_dat = we_dat ? PS_DAT : 1'bz;

// 200 kHz делитель частоты (5 мкс один отсчет)
reg [ 1:0]  stage;
reg [ 7:0]  t;
reg [ 9:0]  dm;                 // Отсчет таймаута 5 мк x 1024 = 5 ms
reg [ 6:0]  dx;                 // 200 kHz
reg [ 1:0]  rt;                 // Обнаружение CLOCK
reg [ 3:0]  cnt;
reg         CMD;
reg [ 9:0]  DAT;
reg         we_clk, we_dat;
reg         PS_CLK, PS_DAT;

always @(negedge clock)
if (reset_n == 0) begin

    t       <= 0;
    dx      <= 0;
    dm      <= 0;
    we_clk  <= 0;
    we_dat  <= 0;
    cnt     <= 0;
    err     <= 0;
    stage   <= IDLE;
    CMD     <= 0;
    DAT     <= 8'h00;

end
else begin

    hit <= 0;

    // Регистрация команды для отсылки устройству
    if (cmd) begin CMD <= 1; DAT <= {/*STOP*/ 1'b1, /*PAR*/ ~^dat, dat}; err <= 0; end

    // 5 микросекунд, 200 kHz (основано на 25 Mhz) 25M / 125 = 200k
    if (dx == PERIOD) begin

        // Регистрация сигнала CLK от клавиатуры
        rt <= {rt[0], ps_clk};

        // Проверка на таймаут, сброс в IDLE при ошибке
        if (stage) begin dm <= dm + 1; if (&dm) begin stage <= IDLE; CMD <= 0; err <= 1; end end

        case (stage)

            // Ожидание команды от хоста или данных от клавиатуры/мыши
            // -----------------------------------------------------------------
            IDLE: begin

                t   <= 0;
                cnt <= 0;

                // [R] Получен сигнал от клавиатуры
                if (rt == 2'b10) begin

                    stage <= RECEIVE;
                    err   <= 0;

                end
                // [T] Запрос исполнения команды
                else if (CMD) begin

                    stage <= TRANSMIT;
                    err   <= 0;
                    {we_clk, we_dat} <= 2'b11;
                    {PS_CLK, PS_DAT} <= 2'b11;

                end

            end

            // Прием сигнала от устройства
            // https://ru.wikipedia.org/wiki/Скан-код
            // -----------------------------------------------------------------
            RECEIVE: if (rt == 2'b01) begin

                t  <= t + 1;
                dm <= 0;

                case (t)
                // Старт-бит должен быть равен 0, иначе не принимать данные
                0: if (ps_dat) begin stage <= IDLE; err <= 1; end

                // Прием LSB (8 бит)
                1,2,3,4,5,6,7,8: kbd <= {ps_dat, kbd[7:1]};

                // Сверка бита четности:
                // - Если количество ЧЕТНО, то там будет 0, а в PS_DAT=1, так что ОК
                // - И наоборот также, НЕЧЕТНО дает 1, а в PS_DAT=0, и это OK
                // - Иначе не OK ни разу
                9: begin hit <= ps_dat ^ (^kbd); end

                // Стоповый бит если 0, то это ошибка
                10: begin stage <= IDLE; err <= ~ps_dat; CMD <= 0; end
                endcase

            end

            // Процедура отсылки команды на клавиатуру
            // https://wiki.osdev.org/PS/2_Keyboard#Command_Queue_and_State_Machine
            // -----------------------------------------------------------------
            TRANSMIT: begin

                t <= t + 1;

                case (t)

                    // Пролог
                    CWAIT:    begin PS_CLK <= 0; end              // CLK=0 на 100 мк
                    CWAIT+20: begin PS_DAT <= 0; end              // DAT=0 на 50 мк
                    CWAIT+28: begin PS_CLK <= 1; end              // CLK=1 старт бит
                    CWAIT+29: begin we_clk <= 0; dm <= 0; end

                    // Ожидание 10 битов от клавиатуры
                    // Ожидание может длиться до 5 миллисекунд, иначе сброс
                    CWAIT+30: begin

                        t <= CWAIT+30;

                        // На негативном фронте CLK отправка данных
                        if (rt == 2'b10) begin

                            PS_DAT  <= DAT[0];
                            DAT     <= DAT[9:1];
                            cnt     <= cnt + 1;
                            dm      <= 0;

                        end
                        // Отосланы все 10 бит, на позитивном фронте CLK
                        else if (rt == 2'b01 && cnt == 10) t <= CWAIT+31;

                    end

                    // Ждать бита ACK от клавиатуры
                    CWAIT+31: we_dat <= 0;
                    CWAIT+33: begin dm <= 0; t <= CWAIT + (rt == 2'b01 ? 34 : 33); end

                    // Через 10 мкс завершить отсылку и перейти к стадии приема ответа от клавиатуры
                    CWAIT+34: t <= (rt == 2'b10) ? CWAIT+35 : CWAIT+34;
                    CWAIT+35: begin stage <= RECEIVE; t <= 0; end

                endcase

            end

        endcase

    end

    dx <= (dx == PERIOD) ? 0 : dx + 1;

end

endmodule