–作者:小黑同学
本文为明德扬原创及录用文章,转载请注明出处!

1.1 总体设计

1.1.1 概述

计算器是近代人发明的可以进行数字运算的机器。现代的电子计算器能进行数学运算的手持电子机器,拥有集成电路芯片,但结构比电脑简单得多,可以说是第一代的电子计算机,且功能也较弱,但较为方便与廉价,可广泛运用于商业交易中,是必备的办公用品之一。除显示计算结果外,还常有溢出指示、错误指示等。计算器电源采用交流转换器或电池。为了节省电能,计算器都采用CMOS工艺制作的大规模集成电路。
计算器一般由运算器、控制器、存储器、键盘、显示器、电源和一些可选外围设备及电子配件,通过人工或机器设备组成,抵挡计算器的运算器、控制器由数字逻辑电路实现简单的串行运算
计算器是最早的计算工具,例如:古代印加人利用许多颜色的绳结来计数或者记录历史,还有古希腊人的安提凯希拉装置,中国的算盘等。中国古代最早采用的一种计算工具叫筹策,又被叫做算筹。

1.1.2 设计目标

简易计算器支持简单的四则运算(支持负数),在此基础上,添加了连续运算功能。计算器面板如下:

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.1.3 系统结构框图

系统结构框图如下所示:

图一

1.1.4 模块功能

键盘扫描模块实现功能
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、该模块的作用是对各种错误输入或输出进行响铃警告。

1.1.5 顶层信号

信号名 接口方向 定义
clk 输入 系统时钟,50Mhz
rst_n 输入 低电平复位信号
Key_col 输入 4位矩阵键盘列信号,默认高电平,开发板按键为普通按键时,不需要该信号
Key_row 输出 4位矩阵键盘行信号,默认低电平,开发板按键为普通按键时,不需要该信号
Segment 输出 8位数码管段选信号
Seg_sel 输出 8位数码管位选信号
beep 输出 1位蜂鸣器控制信号

1.1.6 参考代码

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

1.2 键盘扫描模块设计

1.2.1 接口信号

信号 接口方向 定义
clk 输入 系统时钟
rst_n 输入 低电平复位信号
key_col 输入 矩阵键盘列输入信号
Key_row 输出 矩阵键盘行输出信号
Key_out 输出 按键位置输出信号,key_vld有效时,该信号有效。
Key_vld 输出 按键有效指示信号,高电平有效

1.2.2 设计思路

在前面的案例中已经有矩阵键盘的介绍,所以这里不在过多介绍,详细介绍请看下方链接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=310

1.2.3 参考代码

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

1.3 工作状态选择模块设计

1.3.1 接口信号

信号 接口方向 定义
clk 输入 系统时钟
rst_n 输入 低电平复位信号
key_vld 输入 按键按下指示信号
Key_num 输入 按键位置输入信号,key_vld有效时,该信号有效
Result_err 输入 运算结果错误指示信号
Key_num_out 输出 计算器按下位置输出信号,key_vld_out有效时,该信号有效。
Key_vld_out 输出 计算器按键按下有效指示信号,高电平有效。
State_c 输出 计算器工作状态指示信号

1.3.2 设计思路

该模块的主要功能是根据按下的按键进行不同来判断和决定计算器的工作状态。一条等式可以写成:运算数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状态。

1.3.3 参考代码

使用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

1.4 运算数1模块设计

1.4.1 接口信号

信号 接口方向 定义
clk 输入 系统时钟
rst_n 输入 低电平复位信号
Key_num_out 输入 计算器按下位置输出信号,key_vld_out有效时,该信号有效。
Key_vld_out 输入 计算器按键按下有效指示信号,高电平有效。
State_c 输入 计算器工作状态指示信号
Op_1 输出 运算数1输出信号
Result 输入 运算结果输出信号
Op_1_err 输出 运算数1溢出信号

1.4.2 设计思路

该模块主要的作用是根据当前状态和输入的按键,来决定运算数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、其他时刻保持为低电平。

1.4.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

1.5 运算符模块设计

1.5.1 接口信号

信号 接口方向 定义
clk 输入 系统时钟
rst_n 输入 低电平复位信号
Key_num_out 输入 计算器按下位置输出信号,key_vld_out有效时,该信号有效。
Key_vld_out 输入 计算器按键按下有效指示信号,高电平有效。
State_c 输入 计算器工作状态指示信号
oper 输出 运算符输出信号

1.5.2 设计思路

本模块的设计思路比较简单,只需要判断哪些按键是运算符,然后再这些运算符被按下的时候,将他们对应的值输出就可以了。

