PS/2リーダー

またしてもこの本でVerilogだ。

FPGA ボードで学ぶ組込みシステム開発入門 ?Altera編?

FPGA ボードで学ぶ組込みシステム開発入門 ?Altera編?

今回は7-1節のPS/2インターフェース。
手近にPS/2マウスがなかったため、キーボードの入力を読み込むPS/2リーダーとして実装した。
まずはPS/2リーダーモジュールのVerilog、本のコードからはいくらか変更してある。

  • ステートマシンは読み込み専用に簡略化してある。
  • レジスタマップを変更。
    • アドレス0: PS/2リーダーモジュールの状態レジスタ(PS2STATUS、8bit)、[0]PS/2からの読み出しデータフラグ、1でready、0を書き込むとクリアできる、残りのビットはnull
    • アドレス1: PS/2から読み出したデータ(PS2RDARA、8bit)
  • さらにステートの定義も変更。本ではパリティを受信するステートをSETFLGとしているが、ここではそのままGETBITとしている(後でパリティチェックを実装予定)。STOPBIT(ストップビット待ちの状態)で状態レジスタを変更し、同時にPS/2読み出し値をリードバスへセットしている。
  • PS/2クロックの立下り検出を2段のFFにしている。いろいろやってみたが1段(FF一個とPS/2クロックで直接立下り検出をする)では動作が安定しなかった。
