赞
踩
–作者:小黑同学
本文为明德扬原创及录用文章,转载请注明出处!
计算器是近代人发明的可以进行数字运算的机器。现代的电子计算器能进行数学运算的手持电子机器,拥有集成电路芯片,但结构比电脑简单得多,可以说是第一代的电子计算机,且功能也较弱,但较为方便与廉价,可广泛运用于商业交易中,是必备的办公用品之一。除显示计算结果外,还常有溢出指示、错误指示等。计算器电源采用交流转换器或电池。为了节省电能,计算器都采用CMOS工艺制作的大规模集成电路。
计算器一般由运算器、控制器、存储器、键盘、显示器、电源和一些可选外围设备及电子配件,通过人工或机器设备组成,抵挡计算器的运算器、控制器由数字逻辑电路实现简单的串行运算
计算器是最早的计算工具,例如:古代印加人利用许多颜色的绳结来计数或者记录历史,还有古希腊人的安提凯希拉装置,中国的算盘等。中国古代最早采用的一种计算工具叫筹策,又被叫做算筹。
简易计算器支持简单的四则运算(支持负数),在此基础上,添加了连续运算功能。计算器面板如下:
1、计算器通过矩阵键盘模拟按键输入,并通过数码管显示。
2、计算器有“0、1、2、3、4、5、6、7、8、9、+、-、*、/、C、=”共16个按键。
3、计算器不支持输入负数,运算结果支持负数但不支持小数。
4、运算数1、运算数2以及运算结果最大支持8位。其中,运算数1和运算结果的位数包括符号位“-”。
5、运算数1和运算数2的默认值为0.
6、计算器支持连续运算,允许在输入运算数2后按下运算符,或者得出运算结果后按下运算符。
7、当运算结果溢出时,数码管显示8个F。
8、当操作数1或者操作数2的长度溢出时,蜂鸣器会响。
系统结构框图如下所示:
图一
键盘扫描模块实现功能
1、将外来异步信号打两拍处理,将异步信号同步化。
2、实现20ms按键消抖功能。
3、实现矩阵键盘的按键检测功能,并输出有效按键信号。
工作状态选择模块实现功能
1、根据接收的不同的按键信号,判断和决定计算器的工作状态。共有5种状态,输入运算数1(OP_1)、运算符(OPER)、输入运算数2(OP_2)、输出结果(RESULT)、结果错误(ERROR)
运算数1模块实现功能
1、当计算器处于运算数1状态下,任何连续输入的数字(不超过8位)都将存放在该模块中,作为运算数1.
2、当运算数已经到达8位时,此时无论输入任何数字,运算数1不变。
3、当计算器经过一次运算后(按下等号或者在运算数2状态下按下运算符),运算数去存放结果result。
运算符模块实现功能
1、保存最新按下的运算符。
运算数2模块实现功能
1、当计算器处于运算数2状态下,任何连续输入的数字(不超过8位)都将存放在该模块中,作为运算数2,默认值为0。
2、当运算数2已经到达8(包括符号位“-”),此时无论输入任何数字,运算数2不变。
运算单元模块实现功能
1、当计算器处于运算数2状态下按下运算符或者在任何状态下按下等号时,该模块根据此时运算数1、运算数2以及运算符的值,进行运算。
2、若运算结果溢出,或者长度大于8位(包括符号位“-”)或者除数为0时,输出8个F。
3、最多保留运算结果整数部分的8个有效数字,不保留任何小数。
显示对象选则模块实现功能
1、该模块的作用是根据当前计算器的工作状态来选择数码管的显示内容。
数码管显示模块实现功能
1、该模块的作用是对显示对象选择模块的显示数据输出信号进行数码管显示。
蜂鸣器模块实现功能
1、该模块的作用是对各种错误输入或输出进行响铃警告。
信号名 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟,50Mhz |
rst_n | 输入 | 低电平复位信号 |
Key_col | 输入 | 4位矩阵键盘列信号,默认高电平,开发板按键为普通按键时,不需要该信号 |
Key_row | 输出 | 4位矩阵键盘行信号,默认低电平,开发板按键为普通按键时,不需要该信号 |
Segment | 输出 | 8位数码管段选信号 |
Seg_sel | 输出 | 8位数码管位选信号 |
beep | 输出 | 1位蜂鸣器控制信号 |
1.module calc_project( 2. clk , 3. rst_n , 4. key_col , 5. key_row , 6. seg_sel , 7. segment , 8. beep 9. ); 10. 11. parameter KEY_WID = 4 ; 12. parameter STATE_WID = 5 ; 13. parameter NUM_WID = 27 ; 14. parameter SEG_NUM = 8 ; 15. parameter SEG_WID = 8 ; 16. 17. input clk ; 18. input rst_n ; 19. input [KEY_WID-1:0] key_col ; 20. output [KEY_WID-1:0] key_row ; 21. output [SEG_NUM-1:0] seg_sel ; 22. output [SEG_WID-1:0] segment ; 23. output beep ; 24. 25. wire [KEY_WID-1:0] key_num ; 26. wire key_vld ; 27. wire [KEY_WID-1:0] key_num_out ; 28. wire [KEY_WID-1:0] key_vld_out ; 29. wire [STATE_WID-1:0] state_c ; 30. wire [NUM_WID-1:0] op_1 ; 31. wire op_1_err ; 32. wire [KEY_WID-1:0] oper ; 33. wire [NUM_WID-1:0] op_2 ; 34. wire op_2_err ; 35. wire [NUM_WID-1:0] result ; 36. wire result_err ; 37. wire result_neg ; 38. wire [SEG_NUM*4-1:0] display ; 39. wire display_vld ; 40. 41. key_scan key_scan_prj( 42. .clk (clk ) , 43. .rst_n (rst_n ) , 44. .key_col (key_col) , 45. .key_row (key_row) , 46. .key_out (key_num) , 47. .key_vld (key_vld) 48. ); 49. 50. work_state work_state_prj( 51. .clk (clk ) , 52. .rst_n (rst_n ) , 53. .key_num (key_num ) , 54. .key_vld (key_vld ) , 55. .result_err (result_err ) , 56. .key_num_out(key_num_out) , 57. .key_vld_out(key_vld_out) , 58. .state_c (state_c ) 59. ); 60. 61. op_1 op_1_prj( 62. .clk (clk ) , 63. .rst_n (rst_n ) , 64. .key_num_out(key_num_out) , 65. .key_vld_out(key_vld_out) , 66. .state_c (state_c ) , 67. .result (result ) , 68. .op_1 (op_1 ) , 69. .op_1_err (op_1_err ) 70. ); 71. 72. oper oper_prj( 73. .clk (clk ) , 74. .rst_n (rst_n ) , 75. .key_num_out(key_num_out) , 76. .key_vld_out(key_vld_out) , 77. .state_c (state_c ) , 78. .oper (oper ) 79. ); 80. 81. op_2 op_2_prj( 82. .clk (clk ) , 83. .rst_n (rst_n ) , 84. .key_num_out(key_num_out) , 85. .key_vld_out(key_vld_out) , 86. .state_c (state_c ) , 87. .op_1 (op_1 ) , 88. .op_2 (op_2 ) , 89. .op_2_err (op_2_err ) 90. ); 91. 92. result result_prj( 93. .clk (clk ) , 94. .rst_n (rst_n ) , 95. .key_num_out(key_num_out) , 96. .key_vld_out(key_vld_out) , 97. .state_c (state_c ) , 98. .op_1 (op_1 ) , 99. .oper (oper ) , 100. .op_2 (op_2 ) , 101. .result (result ) , 102. .result_err (result_err ) , 103. .result_neg (result_neg ) 104. ); 105. 106. display_sel display_sel_prj( 107. .clk (clk ) , 108. .rst_n (rst_n ) , 109. .state_c (state_c ) , 110. .op_1 (op_1 ) , 111. .op_2 (op_2 ) , 112. .result (result ) , 113. .result_neg (result_neg ) , 114. .display (display ) , 115. .display_vld(display_vld) 116. ); 117. 118. segment segment_prj( 119. .rst_n (rst_n ) , 120. .clk (clk ) , 121. .display (display ) , 122. .display_vld(display_vld) , 123. .seg_sel (seg_sel ) , 124. .segment (segment ) 125. ); 126. 127. beep beep_prj( 128. .clk (clk ) , 129. .rst_n (rst_n ) , 130. .op_1_err (op_1_err ) , 131. .op_2_err (op_2_err ) , 132. .result_err (result_err ) , 133. .beep (beep ) 134. ); 135. 136.endmodule
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
key_col | 输入 | 矩阵键盘列输入信号 |
Key_row | 输出 | 矩阵键盘行输出信号 |
Key_out | 输出 | 按键位置输出信号,key_vld有效时,该信号有效。 |
Key_vld | 输出 | 按键有效指示信号,高电平有效 |
在前面的案例中已经有矩阵键盘的介绍,所以这里不在过多介绍,详细介绍请看下方链接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=310
1.always @(posedge clk or negedge rst_n)begin 2. if(rst_n==1'b0)begin 3. key_col_ff0 <= 4'b1111; 4. key_col_ff1 <= 4'b1111; 5. end 6. else begin 7. key_col_ff0 <= key_col ; 8. key_col_ff1 <= key_col_ff0; 9. end 10.end 11. 12. 13.always @(posedge clk or negedge rst_n) begin 14. if (rst_n==0) begin 15. shake_cnt <= 0; 16. end 17. else if(add_shake_cnt) begin 18. if(end_shake_cnt) 19. shake_cnt <= 0; 20. else 21. shake_cnt <= shake_cnt+1 ; 22. end 23.end 24.assign add_shake_cnt = key_col_ff1!=4'hf; 25.assign end_shake_cnt = add_shake_cnt && shake_cnt == TIME_20MS-1 ; 26. 27. 28.always @(posedge clk or negedge rst_n)begin 29. if(rst_n==1'b0)begin 30. state_c <= CHK_COL; 31. end 32. else begin 33. state_c <= state_n; 34. end 35.end 36. 37.always @(*)begin 38. case(state_c) 39. CHK_COL: begin 40. if(col2row_start )begin 41. state_n = CHK_ROW; 42. end 43. else begin 44. state_n = CHK_COL; 45. end 46. end 47. CHK_ROW: begin 48. if(row2del_start)begin 49. state_n = DELAY; 50. end 51. else begin 52. state_n = CHK_ROW; 53. end 54. end 55. DELAY : begin 56. if(del2wait_start)begin 57. state_n = WAIT_END; 58. end 59. else begin 60. state_n = DELAY; 61. end 62. end 63. WAIT_END: begin 64. if(wait2col_start)begin 65. state_n = CHK_COL; 66. end 67. else begin 68. state_n = WAIT_END; 69. end 70. end 71. default: state_n = CHK_COL; 72. endcase 73.end 74.assign col2row_start = state_c==CHK_COL && end_shake_cnt; 75.assign row2del_start = state_c==CHK_ROW && row_index==3 && end_row_cnt; 76.assign del2wait_start= state_c==DELAY && end_row_cnt; 77.assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf; 78. 79.always @(posedge clk or negedge rst_n)begin 80. if(rst_n==1'b0)begin 81. key_row <= 4'b0; 82. end 83. else if(state_c==CHK_ROW)begin 84. key_row <= ~(1'b1 << row_index); 85. end 86. else begin 87. key_row <= 4'b0; 88. end 89.end 90. 91. 92.always @(posedge clk or negedge rst_n) begin 93. if (rst_n==0) begin 94. row_index <= 0; 95. end 96. else if(add_row_index) begin 97. if(end_row_index) 98. row_index <= 0; 99. else 100. row_index <= row_index+1 ; 101. end 102. else if(state_c!=CHK_ROW)begin 103. row_index <= 0; 104. end 105.end 106.assign add_row_index = state_c==CHK_ROW && end_row_cnt; 107.assign end_row_index = add_row_index && row_index == 4-1 ; 108. 109. 110.always @(posedge clk or negedge rst_n) begin 111. if (rst_n==0) begin 112. row_cnt <= 0; 113. end 114. else if(add_row_cnt) begin 115. if(end_row_cnt) 116. row_cnt <= 0; 117. else 118. row_cnt <= row_cnt+1 ; 119. end 120.end 121.assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY; 122.assign end_row_cnt = add_row_cnt && row_cnt == 16-1 ; 123. 124. 125.always @(posedge clk or negedge rst_n)begin 126. if(rst_n==1'b0)begin 127. key_col_get <= 0; 128. end 129. else if(state_c==CHK_COL && end_shake_cnt ) begin 130. if(key_col_ff1==4'b1110) 131. key_col_get <= 0; 132. else if(key_col_ff1==4'b1101) 133. key_col_get <= 1; 134. else if(key_col_ff1==4'b1011) 135. key_col_get <= 2; 136. else 137. key_col_get <= 3; 138. end 139.end 140. 141. 142.always @(posedge clk or negedge rst_n)begin 143. if(rst_n==1'b0)begin 144. key_out <= 0; 145. end 146. else if(state_c==CHK_ROW && end_row_cnt)begin 147. key_out <= {row_index,key_col_get}; 148. end 149. else begin 150. key_out <= 0; 151. end 152.end 153. 154.always @(posedge clk or negedge rst_n)begin 155. if(rst_n==1'b0)begin 156. key_vld <= 1'b0; 157. end 158. else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin 159. key_vld <= 1'b1; 160. end 161. else begin 162. key_vld <= 1'b0; 163. end 164.end
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
key_vld | 输入 | 按键按下指示信号 |
Key_num | 输入 | 按键位置输入信号,key_vld有效时,该信号有效 |
Result_err | 输入 | 运算结果错误指示信号 |
Key_num_out | 输出 | 计算器按下位置输出信号,key_vld_out有效时,该信号有效。 |
Key_vld_out | 输出 | 计算器按键按下有效指示信号,高电平有效。 |
State_c | 输出 | 计算器工作状态指示信号 |
该模块的主要功能是根据按下的按键进行不同来判断和决定计算器的工作状态。一条等式可以写成:运算数1+操作符+运算数2+等号+结果的形式。考虑到结果错误的情况,我将本模块的状态划分为5个,分别是:输入运算数1(OP_1)、运算符(OPER)、输入运算数2(OP_2)、输出结果(RESULT)、结果错误(ERROR)。
下图为本模块的状态跳转图:
复位后,状态机进入OP_1状态,即初始状态为OP_1;
在OP_1状态下:
A.按下等号,跳到RESULT状态;
B.按下运算符,跳到OPER状态;
在OPER状态下:
A.按下数字,跳到OP_2状态;
B.按下等号,跳到RESULT状态;
在OP_2状态下:
A.按下等号,跳到RESULT状态;
B.按下运算符,跳到OPER状态;
在RESULT状态下:
A.按下数字,跳到OP_1状态;
B.按下运算符,跳到OPER状态;
C.按下等号,停留在RESULT状态;
在ERROR状态下:
A.按下数字,跳到OP_1状态;
B.按下其他按键,停留在ERROR状态;
无论当前处于什么状态,只要检测到运算结果错误指示信号有效,即刻跳转到ERROR状态。
使用GVIM,在命令模式下输入如下内容,即可生成本模块所需要的状态机代码。
使用明德扬的状态机模板,可以很快速的写出此模块代码。
165.always @(*)begin 166. case(key_num) 167. 4'd0 :key_num_chg = 4'd7 ; 168. 4'd1 :key_num_chg = 4'd8 ; 169. 4'd2 :key_num_chg = 4'd9 ; 170. 4'd3 :key_num_chg = 4'd10 ; 171. 4'd7 :key_num_chg = 4'd11 ; 172. 4'd8 :key_num_chg = 4'd1 ; 173. 4'd9 :key_num_chg = 4'd2 ; 174. 4'd10 :key_num_chg = 4'd3 ; 175. 4'd11 :key_num_chg = 4'd14 ; 176. 4'd12 :key_num_chg = 4'd0 ; 177. 4'd13 :key_num_chg = 4'd12 ; 178. 4'd14 :key_num_chg = 4'd13 ; 179. default:key_num_chg = key_num; 180. endcase 181.end 182. 183.assign key_num_en = (key_num_chg==0 || key_num_chg==1 || key_num_chg==2 || key_num_chg==3 || key_num_chg==4 || key_num_chg==5 || key_num_chg==6 || key_num_chg==7 || key_num_chg==8 || key_num_chg==9) && key_vld==1; 184.assign key_op_en = (key_num_chg==10 || key_num_chg==11 || key_num_chg==12 || key_num_chg==13) && key_vld==1; 185.assign key_cal_en = key_num_chg==15 && key_vld==1; 186.assign key_back_en = key_num_chg==14 && key_vld==1; 187. 188. 189. 190.always @(posedge clk or negedge rst_n) begin 191. if (rst_n==0) begin 192. state_c <= OP_1 ; 193. end 194. else begin 195. state_c <= state_n; 196. end 197.end 198. 199.always @(*) begin 200. if(result_err)begin 201. state_n = ERROR; 202. end 203. else begin 204. case(state_c) 205. OP_1 :begin 206. if(op_12oper_start) 207. state_n = OPER ; 208. else if(op_12result_start) 209. state_n = RESULT ; 210. else 211. state_n = state_c ; 212. end 213. OPER :begin 214. if(oper2op_2_start) 215. state_n = OP_2 ; 216. else if(oper2result_start) 217. state_n = RESULT ; 218. else 219. state_n = state_c ; 220. end 221. OP_2 :begin 222. if(op_22oper_start) 223. state_n = OPER ; 224. else if(op_22result_start) 225. state_n = RESULT ; 226. else 227. state_n = state_c ; 228. end 229. RESULT :begin 230. if(result2op_1_start) 231. state_n = OP_1 ; 232. else if(result2oper_start) 233. state_n = OPER ; 234. else 235. state_n = state_c ; 236. end 237. ERROR :begin 238. if(error2op_1_start) 239. state_n = OP_1 ; 240. else 241. state_n = state_c ; 242. end 243. default : state_n = OP_1 ; 244. endcase 245.end 246.end 247. 248.assign op_12oper_start = state_c==OP_1 && key_op_en ; 249.assign op_12result_start = state_c==OP_1 && key_cal_en; 250.assign oper2op_2_start = state_c==OPER && key_num_en; 251.assign oper2result_start = state_c==OPER && key_cal_en; 252.assign op_22oper_start = state_c==OP_2 && key_op_en ; 253.assign op_22result_start = state_c==OP_2 && key_cal_en; 254.assign result2op_1_start = state_c==RESULT && key_num_en; 255.assign result2oper_start = state_c==RESULT && key_op_en ; 256.assign error2op_1_start = state_c==ERROR && key_num_en; 257. 258.always @(posedge clk or negedge rst_n)begin 259. if(rst_n==1'b0)begin 260. key_num_out <= 0; 261. end 262. else begin 263. key_num_out <= key_num_chg; 264. end 265.end 266. 267.always @(posedge clk or negedge rst_n)begin 268. if(rst_n==1'b0)begin 269. key_vld_out <= 0; 270. end 271. else begin 272. key_vld_out <= key_vld; 273. end 274.end
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
Key_num_out | 输入 | 计算器按下位置输出信号,key_vld_out有效时,该信号有效。 |
Key_vld_out | 输入 | 计算器按键按下有效指示信号,高电平有效。 |
State_c | 输入 | 计算器工作状态指示信号 |
Op_1 | 输出 | 运算数1输出信号 |
Result | 输入 | 运算结果输出信号 |
Op_1_err | 输出 | 运算数1溢出信号 |
该模块主要的作用是根据当前状态和输入的按键,来决定运算数1要输出的结果。由于本工程需要实现连续运算的功能,所以在这个模块中要区分是否已经得出了运算结果。
下面是计算完成指示信号flag_calc的设计思路:
1、该信号为高时,表示完成一次计算过程得到了结果。初始状态为低电平;
2、当输入操作数2后又按下了等号或者其他操作符的时候,变为高电平,所以变高的条件为(state_c_ffOP_2 && state_cOPER) || state_cRESULT;
3、当处在操作数1状态时,为低电平,所以变低的条件为state_cOP_1。其他情况保持不变。
下面是运算数1输出信号op_1的设计思路:
1、该信号表示运算数1要输出的值。初始状态为0;
2、在结果错误状态的时候,给一个不超过范围的任意值,此代码中给的10;
3、在得到计算结果或者计算结果错误的时候,输入数字,输出为按下按键的对应值(key_num_out);
4、在输入操作数1之后,按下退格键,op_1输出的值除以10进行取整;
5、在输入操作数1状态下通过键盘输入数字,需要判断是否超过显示范围,如果没有超过的话就需要将当前op_1的值乘以10,然后加上按下的数字的值,进行输出;
6、当计算完成时,即flag_calc==1,操作数1输出计算的结果result;
7、其他时侯操作数1保持不变。
下面是运算数1溢出信号op_1_err的设计思路:
1、初始状态为0,表示没有溢出。
2、当一直处于操作数1状态,按下键盘输入数字之后,操作数1的值溢出了,则将运算数1溢出信号拉高。
3、其他时刻保持为低电平。
275.assign key_num_en = (key_num_out==0 || key_num_out==1 || key_num_out==2 || key_num_out==3 || key_num_out==4 || key_num_out==5 || key_num_out==6 || key_num_out==7 || key_num_out==8 || key_num_out==9) && key_vld_out==1; 276.assign key_op_en = (key_num_out==10 || key_num_out==11 || key_num_out==12 || key_num_out==13) && key_vld_out==1; 277.assign key_cal_en = key_num_out==15 && key_vld_out==1; 278.assign key_back_en = key_num_out==14 && key_vld_out==1; 279. 280.always @(posedge clk or negedge rst_n)begin 281. if(rst_n==1'b0)begin 282. state_c_ff <= 0; 283. end 284. else begin 285. state_c_ff <= state_c; 286. end 287.end 288. 289.always @(posedge clk or negedge rst_n)begin 290. if(rst_n==1'b0)begin 291. flag_calc <= 0; 292. end 293. else if(state_c==OP_1)begin 294. flag_calc <= 1'b0; 295. end 296. else if(state_c_ff==OP_2 && state_c==OPER || state_c==RESULT)begin 297. flag_calc <= 1'b1; 298. end 299. else begin 300. flag_calc <= flag_calc; 301. end 302.end 303. 304.always @(posedge clk or negedge rst_n)begin 305. if(rst_n==1'b0)begin 306. op_1 <= 0; 307. end 308. else if(state_c==ERROR)begin 309. op_1 <= 10; 310. end 311. else if((state_c_ff==RESULT || state_c_ff==ERROR) && state_c==OP_1)begin 312. op_1 <= key_num_out; 313. end 314. else if(state_c==OP_1 && key_back_en==1)begin 315. op_1 <= op_1 / 10; 316. end 317. else if(state_c==OP_1 && key_num_en==1)begin 318. op_1 <= (op_1>9999999) ? op_1 : (op_1*10+key_num_out); 319. end 320. else if(flag_calc==1)begin 321. op_1 <= result; 322. end 323. else begin 324. op_1 <= op_1; 325. end 326.end 327. 328.always @(posedge clk or negedge rst_n)begin 329. if(rst_n==1'b0)begin 330. op_1_err <= 0; 331. end 332. else if(state_c==OP_1 && state_c_ff==OP_1 && key_num_en==1 && op_1>9999999)begin 333. op_1_err <= 1'b1; 334. end 335. else begin 336. op_1_err <= 1'b0; 337. end 338.end
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
Key_num_out | 输入 | 计算器按下位置输出信号,key_vld_out有效时,该信号有效。 |
Key_vld_out | 输入 | 计算器按键按下有效指示信号,高电平有效。 |
State_c | 输入 | 计算器工作状态指示信号 |
oper | 输出 | 运算符输出信号 |
本模块的设计思路比较简单,只需要判断哪些按键是运算符,然后再这些运算符被按下的时候,将他们对应的值输出就可以了。
下面是运算符指示信号设计思路:
1、当“加”“减”“乘”“除”四个按键的任意一个被按下之后,该信号置为高电平;
2、当“加”“减”“乘”“除”四个按键没有一个被按下的时候,该信号置为低电平。
下面是运算符输出信号oper设计思路:
初始状态,该信号输出0;
1、当处于操作数1状态时,输出0;
2、当“加”“减”“乘”“除”任意按键被按下之后,输出该按键对应的值;
3、其他时候保持不变;
339.assign key_op_en = (key_num_out==10 || key_num_out==11 || key_num_out==12 || key_num_out==13) && key_vld_out==1; 340. 341.always @(posedge clk or negedge rst_n)begin 342. if(rst_n==1'b0)begin 343. oper <= 0; 344. end 345. else if(state_c==OP_1)begin 346. oper <= 0; 347. end 348. else if(key_op_en==1)begin 349. oper <= key_num_out; 350. end 351. else begin 352. oper <= oper; 353. end 354.End
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
Key_num_out | 输入 | 计算器按下位置输出信号,key_vld_out有效时,该信号有效。 |
Key_vld_out | 输入 | 计算器按键按下有效指示信号,高电平有效。 |
State_c | 输入 | 计算器工作状态指示信号 |
Op_2 | 输出 | 运算数2输出信号 |
Op_2_err | 输出 | 运算数2溢出信号 |
该模块主要的作用是根据当前状态和输入的按键,来决定运算数2要输出的结果。
下面是运算数2输出信号op_2的设计思路:
1、该信号表示运算数2要输出的值。初始状态为0;
2、在运算符状态下,此时数码管不显示运算数2的值,让它输出0;
3、输入运算符之后,之后再输入的就是运算数2的值,此时运算数2就等于按下按键所对应的数值。
4、在输入运算数2之后,按下退格键,运算数2的值除以10进行取整;
5、在输入运算数2状态下通过键盘输入数字,需要判断是否超过显示范围,如果没有超过的话就需要将
当前运算数2的值乘以10,然后加上按下的数字的值,进行输出;
6、其他时侯运算数2保持不变。
下面是运算数2溢出信号op_2_err的设计思路:
1、初始状态为0,表示没有溢出。
2、当一直处于运算数2状态,按下键盘输入数字之后,运算数2的值溢出了,则将运算数2溢出信号拉高。
3、其他时刻保持为低电平。
1.assign key_num_en = (key_num_out==0 || key_num_out==1 || key_num_out==2 || key_num_out==3 || key_num_out==4 || key_num_out==5 || key_num_out==6 || key_num_out==7 || key_num_out==8 || key_num_out==9) && key_vld_out==1; 2.assign key_op_en = (key_num_out==10 || key_num_out==11 || key_num_out==12 || key_num_out==13) && key_vld_out==1; 3.assign key_cal_en = key_num_out==15 && key_vld_out==1; 4.assign key_back_en = key_num_out==14 && key_vld_out==1; 5. 6.always @(posedge clk or negedge rst_n)begin 7. if(rst_n==1'b0)begin 8. state_c_ff <= 0; 9. end 10. else begin 11. state_c_ff <= state_c; 12. end 13.end 14. 15.always @(posedge clk or negedge rst_n)begin 16. if(rst_n==1'b0)begin 17. op_2 <= 0; 18. end 19. else if(state_c==OPER)begin 20. op_2 <= 0; 21. end 22. else if(state_c_ff==OPER && state_c==OP_2)begin 23. op_2 <= key_num_out; 24. end 25. else if(state_c==OP_2 && key_back_en==1)begin 26. op_2 <= op_2 / 10; 27. end 28. else if(state_c==OP_2 && key_num_en==1)begin 29. op_2 <= (op_2>9999999) ? op_2 : (op_2*10+key_num_out); 30. end 31. else begin 32. op_2 <= op_2; 33. end 34.end 35. 36.always @(posedge clk or negedge rst_n)begin 37. if(rst_n==1'b0)begin 38. op_2_err <= 0; 39. end 40. else if(state_c==OP_2 && key_num_en==1 && op_2>9999999)begin 41. op_2_err <= 1'b1; 42. end 43. else begin 44. op_2_err <= 1'b0; 45. end 46.end
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
Key_num_out | 输入 | 计算器按下位置输出信号,key_vld_out有效时,该信号有效。 |
Key_vld_out | 输入 | 计算器按键按下有效指示信号,高电平有效。 |
State_c | 输入 | 计算器工作状态指示信号 |
oper | 输出 | 运算符输出信号 |
Op_1 | 输入 | 运算数1输入信号 |
Op_2 | 输入 | 运算数2输入信号 |
Result | 输出 | 运算结果输出信号 |
Result_err | 输出 | 运算结果错误信号,运算结果溢出或者除数为0时,该信号输出一个时钟周期的高电平 |
Result_neg | 输出 | 运算结果符号位指示信号,当运算结果为负数时,该信号为高电平 |
本模块的作用是根据运算符,对运算数1和运算数2进行操作得出结果。
由于再进行计算的时候考虑小数减去大数的情况,所以运算结果允许为负数,因此需要有符号位指示信号,下面是运算结果符号位指示信号result_neg的设计思路:
1、只有当运算结果为负数的时候,才显示“负号”,因此初始状态为低电平;
2、当算式输入完成按下等号之后,如果运算符是“减”,并且运算数1小于运算数2,则运算结果为负数,将result_neg信号拉高。
3、由于该计算器支持连续输入,如果当前计算的结果为负数,接着输入的运算符为“加”,下一次进行加法运算,并且运算数1(此时比较不考虑符号位)小于或等于运算数2,则表示运算结果为正数,此时将result_neg信号拉低。
4、在进行连续计算的时候,如果得到的结果超过显示上限,要进入错误状态,这个时候符号位指示信号应该为低电平。
5、无论在计算中得到的结果是正还是负,如果下次输入的为运算数1,都需要result_neg信号为低电平。
6、由于除法不支持小数显示,只取整数部分,所以当运算结果为负数,并进行除法运算的时候,如果得到的结果为0,不应该显示为“负0”,应当将符号位指示信号置为低电平。
本模块主要的功能是实现加减乘除运算,下面是对运算结果输出信号result的设计思路:
1、初始状态没有经过计算,自然输出为0。
2、在进行加法的时候,由于存在连续计算的情况,需要考虑符号位。当符号位指示信号为0,直接将运算数1和运算数2相加即可;当符号位指示信号为1,则需要判断运算数1和运算数2的大小,确保是大的减去小的。
3、在进行减法的时候,同样需要考虑符号位。当符号位指示信号为0的时候,需要判断运算数1和运算数2的大小,保证大的减去小的;当符号位指示信号位1的时候,直接将运算数1和运算数2相加即可。
4、乘法运算直接将运算数1和运算数2相乘即可。
5、在进行除法运算时,由于无法表示小数,因此这里需要采用运算数1除以运算数2取整的方法,即op_1/op_2。
在计算过程中,如果得到的结果超过显示上限或者错误的计算方法,需要做出错误提示,下面是对于运算结果错误信号result_err的设计思路:
1、初始状态下,该信号为0,表示没有错误。
2、得到运算结果后,若继续输入数字,则会进入到运算数1状态,这个时候不进行错误提示。
3、在运算数2状态下输入运算符,或者在结果不是错误的状态下输出“等号”(表示进行连续计算)。根据输入的运算符进行相应的判断:
加:如果运算结果为正数,则判断运算数1加上运算数2之后会不会溢出,若溢出则做出错误提示;如果运算结果为负数,则不进行错误提示。
减:如果运算结果为负数,则判断运算数1加上运算数2之后会不会溢出,若溢出则做出错误提示;如果运算结果为正数,则判断两个数相减之后的结果是否会溢出。
乘:无论运算结果为何值,都只需要判断两数相乘之后的的结果会不会溢出就可以了。
除:在进行除法运算的时候,需要避免出现除数为0的情况,如果出现此情况,则进行错误指示。
1.assign key_op_en = (key_num_out==10 || key_num_out==11 || key_num_out==12 || key_num_out==13) && key_vld_out==1; 2.assign key_cal_en = key_num_out==15 && key_vld_out==1; 3.assign calculate = (state_c_ff==OP_2 && state_c==OPER || key_cal_en==1); 4. 5.always @(posedge clk or negedge rst_n)begin 6. if(rst_n==1'b0)begin 7. state_c_ff <= 0; 8. end 9. else begin 10. state_c_ff <= state_c; 11. end 12.end 13. 14.always @(posedge clk or negedge rst_n)begin 15. if(rst_n==1'b0)begin 16. result <= 0; 17. end 18. else if(calculate==1)begin 19. case(oper) 20. ADD:begin 21. if(result_neg==0) 22. result <= op_1 + op_2; 23. else 24. result <= (op_1>op_2) ? (op_1 - op_2) : (op_2 - op_1); 25. end 26. DEV:begin 27. if(result_neg==0) 28. result <= (op_1>op_2) ? (op_1 - op_2) : (op_2 - op_1); 29. else 30. result <= op_1 + op_2; 31. end 32. MUL:begin 33. result <= op_1 * op_2; 34. end 35. DIV:begin 36. result <= op_1 / op_2; 37. end 38. default:result <= op_1; 39. endcase 40. end 41. else begin 42. result <= result; 43. end 44.end 45. 46.always @(posedge clk or negedge rst_n)begin 47. if(rst_n==1'b0)begin 48. result_neg <= 0; 49. end 50. else if(state_c==OP_1)begin 51. result_neg <= 1'b0; 52. end 53. else if(state_c_ff==ERROR)begin 54. result_neg <= 1'b0; 55. end 56. else if(calculate==1 && oper==DEV && op_1<op_2)begin 57. result_neg <= 1'b1; 58. end 59. else if(calculate==1 && result_neg==1 && oper==ADD && op_1<=op_2)begin 60. result_neg <= 1'b0; 61. end 62. else if(result==0)begin 63. result_neg <= 1'b0; 64. end 65. else begin 66. result_neg <= result_neg; 67. end 68.end 69. 70.always @(posedge clk or negedge rst_n)begin 71. if(rst_n==1'b0)begin 72. result_err <= 0; 73. end 74. else if(state_c==OP_1)begin 75. result_err <= 1'b0; 76. end 77. else if((state_c_ff==OP_2 && state_c==OPER) || (key_cal_en==1 && state_c_ff!=ERROR))begin 78. case(oper) 79. ADD:begin 80. if(result_neg==0) 81. result_err <= (op_1+op_2)>9999_9999 ? 1'b1 : 1'b0; 82. else 83. result_err <= 1'b0; 84. end 85. DEV:begin 86. if(result_neg==1) 87. result_err <= (op_1+op_2)>999_9999 ? 1'b1 : 1'b0; 88. else if(op_2>op_1) 89. result_err <= (op_2-op_1)>999_9999 ? 1'b1 : 1'b0; 90. else 91. result_err <= 1'b0; 92. end 93. MUL:begin 94. if(result_neg==1) 95. result_err <= (op_1*op_2)>999_9999 ? 1'b1 : 1'b0; 96. else 97. result_err <= (op_1*op_2)>9999_9999 ? 1'b1 : 1'b0; 98. end 99. DIV:begin 100. if(op_2==0) 101. result_err <= 1'b1; 102. else 103. result_err <= 1'b0; 104. end 105. default:result_err <= 1'b0; 106. endcase 107. end 108. else begin 109. result_err <= 1'b0; 110. end 111.end
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
Op_1 | 输入 | 运算数1输入信号 |
Op_2 | 输入 | 运算数2输入信号 |
State_c | 输入 | 计算器工作状态指示信号 |
Result_neg | 输入 | 运算结果符号位指示信号 |
Disply | 输出 | 显示数据输出信号 |
Display_vld | 输出 | 显示数据有效指示信号 |
该模块的作用是根据当前计算器的工作状态来选择数码管的显示内容。
1、复位后,该模块输出0;
2、当计算器处于OP_1状态下,该模块选择输出运算数1。
3、当计算器处于OPER状态下,该模块选择输出运算数1。
4、当计算器处于OP_2状态下,该模块选择输出运算数2。
5、当计算器处于RESULT状态下,该模块选择输出运算数1。
6、当计算器处于ERROR状态下,该模块选择输出8个F。
要将数据送到数码管显示,需要将收到的数据进行拆分,比如输入进来的是“12”,需要拆成一个4bit的“1”和一个4bit的“2”送给数码管显示模块。因此设计一个计数器的架构,如下图所示:
架构中使用到了一个时钟计数器dis_cnt、一个采集状态指示信号flag_add、dis_sel为输入要显示的数据、dis_sel_tmp为输入数据打一拍之后的数据、result_neg为运算结果符号位指示信号、result_neg_tmp为运算结果符号位指示信号打一拍之后的信号。下面分别介绍一下这些信号的设计思路:
采集状态指示信号flag_add:初始状态为0,表示不对数据进行采集显示。如果检测到输入的数据或者符号位发生变化,表示要在数码管上显示的数据有变化,该信号拉高,计数器可以进行计数,所以由0变1的条件为dis_sel!=dis_sel_tmp || result_neg!=result_neg_tmp。当计数器数完之后,表示要显示的数据已经全部显示,则将此信号拉低,所以由1变0的条件是end_dis_cnt。
显示数据dis_sel:该信号根据工作状态进行选择,当目前处于OP_2状态时,选择运算数2输入数据,其他情况都选择运算数1输入数据。
显示数据打一拍之后的信号dis_sel_tmp:该信号存在的目的就是为了检测显示数据是否发生变化。
运算结果符号位指示信号result_neg:输入信号。
符号位指示信号打一拍之后的信号result_neg_tmp:该信号存在的意义就是为了检测符号位是否发生变化。
时钟计数器dis_cnt:该计数器的作用有两个,延时和控制输入数据赋值给显示数据输出信号的对应位。加一条件为flag_add && (dis_seldis_sel_tmp && result_negresult_neg_tmp),表示处在采集状态时,如果显示数据和符号位指示信号稳定,则开始计数。结束条件为数10个,由于计数器刚开始计数的时候,显示数据存在变化的可能,因此这里选择延迟两个时钟在对显示数据输出信号进行赋值(由于后面数据都是保持不变的,因此这个延时时间不是固定的,可以多延时一些),共有8个数码管,因此要赋值8次,所以计数器共需要数10个。
前面提到过,需要将显示数据显示到数码管上的话,需要将每一个数字进行拆分,一般采用除以10取余和取整的方法,本工程使用除法器的IP核,该IP核的作用就是将输入的数据除以10,得到商和余数。生成过程如下:
第一步、使用软件为Quartus Prime Lite Edition 18.1版本。首先打开软件之后,在主页面的右边找到“IP Catalog”窗口,在搜索栏中输入“div”进行搜索,然后双击“LPM_DIVIDE”。如果没有找到“IP Catalog”窗口,可在上方工具栏“Tools”中选择“IP Catalog”调出。
第二步、选择IP核生成的路径,并将其命名为“div”,注意这里的名字不能有中文字符或者全数字。在下方文件类型中选择“Verilog”,然后点击OK。
第三步、在之后出现的IP核设置界面中,“How wide should the numerator input”表示需要设置的分子的位宽,这里设置为27。“How wide should the denominator input”表示需要设置的分母的位宽这里设置为4。在下方分子和分母的表示都选用“Unsigned”无符号类型。然后点击Next
第四步、下图中的1处表示是否需要对输出进行打拍,这里选择打一拍之后输出。2处表示要进行的优化,这里选择默认优化。3处表示是否总是返回正余数,选择是。然后点击Next。
第五步、方框出表示该IP核在仿真的时候需要调用的库,直接点击Next即可。
第六步、这一界面是设置需要生成的文件,本工程只需要生成默认的即可,所以不用勾选。点击Finish。
112.always @(posedge clk or negedge rst_n)begin 113. if(rst_n==1'b0)begin 114. result_neg_tmp <= 0; 115. end 116. else begin 117. result_neg_tmp <= result_neg; 118. end 119.end 120. 121.always @(*)begin 122. if(state_c==OP_2)begin 123. dis_sel = op_2; 124. end 125. else begin 126. dis_sel = op_1; 127. end 128.end 129. 130.always @(posedge clk or negedge rst_n)begin 131. if(rst_n==1'b0)begin 132. dis_sel_tmp <= 0; 133. end 134. else begin 135. dis_sel_tmp <= dis_sel; 136. end 137.end 138. 139. 140.div div_prj( 141. .clock (clk ) , 142. .numer (dis_tmp ) , 143. .denom (10 ) , 144. .quotient (div_quo ) , 145. .remain (div_rem ) 146. ); 147. 148.always @(posedge clk or negedge rst_n)begin 149. if(rst_n==1'b0)begin 150. flag_add <= 0; 151. end 152. else if(dis_sel!=dis_sel_tmp || result_neg!=rssult_neg_tmp)begin 153. flag_add <= 1; 154. end 155. else if(end_dis_cnt)begin 156. flag_add <= 0; 157. end 158.end 159. 160. 161.always @(posedge clk or negedge rst_n) begin 162. if (rst_n==0) begin 163. dis_cnt <= 0; 164. end 165. else if(add_dis_cnt) begin 166. if(end_dis_cnt) 167. dis_cnt <= 0; 168. else 169. dis_cnt <= dis_cnt+1 ; 170. end 171.end 172.assign add_dis_cnt = flag_add && (dis_sel==dis_sel_tmp && result_neg==result_neg_tmp); 173.assign end_dis_cnt = add_dis_cnt && dis_cnt == 10-1 ; 174. 175. 176.assign dis_tmp = add_dis_cnt && dis_cnt==1 ? dis_sel : div_quo; 177. 178.always @(posedge clk or negedge rst_n)begin 179. if(rst_n==1'b0)begin 180. display <= 4'b0; 181. end 182. else if(state_c==ERROR)begin 183. display[4*(dis_cnt)-1 -:4] <= 4'b1111; 184. end 185. else if(end_dis_cnt && result_neg==1 && state_c!=OP_2)begin 186. display[31:28] <= 4'b1010; 187. end 188. else begin 189. display[4*(dis_cnt-1)-1 -:4] <= div_rem; 190. end 191.end 192. 193. 194.always @(posedge clk or negedge rst_n)begin 195. if(rst_n==1'b0)begin 196. display_vld <= 0; 197. end 198. else begin 199. display_vld <= (dis_cnt==0 && (dis_sel==dis_sel_tmp)) ? 1'b1 : 1'b0; 200. end 201.end
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
Display | 输入 | 显示数据输入信号 |
Display_vld | 输入 | 显示数据有效指示信号 |
Seg_sel | 输出 | 数码管位选信号 |
Segment | 输出 | 数码管段选信号 |
本模块主要实现的功能是对显示对象选择模块的显示数据输出信号(display)进行数码管显示。
1、复位后,数码管默认显示运算数1;
2、当result_err有效时,数码管显示8个F;
3、当result_neg有效时,第8个数码管显示“—”;
4、数码管显示display;
由于数码管显示在前面已有案例介绍,所以这个就不做介绍。感兴趣的同学可以看一下往期的文章:【每周FPGA案例】至简设计系列_7段数码管显示
1.always @(posedge clk or negedge rst_n) begin 2. if (rst_n==0) begin 3. count_20us <= 0; 4. end 5. else if(add_count_20us) begin 6. if(end_count_20us) 7. count_20us <= 0; 8. else 9. count_20us <= count_20us+1 ; 10. end 11.end 12.assign add_count_20us = 1; 13.assign end_count_20us = add_count_20us && count_20us == TIME_20US-1 ; 14. 15. 16.always @(posedge clk or negedge rst_n) begin 17. if (rst_n==0) begin 18. sel_cnt <= 0; 19. end 20. else if(add_sel_cnt) begin 21. if(end_sel_cnt) 22. sel_cnt <= 0; 23. else 24. sel_cnt <= sel_cnt+1 ; 25. end 26.end 27.assign add_sel_cnt = end_count_20us; 28.assign end_sel_cnt = add_sel_cnt && sel_cnt == SEG_NUM-1 ; 29. 30. 31. 32.always @(posedge clk or negedge rst_n)begin 33. if(rst_n==1'b0)begin 34. seg_sel <= {SEG_NUM{1'b1}}; 35. end 36. else begin 37. seg_sel <= ~(1'b1 << sel_cnt); 38. end 39.end 40. 41.always @(posedge clk or negedge rst_n)begin 42. if(rst_n==1'b0)begin 43. display_ff0 <= 0; 44. end 45. else begin 46. for(ii=0;ii<SEG_NUM;ii=ii+1)begin 47. if(display_vld==1)begin 48. display_ff0[(ii+1)*4-1 -:4] <= display[(ii+1)*4-1 -:4]; 49. end 50. else begin 51. display_ff0[(ii+1)*4-1 -:4] <= display_ff0[(ii+1)*4-1 -:4]; 52. end 53. end 54. end 55.end 56. 57.always @(*)begin 58. seg_tmp = display_ff0[(sel_cnt+1)*4-1 -:4]; 59.end 60. 61. 62.always @(posedge clk or negedge rst_n)begin 63. if(rst_n==1'b0)begin 64. segment <= NUM_0; 65. end 66. else begin 67. case(seg_tmp) 68. 0 :segment <=NUM_0 ; 69. 1 :segment <=NUM_1 ; 70. 2 :segment <=NUM_2 ; 71. 3 :segment <=NUM_3 ; 72. 4 :segment <=NUM_4 ; 73. 5 :segment <=NUM_5 ; 74. 6 :segment <=NUM_6 ; 75. 7 :segment <=NUM_7 ; 76. 8 :segment <=NUM_8 ; 77. 9 :segment <=NUM_9 ; 78. 10:segment <=NUM_10 ; 79. default:segment <= NUM_ERR; 80. endcase 81. end 82.end
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
Op_1_err | 输入 | 运算数1溢出信号,高电平有效 |
Op_2_err | 输入 | 运算数2溢出信号,高电平有效 |
Result_err | 输入 | 运算结果错误信号,高电平有效 |
Beep | 输出 | 蜂鸣输出信号,高电平有效 |
该模块的主要功能是根据接收到的各个错误指示信号,进行报警提示。当接收到错误信号有效的时候,蜂鸣器报警,持续1秒的时间,因此提出一个计数器的架构,如下图所示:
主要由时钟计数器cnt_1s和蜂鸣器输出组成,下面时两个信号的设计思路:
时钟计数器cnt_1s:该计数器的作用是计时1秒的时间。加一条件为flag_add,表示进入报警状态的时候便开始计数。结束条件为数5000_0000个,系统时钟为50M,一个时钟周期为20ns,5000_0000个时钟周期就是1秒。
蜂鸣器输出信号beep:初始状态为1,表示不报警。从1变0的条件为op_1_err || op_2_err || result_err,表示接收到这些错误指示信号之后,开始报警。从0变1的条件为end_cnt_1s,表示报警时间持续1秒,之后结束。
1.always @(posedge clk or negedge rst_n)begin 2. if(rst_n==1'b0)begin 3. flag_add <= 0; 4. end 5. else if(op_1_err || op_2_err || result_err)begin 6. flag_add <= 1; 7. end 8. else if(end_cnt_1s)begin 9. flag_add <= 0; 10. end 11.end 12. 13. 14.always @(posedge clk or negedge rst_n) begin 15. if (rst_n==0) begin 16. cnt_1s <= 0; 17. end 18. else if(add_cnt_1s) begin 19. if(end_cnt_1s) 20. cnt_1s <= 0; 21. else 22. cnt_1s <= cnt_1s+1 ; 23. end 24.end 25.assign add_cnt_1s = flag_add; 26.assign end_cnt_1s = add_cnt_1s && cnt_1s == CNT_1S-1 ; 27. 28. 29.always @(posedge clk or negedge rst_n)begin 30. if(rst_n==1'b0)begin 31. beep <= 1'b1; 32. end 33. else if(flag_add)begin 34. beep <= 1'b0; 35. end 36. else begin 37. beep <= 1'b1; 38. end 39.end
由于计算器的演示是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。
由于计算器的演示是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。
感兴趣的朋友也可以访问明德扬论坛(http://www.fpgabbs.cn/)进行FPGA相关工程设计学习,也可以看一下我们往期的文章.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。