下面是运算符指示信号设计思路:
1、当“加”“减”“乘”“除”四个按键的任意一个被按下之后,该信号置为高电平;
2、当“加”“减”“乘”“除”四个按键没有一个被按下的时候,该信号置为低电平。

下面是运算符输出信号oper设计思路:
初始状态,该信号输出0;
1、当处于操作数1状态时,输出0;
2、当“加”“减”“乘”“除”任意按键被按下之后,输出该按键对应的值;
3、其他时候保持不变;

1.5.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

1.6 运算数2模块设计

1.6.1 接口信号

信号 接口方向 定义
clk 输入 系统时钟
rst_n 输入 低电平复位信号
Key_num_out 输入 计算器按下位置输出信号,key_vld_out有效时,该信号有效。
Key_vld_out 输入 计算器按键按下有效指示信号,高电平有效。
State_c 输入 计算器工作状态指示信号
Op_2 输出 运算数2输出信号
Op_2_err 输出 运算数2溢出信号

1.6.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.6.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

1.7 运算单元模块设计

1.7.1 接口信号

信号 接口方向 定义
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.7.2 设计思路

本模块的作用是根据运算符,对运算数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.7.3 参考代码

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

1.8 显示对象选择模块设计

1.8.1 接口信号

信号 接口方向 定义
clk 输入 系统时钟
rst_n 输入 低电平复位信号
Op_1 输入 运算数1输入信号
Op_2 输入 运算数2输入信号
State_c 输入 计算器工作状态指示信号
Result_neg 输入 运算结果符号位指示信号
Disply 输出 显示数据输出信号
Display_vld 输出 显示数据有效指示信号

1.8.2 设计思路

该模块的作用是根据当前计算器的工作状态来选择数码管的显示内容。
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。

1.8.3 参考代码

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

1.9 数码管显示模块设计

1.9.1 接口信号

信号 接口方向 定义
clk 输入 系统时钟
rst_n 输入 低电平复位信号
Display 输入 显示数据输入信号
Display_vld 输入 显示数据有效指示信号
Seg_sel 输出 数码管位选信号
Segment 输出 数码管段选信号

1.9.2 设计思路

本模块主要实现的功能是对显示对象选择模块的显示数据输出信号(display)进行数码管显示。
1、复位后,数码管默认显示运算数1;
2、当result_err有效时,数码管显示8个F;
3、当result_neg有效时,第8个数码管显示“—”;
4、数码管显示display;

由于数码管显示在前面已有案例介绍,所以这个就不做介绍。感兴趣的同学可以看一下往期的文章:【每周FPGA案例】至简设计系列_7段数码管显示

1.9.3 参考代码

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

1.10 蜂鸣器模块设计

1.10.1 接口信号

信号 接口方向 定义
clk 输入 系统时钟
rst_n 输入 低电平复位信号
Op_1_err 输入 运算数1溢出信号,高电平有效
Op_2_err 输入 运算数2溢出信号,高电平有效
Result_err 输入 运算结果错误信号,高电平有效
Beep 输出 蜂鸣输出信号,高电平有效

1.10.2 设计思路

该模块的主要功能是根据接收到的各个错误指示信号,进行报警提示。当接收到错误信号有效的时候,蜂鸣器报警,持续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.10.3 参考代码

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

1.11 效果和总结

1.11.1 db603开发板

由于计算器的演示是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。

1.11.2 ms980试验箱

由于计算器的演示是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。