module PS2Reader(
	// Avalon Bus
	input wire in_clk,
	input wire in_reset,
	input wire in_address,
	input wire in_write,
	input wire in_read,
	input wire [7:0] in_write_data,
	output wire [7:0] out_read_data,
	// Ports
	inout wire io_PS2CLK,
	inout wire io_PS2DATA);
	//
	// PS2 Reader State Machine
	//
	reg [3:0] ps2_state;
	// state definition
	parameter HALT = 4'h0;
	parameter GETBIT = 4'h1;
	parameter STOPBIT = 4'h2;
	//
	always @(posedge in_clk, posedge in_reset)
	begin
		if (in_reset)
			ps2_state <= HALT;
		else
		begin
			case (ps2_state)
				HALT:
				begin
					// wait for START BIT
					if (f_ps2clk_fall && (io_PS2DATA == 1'b0))
						ps2_state <= GETBIT;					
					else
						ps2_state <= HALT;
				end
				GETBIT:
				begin
					if (f_ps2clk_fall && (counter_bit_position) == 4'h8)
						ps2_state <= STOPBIT;				
					else
						ps2_state <= GETBIT;
				end
				STOPBIT:
				begin
					// wait for STOP BIT
					if (f_ps2clk_fall && (io_PS2DATA == 1'b1))
						ps2_state <= HALT;					
					else
						ps2_state <= STOPBIT;			
				end
				default:
					ps2_state <= HALT;
			endcase
		end
	end
	//
	// PS2 CLK falling edge detection = f_ps2clkfall
	//
	reg [1:0] ff_ps2clk_fall_detection;
	wire f_ps2clk_fall;
	//
	always @(posedge in_clk, posedge in_reset)
	begin
		if (in_reset)
			ff_ps2clk_fall_detection <= 2'b00;
		else
			ff_ps2clk_fall_detection <= {ff_ps2clk_fall_detection[0], io_PS2CLK};
	end
	//
	assign f_ps2clk_fall = (ff_ps2clk_fall_detection == 2'b10)? 1'b1: 1'b0;
	//
	// S-P bit position counter
	//
	reg [3:0] counter_bit_position;
	//
	always @(posedge in_clk, posedge in_reset)
	begin
		if (in_reset)
			counter_bit_position <= 4'h0;
		else if (ps2_state == HALT)
			counter_bit_position <= 4'h0;
		else if ((ps2_state == GETBIT) && f_ps2clk_fall)
			counter_bit_position <= counter_bit_position + 4'h1;
	end
	//
	// PS2DATA read (S-P read)
	//
	reg [9:0] _10bit_SP_buffer;
	//
	always @(posedge in_clk, posedge in_reset)
	begin
		if (in_reset)
			_10bit_SP_buffer <= 10'b00_0000_0000;
		else if ((ps2_state == GETBIT) && f_ps2clk_fall)
			_10bit_SP_buffer <= {io_PS2DATA, _10bit_SP_buffer[9:1]};
	end
	//
	// flag when read data is ready
	//
	reg f_PS2_data_ready;
	//
	always @(posedge in_clk, posedge in_reset)
	begin
		if (in_reset)
			f_PS2_data_ready <= 1'b0;
		else if ((in_write == 1'b1) && (in_address == 1'b0))
			f_PS2_data_ready <= in_write_data[0];
		else if ((ps2_state == STOPBIT) && f_ps2clk_fall)
			f_PS2_data_ready <= 1'b1;
	end
	//
	// set read data
	//
	assign out_read_data = (in_read == 1'b0)? 8'h0:
		(in_address == 1'b0)? {7'h0, f_PS2_data_ready}: _10bit_SP_buffer[8:1];
	//
endmodule

このモジュールをQsysでNios IIに組み込む。
方法はこちらと同様。
今回は

  • Nios II/e
  • On-chip Memory (RAM or ROM) 8192bytes
  • System ID Peripheral
  • JTAG UART
  • (自作の)ps2reader

そして、キーボードの値を7セグLEDに表示するために、

  • PIO(16bit, output)

を組み込んだ。
QsysでNios IIと周辺機器IPを組み込んだ統合モジュールを作る方法は、こちらと同様。
トップモジュールはこんな感じだ。

module nios2ps2reader(	
	input wire in_clk, 
	input wire [9:0] in_switch, 
	input wire [2:0] in_button, 
	output wire [9:0] out_led, 
	output wire [7:0] seven_segment_0, 
	output wire [7:0] seven_segment_1, 
	output wire [7:0] seven_segment_2, 
	output wire [7:0] seven_segment_3,
	inout PS2CLK,
	inout PS2DATA);
	//
	assign reset_n = in_button[0];
	//
	// modules
	//
	nios2ps2reader_qsys nios2ps2reader_qsys(
		.clk_clk(in_clk),                                   //                                clk.clk
		.reset_reset_n(reset_n),                             //                              reset.reset_n
		.ps2reader_0_conduit_end_io_ps2clk_export(PS2CLK),  //  ps2reader_0_conduit_end_io_ps2clk.export
		.ps2reader_0_conduit_end_io_ps2data_export(PS2DATA),  // ps2reader_0_conduit_end_io_ps2data.export
		.pio_0_export({seven_segment_1, seven_segment_0})                               //                              pio_0.export
	);
	//
	//	terminate unused LEDs
	//
	assign out_led[0] = ~PS2CLK;
	assign out_led[1] = ~PS2DATA;
	assign out_led[9:2] = 8'b0000_0000;
	assign seven_segment_2 = 8'b1111_1111;
	assign seven_segment_3 = 8'b1111_1111;
	//
endmodule

PS/2クロック(PS2CLK)はピンP22に、PS/2データ(PS2DATA)はピンP21に接続する。
あとはいつもと同じだ。
最後に、Nios IIのソースコード
Nios IIでps2readerの状態レジスタをpollingして、PS/2からの読み出しデータが準備できたらその値を読み込み、7セグLED用のデータに変換して、PIOに出力する。

#include "system.h"
#include "io.h"
int main()
{ 
	int PS2State, PS2Data;
	unsigned short seven_seg_out;
	unsigned char out_MSB, out_LSB;
	IOWR_16DIRECT(PIO_0_BASE, 0, 0xffff);

  /* Event loop never exits. */
  while (1)
  {
	  PS2State = IORD_8DIRECT(PS2READER_0_BASE, 0);
	  if (PS2State == 1)
	  {
		  PS2Data = IORD_8DIRECT(PS2READER_0_BASE, 1);
		  IOWR_8DIRECT(PS2READER_0_BASE, 0, 0x00);
		  if (PS2Data != 0xf0)
		  {
			  alt_printf("%x ", PS2Data);
			  switch(PS2Data / 16)
			  {
				  case 0x0:
					  out_MSB = 0xc0;
					  break;
				  case 0x1:
					  out_MSB = 0xf9;
					  break;
				  case 0x2:
					  out_MSB = 0xa4;
					  break;
				  case 0x3:
					  out_MSB = 0xb0;
					  break;
				  case 0x4:
					  out_MSB = 0x99;
					  break;
				  case 0x5:
					  out_MSB = 0x92;
					  break;
				  case 0x6:
					  out_MSB = 0x82;
					  break;
				  case 0x7:
					  out_MSB = 0xd8;
					  break;
				  case 0x8:
					  out_MSB = 0x80;
					  break;
				  case 0x9:
					  out_MSB = 0x90;
					  break;
				  case 0xa:
					  out_MSB = 0x88;
					  break;
				  case 0xb:
					  out_MSB = 0x83;
					  break;
				  case 0xc:
					  out_MSB = 0xc6;
					  break;
				  case 0xd:
					  out_MSB = 0xa1;
					  break;
				  case 0xe:
					  out_MSB = 0x86;
					  break;
				  case 0xf:
					  out_MSB = 0x8e;
					  break;
				  default:
					  out_MSB = 0xff;
					  break;
			  }
			  switch(PS2Data % 16)
			  {
				  case 0x0:
					  out_LSB = 0xc0;
					  break;
				  case 0x1:
					  out_LSB = 0xf9;
					  break;
				  case 0x2:
					  out_LSB = 0xa4;
					  break;
				  case 0x3:
					  out_LSB = 0xb0;
					  break;
				  case 0x4:
					  out_LSB = 0x99;
					  break;
				  case 0x5:
					  out_LSB = 0x92;
					  break;
				  case 0x6:
					  out_LSB = 0x82;
					  break;
				  case 0x7:
					  out_LSB = 0xd8;
					  break;
				  case 0x8:
					  out_LSB = 0x80;
					  break;
				  case 0x9:
					  out_LSB = 0x90;
					  break;
				  case 0xa:
					  out_LSB = 0x88;
					  break;
				  case 0xb:
					  out_LSB = 0x83;
					  break;
				  case 0xc:
					  out_LSB = 0xc6;
					  break;
				  case 0xd:
					  out_LSB = 0xa1;
					  break;
				  case 0xe:
					  out_LSB = 0x86;
					  break;
				  case 0xf:
					  out_LSB = 0x8e;
					  break;
				  default:
					  out_LSB = 0xff;
					  break;
			  }
			  seven_seg_out = ((unsigned short) out_MSB) * 256 + ((unsigned short) out_LSB);
			  IOWR_16DIRECT(PIO_0_BASE, 0, seven_seg_out);
		  }
	  }
  }
  return 0;
}

今回大きくハマったのは、PIOへの出力。
Niosコンソールにはキーボードのコードがちゃんと出力されるのに、7セグLEDには出ない。

  • (int)を16bitだと思い込んでいた、16bitは(short)
  • signedであることを忘れていた、seven_seg_out、out_MSB、out_LSBともunsignedにする必要がある

などなど。
型定義を厳密に書かなくてはいけないのは、組み込みの基本だ。
まずいまずい。