实验十一:PS/2模块⑤ — 扩展鼠标
当普通鼠标即三键鼠标再也无法满足需求的时候,扩展鼠标即滚轮鼠标就诞生了,然而实验十一的实验目的就是实现滚轮鼠标的驱动。不过,进入整体之前,先让我们来了解一下鼠标的常用命令。
图11.1 命令F3,设置采样频率。
命令F3也是Set Sample Rate,主要是用来设置采集频率。笔者曾经说过,采集频率就是鼠标采集按键状况还有位置状况的间隔时间,默认下是100次/秒。如图11.1所示,FPGA先发送命令数据8’hF3,事后鼠标会反馈8’hFA以示接收成功,余下FPGA再发送参数数据8’d200,鼠标接收成功后也会反馈 8’hFA。如此一来,鼠标的采集频率从原本的 100次/秒,变成 200次/秒。
图11.2 命令E8,设置分辨率。
命令E8也是 Set Resolution,主要是用来设置分辨率。所谓分辨率就是位置对应寄存器计数的单位,默认下是4计数/mm,亦即 1mm 的距离,鼠标计数4下。如图11.2所示,FPGA先发送命令数据 8’hE8,鼠标接收以后便反馈 8’hFA,FPGA随之也会发送参数数据 8’h01,鼠标接收以后也会反馈数据 8’hFA。完后,鼠标的分辨从原本的 4计数/mm 变成 2计数/mm。
参数数据所对应的分辨率如表11.1所示:
表11.1 参数数据所对应的分辨率。
参数数据 |
分辨率 |
8’h00 |
1计数/mm |
8’h01 |
2计数/mm |
8’h02 |
4计数/mm |
8’h03 |
8计数/mm |
图11.3 命令F6,使用默认参数。
假设笔者手痒,不小心打乱鼠标内部的参数数据,此刻笔者可以发送命令F6,即Set Defaults将参数数据回复成原来的缺省值。如图11.3所示,FPGA先发送命令数据8’hF6
,鼠标完成接收以后便会反馈8’hFA。
图11.4 命令F4使能报告,命令F5关闭报告。
PS/2鼠标不像PS/2键盘,上电并且完成初始化以后它便会陷入发呆状态,如果不发送命令数据8’hF4(即Enable Data Report)手动开启鼠标的水龙头,鼠标是不会发送报告(即夹杂按键状况与位置状况的数据)。如图11.4所示,FPGA先发送命令数据8’hF4,鼠标接收以后便会反馈8’hFA,事后鼠标立即处于就绪状态,一旦按键状况或者位置状况发生改变,鼠标就会发送报告。
假设读者觉得鼠标太唠叨,什么大事小事都报告,笔者可以发送命令数据 8’hF5(即 Disable Data Report)为了使其闭嘴。如图11.4所示,FPGA先发送命令数据 8’hF4,鼠标接收完毕以后便会反馈8’hFA,事后鼠标就成为闭嘴状态,大事小事再也不会烦人。如果读者觉得寂寞,读者可以再度发送命令数据 8’hF4,让鼠标再度唱歌。
图11.5 命令F2,读取鼠标ID。
为了区分鼠标是普通鼠标还是扩展鼠标,期间我们必须使用命令8’hF2,即 Get Device ID。如图11.5所示,FPGA发送命令数据 8’hF2,鼠标接收以后先反馈 8’hFA,再来便发送鼠标ID。如果内容是8’h00,则表示该鼠标只是普通鼠标 ... 反之,如果内容是 8’h03,那么该鼠标就是扩展鼠标。因为如此,我们需要更改一下伪函数,结果如代码11.1所示:
1. 32: // Press low PS2_CLK 100us
2. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
3. else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
4.
5. 33: // release PS2_CLK and set in ,PS2_DAT set out
6. begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
7.
8. 34: // start bit 1
9. begin rDAT <= 1'b0; i <= i + 1'b1; end
10.
11. 35,36,37,38,39,40,41,42,43: // data bit 9
12. if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
13.
14. 44: // stop bit 1
15. if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
16.
17. 45: // Ack bit
18. if( isH2L ) begin i <= i + 1'b1; end
19.
20. 46: // PS2_DAT set in
21. begin isQ2 <= 1'b0; i <= i + 1'b1; end
22.
23. /***********/ // Receive 1st Frame
24.
25. 47,48,49,50,51,52,53,54,55,56,57: // Ingnore
26. if( isH2L ) i <= i + 1'b1;
27.
28. 58: // Check comd F2
29. if( T[7:0] == 8'hF2 ) i <= i + 1'b1;
30. else i <= Go;
31.
32. /***********/ // Receive 2nd Frame
33.
34. 59: // Start bit 1
35. if( isH2L ) i <= i + 1'b1;
36.
37. 60,61,62,63,64,65,66,67,68: // Data bit 9
38. if( isH2L ) begin T[i-60] <= PS2_DAT; i <= i + 1'b1; end
39.
40. 69: // Stop bit 1
41. if( isH2L ) i <= Go;
代码11.1
如代码11.1所示,步骤32~57则是发送一帧数据又忽略一帧反馈,基本上与实验十一模一样。至于第58行则是用来判断,FPGA所发送的命令是否是 8’hF2即 Get Device ID
?如果是,步骤则继续读取操作,因为命令8’hF2令鼠标反馈8’hFA之余,还会导致鼠标会发送一帧ID数据。否则的话,即表示其他命令,步骤返回。步骤59~69是用来读取下一帧ID数据,期间步骤60~68用来读取 8位数据位,还有1位校验位。完后,步骤便返回。
小时候的笔者很爱假扮刺客,笔者与近邻的小孩就总是瞎着玩,其它小朋友则扮演秘密商人。刺客为了与秘密商人进行交易,两者之间必须经过暗语核对,例如:
“阳光的男孩赤裸裸 ... ”,对方问道。
“对面的女来看过来 ... ”,笔者答道。
滚轮鼠标也是扩展鼠标,上电以后也不会立即变成扩展鼠标,如果扩展鼠标不经过核对暗语,扩展鼠标也是一只普通的3键鼠标而已 ... 反之,如果完成暗语核对,扩展鼠标才会发挥滚轮功能。
图11.6 设置扩展鼠标的暗语。
如图11.6所示,那是设置扩展鼠标的暗语:
发送命令数据 8’hF3,接收反馈8’hFA,再发送参数数据 8’hC8,在接收反馈8’hFA;
发送命令数据 8’hF3,接收反馈8’hFA,再发送参数数据 8’h64,在接收反馈8’hFA;
发送命令数据 8’hF3,接收反馈8’hFA,再发送参数数据 8’h50,在接收反馈8’hFA;
发送命令数据 8’hF2,接收反馈8’hFA,再接收鼠标ID8’h03。
完后,鼠标便成为扩展鼠标,内部也自动初始化,然后进入默认模式。
图11.7 扩展鼠标标示的位置。
普通鼠标相较扩展鼠标,它多了滚轮功能,即鼠标除了标示左键,中键,右键,X还有Y以外,扩展还会标示Z。如图11.7所示,X与Y可以看成面积,至于Z则可以看成上下。当鼠标向西移动,X呈现正直,反之负值;当鼠标向北移动,Y呈现正直,反之负值;当滚动向下活动,Z呈现正直,反之负值。
图11.8 扩展鼠标的报告长度。
为此,扩展鼠标相较普通鼠标,报告长度则多了一个字节。如图11.8所示,当鼠标察觉变化以后,鼠标便会发送4个字节长度的报告,然而字节之间的位分配如表11.1所示:
表11.1 Device ID 为 8’h03 的报告内容。
字节/位 |
[7] |
[6] |
[5] |
[4] |
[3] |
[2] |
[1] |
[0] |
字节一 |
Y溢出位 |
X溢出位 |
Y[8]符号位 |
X[8]符号位 |
保留 |
中键 |
右键 |
左键 |
字节二 |
X[7:0] |
|||||||
字节三 |
Y[7:0] |
|||||||
字节四 |
保留 |
保留 |
保留 |
保留 |
Z[3]符号位 |
Z[2] |
Z[1] |
Z[0] |
笔者需要补充一下 ... 由于早期Intel 称王,所以扩展鼠标标准都是Intel说话算话,Device ID 为 8’h03 就是其中一种扩展标准。如表11.1所示,字节一至字节三基本上变化不大,反之字节四则稍微不同。字节四的[2..0]位是 Z[2:0],字节四的[3]是Z[3],也是Z的符号位。换句话说,寄存器Z有4位,内容用补码表示。
图11.9 扩展鼠标的位置范围。
图11.9表示扩展鼠标的位置范围,X与Y与普通鼠标一样,Z比较畸形一点,因为Z向上不是正直而是负值,反之亦然。Z的有效范围是 4’b1001~4’b0111或者 -7~7,也就是说滚轮向下活动,寄存器Z就递增,向上滚动,寄存器Z就递减。
上述内容理解完毕以后,我们便可以开始建模了:
图11.10 实验十一的建模图。
如图11.10所示,组合模块 ps2_demo 包含的内容与实验十相差不了多少,不过却少了正直化的即时操作。期间,PS/2初始化功能模块的 oEn 有两位,oEn[1] 拉高表示鼠标为扩展鼠标,oEn[0] 拉高表示鼠标为普通鼠标。PS/2读取功能模块的 oData[2:0] 直接驱动LED资源, oData[27:4]则驱动数码管基础模块的 iData。
ps2_init_funcmod.v
图11.11 PS/2初始化功能模块的建模图。
如图11.11所示,PS/2初始化功能模块有两位oEn,[1]拉高表示鼠标为扩展鼠标,[0]拉高则表示鼠标为普通鼠标。
1. module ps2_init_funcmod
2. (
3. input CLOCK, RESET,
4. inout PS2_CLK,
5. inout PS2_DAT,
6. output [1:0]oEn
7. );
8. parameter T100US = 13'd5000;
9. parameter FF_Write = 7'd32;
以上内容为相关的出入端声明。第8行是100us的常量声明,第9行则是伪函数的入口地址。
11. /*******************************/ // sub1
12.
13. reg F2,F1;
14.
15. always @ ( posedge CLOCK or negedge RESET )
16. if( !RESET )
17. { F2,F1 } <= 2'b11;
18. else
19. { F2, F1 } <= { F1, PS2_CLK };
20.
21. /*******************************/ // core
22.
23. wire isH2L = ( F2 == 1'b1 && F1 == 1'b0 );
以上内容是用来检测电平变化的周边操作,第23行则是下降沿的即时声明。
24. reg [8:0]T;
25. reg [6:0]i,Go;
26. reg [12:0]C1;
27. reg rCLK,rDAT;
28. reg isQ1,isQ2,isEx;
29. reg [1:0]isEn;
30.
31. always @ ( posedge CLOCK or negedge RESET )
32. if( !RESET )
33. begin
34. T <= 9'd0;
35. C1 <= 13'd0;
36. { i,Go } <= { 7'd0,7'd0 };
37. { rCLK,rDAT } <= 2'b11;
38. { isQ1,isQ2,isEx } <= 3'b000;
39. isEn <= 2'b00;
40. end
41. else
以上内容是相关的寄存器声明,第33~39行则是这群寄存器的复位操作。其中isEn有两位,isEx为扩展鼠标的立旗。
42. case( i )
43.
44. /***********/ // INIT Mouse
45.
46. 0: // Send F3 1111_0011
47. begin T <= { 1'b1, 8'hF3 }; i <= FF_Write; Go <= i + 1'b1; end
48.
49. 1: // Send C8 1100_1000
50. begin T <= { 1'b0, 8'hC8 }; i <= FF_Write; Go <= i + 1'b1; end
51.
52. 2: // Send F3 1111_0011
53. begin T <= { 1'b1, 8'hF3 }; i <= FF_Write; Go <= i + 1'b1; end
54.
55. 3: // Send 64 0110_1000
56. begin T <= { 1'b0, 8'h64 }; i <= FF_Write; Go <= i + 1'b1; end
57.
58. 4: // Send F3 1111_0011
59. begin T <= { 1'b1, 8'hF3 }; i <= FF_Write; Go <= i + 1'b1; end
60.
61. 5: // Send 50 0101_0000
62. begin T <= { 1'b1, 8'h50 }; i <= FF_Write; Go <= i + 1'b1; end
63.
64. 6: // Send F2 1111_0010
65. begin T <= { 1'b0, 8'hF2 }; i <= FF_Write; Go <= i + 1'b1; end
66.
67. 7: // Check Mouse ID 00(normal), 03(extend)
68. if( T[7:0] == 8'h03 ) begin isEx <= 1'b1; i <= i + 1'b1; end
69. else if( T[7:0] == 8'h00 ) begin isEx <= 1'b0; i <= i + 1'b1; end
70.
71. 8: // Send F4 1111_0100
72. begin T <= { 1'b0, 8'hF4 }; i <= FF_Write; Go <= i + 1'b1; end
73.
74. 9:
75. if( isEx ) isEn[1] <= 1'b1;
76. else if( !isEx ) isEn[0] <= 1'b1;
77.
以上内容是核心操作。步骤0~9是主操作,步骤0~6则是发送用来开启扩展鼠标的暗语,步骤7用来判断鼠标返回的 Device ID 是否为 8’h03,如果是 isEx 立旗,否则 isEx 消除立旗。步骤8用来使能鼠标。步骤9根据 isEx 的状态再来决定 isEn的结果, 如果isEx为1 isEn[1] 便拉高,否则 isEx 拉高,完后步骤停留。
78. /****************/ // PS2 Write Function
79.
80. 32: // Press low PS2_CLK 100us
81. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
82. else begin isQ1 = 1'b1; rCLK <= 1'b0; C1 <= C1 + 1'b1; end
83.
84. 33: // release PS2_CLK and set in ,PS2_DAT set out
85. begin isQ1 <= 1'b0; rCLK <= 1'b1; isQ2 <= 1'b1; i <= i + 1'b1; end
86.
87. 34: // start bit 1
88. begin rDAT <= 1'b0; i <= i + 1'b1; end
89.
90. 35,36,37,38,39,40,41,42,43: // data bit 9
91. if( isH2L ) begin rDAT <= T[ i-35 ]; i <= i + 1'b1; end
92.
93. 44: // stop bit 1
94. if( isH2L ) begin rDAT <= 1'b1; i <= i + 1'b1; end
95.
96. 45: // Ack bit
97. if( isH2L ) begin i <= i + 1'b1; end
98.
99. 46: // PS2_DAT set in
100. begin isQ2 <= 1'b0; i <= i + 1'b1; end
101.
102. /***********/ // Receive 1st Frame
103.
104. 47,48,49,50,51,52,53,54,55,56,57: // Ingnore
105. if( isH2L ) i <= i + 1'b1;
106.
107. 58: // Check comd F2
108. if( T[7:0] == 8'hF2 ) i <= i + 1'b1;
109. else i <= Go;
110.
以上内容是部分核心操作。步骤32~58是部分伪函数,内容则是发送一帧数据,再读取一帧反馈,完后便进入步骤58判断,发送的命令是否为 8’hF2,如果是便继续步骤,否则便返回步骤。
111. /***********/ // Receive 2nd Frame
112.
113. 59: // Start bit 1
114. if( isH2L ) i <= i + 1'b1;
115.
116. 60,61,62,63,64,65,66,67,68: // Data bit 9
117. if( isH2L ) begin T[i-60] <= PS2_DAT; i <= i + 1'b1; end
118.
119. 69: // Stop bit 1
120. if( isH2L ) i <= Go;
121.
122. endcase
123.
以上内容是部分核心操作。步骤59~69也是部分伪函数,主要用来读取下一帧数据的字节内容,在此是针对命令8’hF2,也就是Device ID。读完一帧数据以后便返回步骤。
124. assign PS2_CLK = isQ1 ? rCLK : 1'bz;
125. assign PS2_DAT = isQ2 ? rDAT : 1'bz;
126. assign oEn = isEn;
127.
128. endmodule
以上内容为驱动输出声明。
ps2_read_funcmod.v
图11.12 PS/2读功能模块的建模图。
实验十一的PS/2读功能模块与实验十相比,左边的 iEn出入多出一位以外,右边的oData也多出一个字节。
1. module ps2_read_funcmod
2. (
3. input CLOCK, RESET,
4. input PS2_CLK,PS2_DAT,
5. input [1:0]iEn,
6. output oTrig,
7. output [31:0]oData
8. );
9. parameter FF_Read = 7'd32;
以上内容是相关的出入端声明。第9行是伪函数的入口。
10.
11. /*******************************/ // sub1
12.
13. reg F2,F1;
14.
15. always @ ( posedge CLOCK or negedge RESET )
16. if( !RESET )
17. { F2,F1 } <= 2'b11;
18. else
19. { F2, F1 } <= { F1, PS2_CLK };
20.
21. /*******************************/ // core
22.
23. wire isH2L = ( F2 == 1'b1 && F1 == 1'b0 );
以上内容是检测电平变化的周边操作,第23行则是下降沿的即时声明。
24. reg [31:0]D1;
25. reg [7:0]T;
26. reg [6:0]i,Go;
27. reg isDone;
28.
29. always @ ( posedge CLOCK or negedge RESET )
30. if( !RESET )
31. begin
32. D1 <= 32'd0;
33. T <= 8'd0;
34. { i,Go } <= { 7'd0,7'd0 };
35. isDone <= 1'b0;
36. end
以上内容为相关的寄存器声明以及复位操作。
37. else if( iEn[1] )
38. case( i )
39.
40. /***********/ // Extend Mouse Read Data
41.
42. 0: // Read Data 1st byte
43. begin i <= FF_Read; Go <= i + 1'b1; end
44.
45. 1: // Store Data 1st byte
46. begin D1[7:0] <= T; i <= i + 1'b1; end
47.
48. 2: // Read Data 2nd byte
49. begin i <= FF_Read; Go <= i + 1'b1; end
50.
51. 3: // Store Data 2nd byte
52. begin D1[15:8] <= T; i <= i + 1'b1; end
53.
54. 4: // Read Data 3rd byte
55. begin i <= FF_Read; Go <= i + 1'b1; end
56.
57. 5: // Store Data 3rd byte
58. begin D1[23:16] <= T; i <= i + 1'b1; end
59.
60. 6: // Read Data 4rd byte
61. begin i <= FF_Read; Go <= i + 1'b1; end
62.
63. 7: // Store Data 4rd byte
64. begin D1[31:24] <= T; i <= i + 1'b1; end
65.
66. 8:
67. begin isDone <= 1'b1; i <= i + 1'b1; end
68.
69. 9:
70. begin isDone <= 1'b0; i <= 7'd0; end
以上内容为部分核心操作。第37行的 if( iEn[1] ) 表示下面所有内容都是扩展鼠标的核心操作。步骤0~7则是读取4个字节的数据,步骤8~9用来产生完成信号以示一次性的报告已经接收完毕。
71.
72. /****************/ // PS2 Write Function
73.
74. 32: // Start bit
75. if( isH2L ) i <= i + 1'b1;
76.
77. 33,34,35,36,37,38,39,40: // Data byte
78. if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1; end
79.
80. 41: // Parity bit
81. if( isH2L ) i <= i + 1'b1;
82.
83. 42: // Stop bit
84. if( isH2L ) i <= Go;
85.
86. endcase
以上内容为部分核心操作。步骤32~42是读取一帧数据的伪函数。
87. else if( iEn[0] )
88. case( i )
89.
90. /***********/ // Normal Mouse Read Data
91.
92. 0: // Read Data 1st byte
93. begin i <= FF_Read; Go <= i + 1'b1; end
94.
95. 1: // Store Data 1st byte
96. begin D1[7:0] <= T; i <= i + 1'b1; end
97.
98. 2: // Read Data 2nd byte
99. begin i <= FF_Read; Go <= i + 1'b1; end
100.
101. 3: // Store Data 2nd byte
102. begin D1[15:8] <= T; i <= i + 1'b1; end
103.
104. 4: // Read Data 3rd byte
105. begin i <= FF_Read; Go <= i + 1'b1; end
106.
107. 5: // Store Data 3rd byte
108. begin D1[23:16] <= T; i <= i + 1'b1; end
109.
110. 6:
111. begin isDone <= 1'b1; i <= i + 1'b1; end
112.
113. 7:
114. begin isDone <= 1'b0; i <= 7'd0; end
115.
以上内容为部分核心操作。第87行的 if( iEn[0] ) 表示下面的内容均为普通鼠标的核心操作。步骤0~5用来读取3个字节的内容,步骤6~7则用来产生完成信号以示一次性的报告已经读取完毕。
116. /****************/ // PS2 Write Function
117.
118. 32: // Start bit
119. if( isH2L ) i <= i + 1'b1;
120.
121. 33,34,35,36,37,38,39,40: // Data byte
122. if( isH2L ) begin T[i-33] <= PS2_DAT; i <= i + 1'b1; end
123.
124. 41: // Parity bit
125. if( isH2L ) i <= i + 1'b1;
126.
127. 42: // Stop bit
128. if( isH2L ) i <= Go;
129.
130. endcase
131.
以上内容为部分核心操作。步骤32~42是读取一帧数据的伪函数。
132. assign oTrig = isDone;
133. assign oData = D1;
134.
135. endmodule
以上内容是输出驱动声明。
ps2_demo.v
笔者就不重复粘贴实验十一的建模图了,具体内容我们还是来看代码吧。
1. module ps2_demo
2. (
3. input CLOCK, RESET,
4. inout PS2_CLK, PS2_DAT,
5. output [7:0]DIG,
6. output [5:0]SEL,
7. output [2:0]LED
8. );
9. wire [1:0]EnU1;
10.
11. ps2_init_funcmod U1
12. (
13. .CLOCK( CLOCK ),
14. .RESET( RESET ),
15. .PS2_CLK( PS2_CLK ), // < top
16. .PS2_DAT( PS2_DAT ), // < top
17. .oEn( EnU1 ) // > U2
18. );
19.
20. wire [31:0]DataU2;
21.
22. ps2_read_funcmod U2
23. (
24. .CLOCK( CLOCK ),
25. .RESET( RESET ),
26. .PS2_CLK( PS2_CLK ), // < top
27. .PS2_DAT( PS2_DAT ), // < top
28. .iEn( EnU1 ), // < U1
29. .oTrig(),
30. .oData( DataU2 ) // > U3
31. );
32.
33. smg_basemod U3
34. (
35. .CLOCK( CLOCK ),
36. .RESET( RESET ),
37. .DIG( DIG ), // > top
38. .SEL( SEL ), // > top
39. .iData( { 2'd0,DataU2[5],DataU2[4],DataU2[27:24],DataU2[23:16],DataU2[15:8] }) // < U2
40. );
41.
42. assign LED = {DataU2[1], DataU2[2], DataU2[0]};
43.
44. endmodule
上诉内容的连线部署基本上与图11.10差不了多少,期间第39行的 2’d0,DataU2[5],DataU2[4] 表示数码管的第一位显示 X 与 Y的符号位;DataU2[27:24] 表示数码管的第二位显示 Z的内容;DataU2[23:16] 表示数码管的第三至第四位显示 Y 的内容;DataU2[15:8] 表示数码管的第五至第六位显示 X 的内容。第42行则表示 LED[2]显示右键,LED[1]显示中键,LED[0]显示左键。
编译完毕并且下载程序。当鼠标向西南方移动的时候,第一位数码管便会显示 4’h3,即 4’b0011,也就是说 X 与 Y 的符号位都是拉高状态(负值)。当滚轮向上滚动的时候,第二位数码管便会显示 4’hF,即4’b1111,也就是Z为负值 -1(只要滚动速度够快,负值还能更小)。至于数码管第3~4显示Y的内容(补码形式),数码管5~6则显示X的内容(补码形式)。
细节一: 两个人,两把汤匙
1. else if( iEn[1] )
2. case( i )
3. 扩展鼠标的核心操作;
4. 伪函数;
5. endcase
6. else if(isEn[0])
7. case(i)
8. 普通鼠标的核心操作;
9. 伪函数;
10. endcase
代码11.2
PS/2 读取功能模块有一个有趣的现象,即资源多义性的问题。如代码11.2所示,PS/2读取功能模块用 if( iEn[1] ) 与 if( iEn[0] ) 表示该模块针对两种鼠标的读取操作。这种感觉好比一对兄弟在吃饭 ... 正常情况下,当然是一个人一把汤匙才对,这种比喻完全对应代码11.2的内容。
PS/2读取功能模块负责两种鼠标的读取操作之际,里边好比有一对兄弟,一个人负责扩展鼠标的读取操作,另一个人则针对普通鼠标的读取操作。期间,伪函数就是某种操作资源,也可以看成是汤匙。为了不让两位兄弟争用一把汤匙而吵架,身为设计者的我们,应该为每个人分配一把汤匙。
对此,我们必须多花一些钱买另一把汤匙,这样做我们可能多消耗一些逻辑资源。不过,家和为贵,为使模块可以和谐共处以致提高表达能力,要笔者多消耗一些逻辑资源,笔者也觉得值得。
细节二:完整的个体模块
图11.13 PS/2鼠标基础模块的建模图。
图11.13是PS/2鼠标基础模块的建模图。
ps2mouse_basemod.v
1. module ps2mouse_basemod
2. (
3. input CLOCK, RESET,
4. inout PS2_CLK, PS2_DAT,
5. output oTrig,
6. output [31:0]oData
7. );
8. wire [1:0]EnU1;
9.
10. ps2_init_funcmod U1
11. (
12. .CLOCK( CLOCK ),
13. .RESET( RESET ),
14. .PS2_CLK( PS2_CLK ), // < top
15. .PS2_DAT( PS2_DAT ), // < top
16. .oEn( EnU1 ) // > U2
17. );
18.
19. ps2_read_funcmod U2
20. (
21. .CLOCK( CLOCK ),
22. .RESET( RESET ),
23. .PS2_CLK( PS2_CLK ), // < top
24. .PS2_DAT( PS2_DAT ), // < top
25. .iEn( EnU1 ), // < U1
26. .oTrig( oTrig ), // > top
27. .oData( oData ) // > top
28. );
29.
30. endmodule