本文为明德扬原创及录用文章,转载请注明出处!
1.1 总体设计
1.1.1 概述
1.1.2 设计目标
1.1.3 系统结构框图
系统结构框图如下所示:

1.1.4 模块功能
- key模块实现功能
- speed模块实现功能
- fare模块实现功能
- show模块实现功能
|
信号名
|
接口方向
|
定义
|
|
clk
|
输入
|
系统时钟
|
|
rst_n
|
输入
|
系统复位
|
|
d_w
|
输入
|
出租车的档位信号,空挡为0,其他档位为1
|
|
rev
|
输入
|
出租车轮胎转速信号。通过该信号,可以用来计算汽车行驶的距离。
|
|
key_in
|
输入
|
乘客上、下车指示信号。
使用按键来表示。
|
|
seg_sel
|
输出
|
3个数码管的位选信号,在低电平是该位置数码管亮。
|
|
segment
|
输出
|
段选信号,共8位。由低到高,分别表示数码管的a,b,c,d,e,f,g,点。当该比特为0时,表示点亮相应位置;为1时熄灭。
|
1.1.6 顶层代码
|
module taxi_fare(
clk ,
rst_n ,
d_w ,
rev ,
key_in ,
seg_sel ,
segment
);
input clk ;
input rst_n ;
input d_w ; //档位
input [15:0] rev ; //转速
input key_in ; //按键,表示乘客是否上车
output seg_sel ; //位选信号,在低电平是该位置数码管亮。
output segment ; //段选信号,共8位。由低到高,分别表示数码管的a,b,c,d,e,f,g,点。当该比特为0时,表示点亮相应位置;为1时熄灭。
wire [3-1:0] seg_sel ;
wire [8-1:0] segment ;
//中间量
wire key_flag ; //有乘客为1,无乘客为0
wire en_0 ; //按时间计费使能信号
wire [19:0] distance ; //行驶路程
wire [3:0] x_g ; //总费用的个位值
wire [3:0] x_s ; //总费用的十位值
wire [3:0] x_b ; //总费用的百位值
key_litter key_litter_0(
.clk (clk) ,
.rst_n (rst_n) ,
.key_in (key_in) ,
.key_flag (key_flag)
);
speed speed_0(
.clk (clk) ,
.rst_n (rst_n) ,
.d_w (d_w) ,
.rev (rev) ,
.key_flag (key_flag) ,
//输出信号
.en_0 (en_0) ,
.distance (distance)
);
fare fare_0(
.clk (clk) ,
.rst_n (rst_n) ,
.distance (distance) ,
.en_0 (en_0) ,
.key_flag (key_flag) ,
.x_g (x_g) ,
.x_s (x_s) ,
.x_b (x_b)
);
show show_0(
.clk (clk) ,
.rst_n (rst_n) ,
.din ({x_b,x_s,x_g}) ,
.din_vld (3'b111) ,
.disp_en (1) ,
.seg_sel (seg_sel) ,
.segment (segment)
);
endmodule
|
1.2 key模块设计
1.2.1 接口信号
|
信号
|
接口方向
|
定义
|
|
clk
|
输入
|
系统时钟
|
|
rst_n
|
输入
|
系统复位
|
|
key_in
|
输入
|
按键输入。使用按键来表示乘客上、下车。
|
|
key_flag
|
输出
|
输出表示有无乘客。1为有乘客,0为没有乘客
|
1.2.2 设计思路
- 硬件电路

独立式按键工作原理如上图所示,4条输入线接到FPGA的IO口上,当按键K1按下时,VCC通过电阻R1再通过按键K1最终进入GND形成一条通路,这条线路的全部电压都加在R1上,则引脚P14是低电平。当松开按键后,线路断开,就不会有电流通过,P14和VCC就应该是等电位,为高电平。我们可以通过P14这个IO口的高低电平状态来判断是否有按键按下。其它按键原理与K1一致,当然本实验只需要一个按键即可,任选一个按键都可以。
- 按键消抖

1.2.3 参考代码
本模块设计了一个状态机,用于按键的检测。该状态机共包括4个状态。
其各个状态的含义如下。
|
module key_litter(
clk ,
rst_n ,
key_in ,
key_flag
);
//消抖的状态
parameter IDLE = 4'b0000 ;
parameter S1 = 4'b0001 ;
parameter S2 = 4'b0010 ;
parameter S3 = 4'b0100 ;
parameter S4 = 4'b1000 ;
//输入信号定义
input clk ;
input rst_n ;
input key_in ;
//输出信号定义
output key_flag; //输出,表示是否有乘客
//输出信号reg定义
reg key_flag;
//中间信号定义
reg [19:0] cnt ;
reg key_in_1; //寄存器
reg key_in_2; //寄存器
reg [3:0] state_c ;
reg [3:0] state_n ;
wire idl2s1_start ;
wire s12s2_start ;
wire s12s2_end ;
wire s22s3_start ;
wire s32s4_start ;
wire s32s4_end ;
wire add_cnt ; //计数进行
wire end_cnt ; //计数清零
wire cnt_during ; //计数过程中
//打两拍
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_in_1 <= 0;
key_in_2 <= 0;
end
else begin
key_in_1 <= key_in;
key_in_2 <= key_in_1;
end
end
//消抖
//四段式状态机
//第一段:同步时序always模块,格式化描述次态寄存器迁移到现态寄存器(不需更改)
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段:组合逻辑always模块,描述状态转移条件判断
always@(*)begin
case(state_c)
IDLE:begin
if(idl2s1_start)begin
state_n = S1;
end
else begin
state_n = state_c;
end
end
S1:begin
if(s12s2_start)begin
state_n = S2;
end
else if(s12s2_end)begin
state_n = IDLE ;
end
else begin
state_n = state_c;
end
end
S2:begin
if(s22s3_start)begin
state_n = S3;
end
else begin
state_n = state_c;
end
end
S3:begin
if(s32s4_start)begin
state_n = IDLE;
end
else if(s32s4_end)begin
state_n = S2;
end
else begin
state_n = state_c;
end
end
default:begin
state_n = IDLE;
end
endcase
end
//第三段:设计转移条件
assign idl2s1_start = key_in_2 == 0;
assign s12s2_start = key_in_2==0 && end_cnt == 1;
assign s12s2_end = key_in_2==1 && cnt_during ;
assign s22s3_start = key_in_2 == 1;
assign s32s4_start = key_in_2 == 1 && end_cnt == 1;
assign s32s4_end = key_in_2 == 0 && cnt_during ;
//第四段:同步时序always模块,格式化描述寄存器输出(可有多个输出)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_flag <=1'b0 ; //初始化
end
else if(s32s4_start)begin
key_flag = ~key_flag;
end
else begin
key_flag <= key_flag;
end
end
//计数器,计数10ms
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
else
cnt <= 0;
end
assign add_cnt = state_c == S1 || state_c == S3;
assign end_cnt = add_cnt && cnt== /*仿真时使用以减少仿真时间5 - 1;*/ 500000-1;
assign cnt_during = add_cnt && end_cnt == 0;
endmodule
|
1.3 speed模块设计
1.3.1 接口信号
|
信号
|
接口方向
|
定义
|
|
clk
|
输入
|
系统时钟
|
|
rst_n
|
输入
|
系统复位
|
|
d_w
|
输入
|
出租车档位信号,空挡为0、其他档位为1
|
|
rev
|
输入
|
出租车轮胎转速信号。通过该信号,可以用来计算汽车行驶的距离。
|
|
key_flag
|
输入
|
表示有无乘客上车,有乘客为1,没有为0
|
|
en_o
|
输出
|
表示出租车按时间计费信号,1位按时间计费,0则不按时间计费。等待红绿灯或堵车等需要停车过程中,使用时间计费;行驶过程中,使用距离计费。
|
|
distance
|
输出
|
输出乘客乘坐的总路程
|
1.3.2 设计思路
1.3.3 参考代码
|
module speed(
clk ,
rst_n ,
d_w ,
rev ,
key_flag,
//输出信号
en_0 ,
distance
);
//参数定义
parameter DATA_W = 20;
//输入信号定义
input clk ;
input rst_n ;
input d_w ; //档位
input rev ; //转速
input key_flag; //有无乘客信号
//输入信号定义
wire [15:0] rev ;
//输出信号定义
output[DATA_W-1:0] distance ; //路程
output en_0 ; //按时计费信号
//输出信号reg定义
reg [DATA_W-1:0] distance ;
reg en_0 ;
//中间信号定义
reg [15:0] rev_1 ; //每秒取样转速信号
reg [25:0] cnt ; //一秒的计数器
wire high_en_0 ; //拉高按时计费信号的信号
wire add_cnt ;
wire end_cnt ;
//按时计费
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
en_0 <= 0;
end
else if(high_en_0) begin
en_0 <= 1;
end
else
en_0 <= 0;
end
assign high_en_0 = ( d_w == 0 && rev < 3 || rev < 3 ) && key_flag == 1;
//计数1秒
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
else if(key_flag == 0)
cnt <= 0;
end
assign add_cnt = rev >= 3 && key_flag == 1;
assign end_cnt = add_cnt && cnt == /*仿真时使用以节省仿真时间500-1;*/50000000-1 ;
//每秒取样rev
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rev_1 <= 0;
end
else if(end_cnt) begin
rev_1 <= rev;
end
end
//distance
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
distance <= 0;
end
else if(end_cnt) begin
distance <=distance + rev_1/3;
end
end
endmodule
|
1.4 fare模块设计
1.4.1 接口信号
|
信号
|
接口方向
|
定义
|
|
clk
|
输入
|
系统时钟
|
|
rst_n
|
输入
|
系统复位
|
|
distance
|
输入
|
乘客乘坐总路程
|
|
en_0
|
输入
|
按时间计费信号,1为按时间计费,0为不按时间计费
|
|
key_flag
|
输入
|
表示是否有乘客,有为1,没有为0,且在没有乘客时,所有信号归0
|
|
x_g
|
输出
|
输出总费用的个位数值
|
|
x_s
|
输出
|
输出总费用的十位数值
|
|
x_b
|
输出
|
输出总费用的百位数值
|
1.4.2 设计思路
|
module fare(
clk ,
rst_n ,
distance,
en_0 ,
key_flag,
x_g ,
x_s ,
x_b
);
//参数定义
parameter DATA_W = 4;
//输入信号定义
input clk ;
input rst_n ;
input en_0 ; //按时计费
input [19:0] distance; //路程
input key_flag; //有无乘客
//输出信号定义
output[DATA_W-1:0] x_g ; //数码管显示个位
output[DATA_W-1:0] x_s ; //数码管显示十位
output[DATA_W-1:0] x_b ; //数码管显示百位
//输出信号reg定义
reg [DATA_W-1:0] x_g ;
reg [DATA_W-1:0] x_s ;
reg [DATA_W-1:0] x_b ;
//中间信号定义
reg [9:0] taxi_fare ; //总车费
reg [9:0] taxi_fare_1; //按路程车费
reg [9:0] taxi_fare_2; //按时间车费
reg [35:0] cnt ; //计数20分钟
reg key_flag1 ; //寄存器
reg key_flag2 ; //寄存器
reg key_flag_low ; //乘客下车信号
reg key_flag_low1 ; //乘客下车后隔一个时钟周期信号
reg [2:0] state ;
wire add_taxi_fare_1;
wire add_taxi_fare_2;
wire add_cnt ;
wire end_cnt ;
//获取key_flag下降沿,表示乘客下车
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_flag1 <= 0;
end
else begin
key_flag1 <= key_flag;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_flag2 <= 0;
end
else begin
key_flag2 <= key_flag1;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_flag_low <= 0;
end
else if(key_flag2==1&&key_flag1==0)begin
key_flag_low <= 1;
end
else begin
key_flag_low <= 0;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_flag_low1 <= 0;
end
else
key_flag_low1 <= key_flag_low;
end
//按路程计费
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
taxi_fare_1 <= 0;
end
else if(add_taxi_fare_1)begin
if(distance<=3000)begin
taxi_fare_1 <= 5;
end
else if(distance>3000)begin
taxi_fare_1 <= 3'd5+ (distance-3000) / 500;//* 0.002;
end
end
end
assign add_taxi_fare_1 = key_flag_low && en_0 == 0 ;
//按时间计费
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
taxi_fare_2 <= 0;
end
else if(add_taxi_fare_2)begin
taxi_fare_2 <= taxi_fare_2 + 1;
end
end
assign add_taxi_fare_2 = end_cnt == 1;
//计数20分钟
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = en_0 == 1 && key_flag == 1;
assign end_cnt = add_cnt && cnt== /*仿真时使用以节省仿真时间500*1200-1;*/ 50000000*1200-1;
//获得总车费
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
taxi_fare <= 0;
end
else if(key_flag_low1==1)begin
taxi_fare <= taxi_fare_1+ taxi_fare_2;
end
end
//输出
always @(*)begin
if(rst_n == 1'b0)begin
x_g <= 0;
x_s <= 0;
x_b <= 0;
state <= 1;
end
else if(taxi_fare != 0)begin
case(state)
1: begin x_g <= taxi_fare%10;taxi_fare <= taxi_fare/10;state <= 2;end
2: begin x_s <= taxi_fare%10;taxi_fare <= taxi_fare/10;state <= 3;end
3: begin x_b <= taxi_fare%10;taxi_fare <= taxi_fare/10;state <= 1;end
endcase
end
end
endmodule
|
1.5 show模块设计
1.5.1 接口信号
|
信号
|
接口方向
|
定义
|
|
clk
|
输入
|
系统时钟
|
|
rst_n
|
输入
|
系统复位
|
|
disp_en
|
输入
|
打开数码管显示的使能信号。1表示打开显示,0表示不显示。
|
|
din
|
输入
|
输入总费用{x_b,x_s,x_g}
|
|
din_vld
|
输入
|
数码管显示数据刷新使能信号。当其值为1有效时,刷新要显示的数据值。
|
|
seg_sel
|
输出
|
3个数码管的位选信号,在低电平是该位置数码管亮。
|
|
segment
|
输出
|
段选信号,共8位。由低到高,分别表示数码管的a,b,c,d,e,f,g,点。当该比特为0时,表示点亮相应位置;为1时熄灭。
|
1.5.2 设计思路
1.5.3 参考代码
|
接口定义:
clk : 时钟信号,频率是50MHz
rst_n : 复位信号,在低电平时有效
seg_sel : 位选信号,在低电平是该位置数码管亮。
segment : 段选信号,共8位。由低到高,分别表示数码管的a,b,c,d,e,f,g,点。当该比特为0时,表示点亮相应位置;为1时熄灭。
module show(
rst_n ,
clk ,
disp_en ,
din ,
din_vld ,
seg_sel ,
segment
);
参数定义,明德扬规范要求,verilog内的用到的数字,都使用参数表示。
参数信号全部大写
parameter SEG_WID = 8;
parameter SEG_NUM = 3;
parameter COUNT_WID = 26;
parameter TIME_20US = 20'd1000;
parameter NUM_0 = 8'b1100_0000;
parameter NUM_1 = 8'b1111_1001;
parameter NUM_2 = 8'b1010_0100;
parameter NUM_3 = 8'b1011_0000;
parameter NUM_4 = 8'b1001_1001;
parameter NUM_5 = 8'b1001_0010;
parameter NUM_6 = 8'b1000_0010;
parameter NUM_7 = 8'b1111_1000;
parameter NUM_8 = 8'b1000_0000;
parameter NUM_9 = 8'b1001_0000;
parameter NUM_F = 8'b1011_1111;
parameter NUM_ERR = 8'b1000_0110;
input clk ;
input rst_n ;
input disp_en ;
input [SEG_NUM*4-1:0] din ;
input [SEG_NUM-1:0] din_vld ;
output [SEG_NUM-1:0] seg_sel ;
output [SEG_WID-1:0] segment ;
reg [SEG_NUM-1:0] seg_sel ;
reg [SEG_WID-1:0] segment ;
reg [COUNT_WID-1:0] cnt0 ;
wire add_cnt0 ;
wire end_cnt0 ;
reg [SEG_NUM-1:0] cnt1 ;
wire add_cnt1 ;
wire end_cnt1 ;
reg [4*SEG_NUM-1:0] din_ff0 ;
reg [ 4-1:0] seg_tmp ;
wire flag_20us ;
integer ii ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = 1;
assign end_cnt0 = add_cnt0 && cnt0==TIME_20US-1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==SEG_NUM-1 ;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
seg_sel <= {SEG_NUM{1'b1}};
end
else if(disp_en)
seg_sel <= ~(1'b1 << cnt1);
else
seg_sel <= {SEG_NUM{1'b1}};
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
din_ff0 <= 0;
end
else begin
for(ii=0;ii<SEG_NUM;ii=ii+1)begin
if(din_vld[ii]==1'b1)begin
din_ff0[(ii+1)*4-1 -:4] <= din[(ii+1)*4-1 -:4];
end
else begin
din_ff0[(ii+1)*4-1 -:4] <= din_ff0[(ii+1)*4-1 -:4];
end
end
end
end
always @(*)begin
seg_tmp = din_ff0[(cnt1+1)*4-1 -:4];
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
segment<=NUM_0;
end
else if(seg_tmp==0)begin
segment<=NUM_0;
end
else if(seg_tmp==1)begin
segment<=NUM_1;
end
else if(seg_tmp==2)begin
segment<=NUM_2;
end
else if(seg_tmp==3)begin
segment<=NUM_3;
end
else if(seg_tmp==4)begin
segment<=NUM_4;
end
else if(seg_tmp==5)begin
segment<=NUM_5;
end
else if(seg_tmp==6)begin
segment<=NUM_6;
end
else if(seg_tmp==7)begin
segment<=NUM_7;
end
else if(seg_tmp==8)begin
segment<=NUM_8;
end
else if(seg_tmp==9)begin
segment<=NUM_9;
end
else if(seg_tmp==4'hf)begin
segment<=NUM_F;
end
else begin
segment<=NUM_ERR;
end
end
endmodule
|
1.6 效果和总结
- 仿真验证特殊说明:



- 测试文件和理论计算
|
`define clk_period 20
module taxi_fare_tb(
);
reg clk ;
reg rst_n ;
reg d_w ;
reg [15:0] rev ;
reg key_in ;
wire [3-1:0] seg_sel ;
wire [8-1:0] segment ;
taxi_fare taxi_fare_0(
.clk (clk) ,
.rst_n (rst_n) ,
.d_w (d_w) ,
.rev (rev) ,
.key_in (key_in) ,
.seg_sel (seg_sel) ,
.segment (segment)
);
initial clk = 1;
always#(`clk_period/2) clk = ~clk;
initial begin
rst_n = 0;
#100;
rst_n = 1;
end
initial begin
d_w = 0;
#10000;
d_w = 1;
#200000000;
d_w = 0;
end
initial begin
rev = 0;
#10000;
rev = 15;
#50000000;
rev = 1;
#24000000;
rev = 0;
end
initial begin
key_in = 1;
#1000;
key_in = 0;
#10;
key_in = 1;
#10;
key_in = 0;
#10;
key_in = 1;
#10;
key_in = 0;
#9000;
key_in = 1;
#10;
key_in = 0;
#10;
key_in = 1;
#74000000;
key_in = 0;
#9000;
key_in = 1;
end
endmodule
|
- 仿真验证结果