感兴趣的朋友也可以访问明德扬论坛(http://www.fpgabbs.cn/)进行FPGA相关工程设计学习,也可以看一下我们往期的文章.

至简设计系列_简易计算器相关推荐

  1. 至简设计系列_电子密码锁

    –作者:肖肖肖 本文为明德扬原创及录用文章,转载请注明出处! 1.1 总体设计 1.1.1 概述 随着生活质量的不断提高,加强家庭防盗安全变得非常重要,但传统机械锁的构造过于简单,很容易被打开,从而降 ...

  2. led计数电路实验报告_至简设计系列_状态机实现LED交通灯2

    --作者:肖肖肖 本文为明德扬原创及录用文章,转载请注明出处! 1.1 总体设计 1.1.1 概述 发光二极管简称为LED,是一种常用的发光器件,通过电子与空穴复合释放能量发光,可以高效的将电能转化为 ...

  3. 至简设计系列_按键控制数字时钟

    –作者:小黑同学 本文为明德扬原创及录用文章,转载请注明出处! 1.1 总体设计 1.1.1 概述 数字时钟是采用数字电路技术实现时.分.秒计时显示的装置,可以用数字同时显示时,分,秒的精确时间并实现 ...

  4. 至简设计系列_定时转换的LED交通灯1

    –作者:肖肖肖 本文为明德扬原创及录用文章,转载请注明出处! 1.1 总体设计 1.1.1 概述 发光二极管简称为LED,是一种常用的发光器件,通过电子与空穴复合释放能量发光,它可以高效的将电能转化为 ...

  5. c语言程序游戏玩家管理系统,c语言程序课程设计--游戏玩家管理系统及C语言课程设计_简易计算器.doc...

    湖南涉外经济学院 课程设计报告 课程名称: C语言课程设计 报告题目: 游戏玩家管理系统 学生姓名: ** ** 所在学院: 信息科学与工程学院 专业班级: 电科 学生学号: **** ****** ...

  6. C语言简单计算器考虑优先级,利用你现有的c语言知识 设计开发一个简易计算器,可进行加、减、乘、除、求余运算。...

    满意答案 nxy_159 推荐于 2017.11.21 采纳率:52%    等级:9 已帮助:3561人 #include float numA = 0; float numB = 0; float ...

  7. c语言课程设计之简易计算器,简易计算器课程设计 帮忙写简单计算器课程设计...

    帮忙写简单计算器课程设计书繁华若真如一梦,过而无痕多好,人就不必失意,只当醉了一场,醒来仍过平淡的生活. 程序 #include #include //定义atlf函数 #include 简介下的 要 ...

  8. QT学习日志(附:简易计算器,qq登录界面,简易绘图板,植物大战僵尸魔改版项目实践)

    目录 写在前面 实训前一天 头文件无法生成 项目栏不见了 不知道如何发布程序 实训第一天(附简易计算器的实现) 自定义命名空间的相关问题 关于隐式调用构造函数的问题 实训第二天(不附地址薄的实现) 关 ...

  9. 单片机实训简易计算机,单片机简易计算器实验报告.doc

    单片机简易计算器实验报告 单片机简易计算器实验报告 单片机原理及应用课程设计报告-简易计算器 <单片机原理>课 程课程设计 题 目简易计算器 院 (系) 信息工程学院 专 业 班 级 计算 ...

最新文章

  1. LR中的吞吐量与响应时间
  2. python【数据结构与算法】选数问题(选不相连最大值——DP)
  3. 邀请参加活动的邀请函_邀请函||王坝镇中心幼儿园邀请家长参加期中分享活动的通知...
  4. Spark1.3.0安装
  5. boost::metaparse::debug_parsing_error相关用法的测试程序
  6. iOS开发- 相机(摄像头)获取到的图片自动旋转90度解决办法
  7. vue函数如何调用其他函数?_好程序员Python教程系列之递归函数与匿名函数调用...
  8. 在命令行下对ntfs分区文件夹权限的设置
  9. react做h5 例子_使用React写一个网站的心得体会
  10. springboot细节挖掘(数据初始化)
  11. 【转】61条面向对象设计的经验原则
  12. 忽略字母大小写情况下统计字符出现的次数
  13. spark、hive、impala、hbase、gbase在结构化数据方面查询原理对比(含parquet/orc)
  14. Linux系统解决SSH登录慢的详细步骤
  15. 如何查询服务器jdk版本信息,linux服务器jdk版本查看_linux如何查看jdk版本
  16. Revit 和 ArchiCAD 在软件设计理念方面的对比
  17. 群控云控SDK开发包(快速开发群控云控微信SCRM客服系统)
  18. 随机访问介质访问控制 —— ALOHA协议
  19. 限速linux c语言,基于Linux系统的流量控制程序的C语言代码
  20. 情有独钟,迷情电子三五年(转贴)想学好电子必看 后悔的找我

热门文章

  1. html如何自动转到ie打开,在HTML怎么修改代码以达到在点击超链接时默认以IE浏览器打开?...
  2. secureCRT安装和使用教程【图文并茂】
  3. 一代音频传奇Winamp如何自毁长城
  4. 得州大停电后续来了!得州最大电力公司欠债18亿美元已申请破产
  5. 阿里职位层级解读(附P级详细要求)
  6. android APP隐藏NavigationBar,通过修改framework隐藏/显示 navigation bar
  7. 性能测试混合业务场景按比例设计
  8. 蚂蚁感冒(简单数论)
  9. access横向求和sum_对ACCESS数据表项目下的所有数据求和(列求和)及计数
  10. 如何参加活动拿到华为实践证书?一起来吧~