Контроллер клавиатуры, реализованный на ПЛИС, описан на языке Verilog.
Я уже давно знаю, как устроена клавиатура и чем занимается контроллер клавиатуры в ЭВМ, но собственный контроллер мне ещё ни разу не доводилось реализовывать. Для начала я нашёл документацию на протокол PS/2. Моя клавиатура имеет интерфейс AT/XT (DIN-5); для PS/2 протокол не отличается.
Цоколёвка
Протокол
Клавиатура использует протокол очень схожий с UART. Передача скан-кода нажатой клавиши происходит по спаду синхросигнала.
Последовательность сигналов:
1. Стартовый бит - равен нулю.
2. 8 бит данных, это и есть наш скан-код.
3. Бит чётности.
4. Стоп - бит.
Код отпускания отличается от кода нажатия: в случае отпускания последовательно передаются два числа - F0 и скан-код нажатой клавиши. То же самое происходит, когда клавиша имеет расширенный скан-код, но последовательно отправляются уже 3 байта - E0, F0 и скан-код.
Исследование
Пора подключить клавиатуру к питанию и логическому анализатору, чтобы посмотреть всё «вживую». Для подачи питания на клавиатуру использую ПЛИС Altera Cyclone IV EP4CE6E22C8.
Запускаем программное обеспечение логического анализатора, включаем декодирование протокола клавиатуры PS/2 (хотя можно обойтись и без этого), нажимаем любую клавишу и наблюдаем за результатом.
Код нажатия:
Код отпускания:
Видно, что все сигналы строго соответствуют документации. Отлично, теперь можно описывать устройство на языке Verilog.
Описание устройства и тестирование
Код на языке Verilog:
Code: Select all
module kbd
(input kbd_clk, kbd_data,
output reg [7:0] scan = 0, output reg scan_ready = 0);
parameter start_bit = 0;
parameter receive_data = 1;
parameter parity = 2;
parameter stop_bit = 3;
reg [7:0] rxdata = 8'd0;
reg [3:0] bitcounter = 4'd0;
reg [1:0] state = 2'd0;
wire kbdclk;
wire kbddata;
assign kbdclk = kbd_clk;
assign kbddata = kbd_data;
always @(negedge kbdclk) begin
case(state)
start_bit: begin
if (kbddata == 1'b1) state = start_bit;
else begin
state <= receive_data;
scan_ready <= 1'b0;
rxdata <= 8'd0;
bitcounter <= 0;
end
end
receive_data: begin
if (bitcounter == 4'b1000) begin
state <= parity;
bitcounter <= 4'd0;
end else begin
bitcounter <= bitcounter + 1;
rxdata <= {kbddata,rxdata[7:1]};
end
end
parity: state <= stop_bit;
stop_bit: begin
if (kbddata == 1'b1) begin
scan = rxdata;
scan_ready <= 1'b1;
state = start_bit;
end else state <= start_bit;
end
default: state <= start_bit;
endcase
end
endmodule Теперь необходимо описать тестовое оборудование(test bench) и провести симуляцию работы устройства.
Code: Select all
`timescale 1us/1ns
module tb;
reg kbdclk;
reg kbddata;
wire [7:0] scan;
wire scan_ready;
integer i;
reg [10:0] frame;
kbd dut (
.kbd_clk(kbdclk),
.kbd_data(kbddata),
.scan(scan),
.scan_ready(scan_ready)
);
task send_bit(input reg b);
begin
kbddata = b;
#5;
kbdclk = 0;
#5;
kbdclk = 1;
#5;
end
endtask
initial begin
kbdclk = 1;
kbddata = 1;
frame[0] = 1'b0;
frame[1] = 1'b1;
frame[2] = 1'b0;
frame[3] = 1'b1;
frame[4] = 1'b0;
frame[5] = 1'b1;
frame[6] = 1'b0;
frame[7] = 1'b1;
frame[8] = 1'b0;
frame[9] = 1'b1;
frame[10] = 1'b1;
#20;
for (i = 0; i < 11; i = i + 1) send_bit(frame[i]); #50;
end
initial begin
$monitor("t=%0t kbdclk=%b kbddata=%b scan=%b scan_ready=%b state=%0d bitcounter=%0d rxdata=%b",
$time, kbdclk, kbddata, scan, scan_ready,
dut.state, dut.bitcounter, dut.rxdata);
end
endmoduleРезультат показывает нам, что все входные сигналы обрабатываются корректно, вывод тоже корректен, можно собирать устройство.
Загрузим прошивку и попробуем нажать клавишу 'F8'
Состояние светодиодов указывает на скан-код клавиши 0Ah, всё правильно.
Стоит отметить, что в моём случае ПЛИС согласована по сопротивлению с клавиатурой. Без согласования возникают отражения сигнала, из-за которых устройство не сможет корректно считывать скан-код. Поэтому согласование линии передачи по сопротивлению крайне важно.
Спасибо за внимание! Дополняйте тему своими исследованиями!