Page 1 of 1

Контроллер клавиатуры

Posted: Sun Mar 29, 2026 7:00 pm
by push0ret
Приветствую форумчан! Я уже давно забыл о существовании форума, занявшись другими делами, но недавно вспомнил о нём и решил описать одну из своих последних задач.

Контроллер клавиатуры, реализованный на ПЛИС, описан на языке Verilog.

Я уже давно знаю, как устроена клавиатура и чем занимается контроллер клавиатуры в ЭВМ, но собственный контроллер мне ещё ни разу не доводилось реализовывать. Для начала я нашёл документацию на протокол PS/2. Моя клавиатура имеет интерфейс AT/XT (DIN-5); для PS/2 протокол не отличается.

Цоколёвка
pinsdin5.png
Протокол

Клавиатура использует протокол очень схожий с UART. Передача скан-кода нажатой клавиши происходит по спаду синхросигнала.

Последовательность сигналов:

1. Стартовый бит - равен нулю.
2. 8 бит данных, это и есть наш скан-код.
3. Бит чётности.
4. Стоп - бит.

Код отпускания отличается от кода нажатия: в случае отпускания последовательно передаются два числа - F0 и скан-код нажатой клавиши. То же самое происходит, когда клавиша имеет расширенный скан-код, но последовательно отправляются уже 3 байта - E0, F0 и скан-код.

Исследование

Пора подключить клавиатуру к питанию и логическому анализатору, чтобы посмотреть всё «вживую». Для подачи питания на клавиатуру использую ПЛИС Altera Cyclone IV EP4CE6E22C8.
photo_2026-03-29_20-48-37.jpg
Запускаем программное обеспечение логического анализатора, включаем декодирование протокола клавиатуры PS/2 (хотя можно обойтись и без этого), нажимаем любую клавишу и наблюдаем за результатом.

Код нажатия:
photo_2026-03-28_16-09-48.jpg
Код отпускания:
photo_2026-03-28_16-10-12.jpg
Видно, что все сигналы строго соответствуют документации. Отлично, теперь можно описывать устройство на языке 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
Результат симуляции:
2026-03-29 214822.png
Результат показывает нам, что все входные сигналы обрабатываются корректно, вывод тоже корректен, можно собирать устройство.
photo_2026-03-29_21-55-38.jpg
Загрузим прошивку и попробуем нажать клавишу 'F8'
photo_2026-03-29_16-03-10.jpg
Состояние светодиодов указывает на скан-код клавиши 0Ah, всё правильно.

Стоит отметить, что в моём случае ПЛИС согласована по сопротивлению с клавиатурой. Без согласования возникают отражения сигнала, из-за которых устройство не сможет корректно считывать скан-код. Поэтому согласование линии передачи по сопротивлению крайне важно.

Спасибо за внимание! Дополняйте тему своими исследованиями!