明德扬吴老师 发表于 2020-4-15 10:44:35

[至简设计案例]基于FPGA的出租车计费系统


基于FPGA的出租车计费系统(设计分享)                                                                                             --作者:就楞哎
                                                                                                                                                                         本文为明德扬原创及录用文章,转载请注明出处!1.1 总体设计1.1.1 概述    学习了明德扬至简设计法和明德扬设计规范,本人设计了一个基于FPGA的出租车计费系统。该系统由一个按键表示出租车上是否有乘客,再通过检测出租车的档位和轮胎的转速来获得乘客所需支付的总费用。在本案例的设计过程中,包含了按键定义和消抖、计数器、数码管显示等技术。经过逐步改进、调试等一系列操作之后,完成了此设计,下面将完整的设计记录与大家分享。
本工程使用VIVADO进行仿真,未进行上板。

1.1.2 设计目标
   此设计可以完成出租车计费的功能。在按键按下后(乘客上车),开始按如下规则计费:
   起步价5元,超过3KM以每公里2元计费,如果遇到红绿灯、堵车等需要停车等待时,则以每20分钟1元计费。
   再按一下按键(乘客下车)则计费结束,算出乘客所需支付的总费用,并在数码管上显示。
1.1.3 系统结构框图
      系统结构框图如下所示:

1.1.4 模块功能Ø key模块实现功能
      由于按键信号是异步信号,所以对该将信号打两拍处理,将异步信号同步化;
      实现10ms按键消抖功能,并输出按键信号key_flag,每按一次按键,key_flag取反。
    其中key_flag=1表示有乘客,key_flag=0表示没有乘客。
Ø speed模块实现功能
    通过按键信号key_flag有效,对轮胎转速进行每秒取样,进而获得乘客行驶总的路程和判断按时计费的使能信号en_0是否有效。
Ø fare模块实现功能
    通过speed模块的总路程和按时计费有效使能信号来获得乘客所需支付的总费用,再取得总费用的个位、十位和百位数值并输出。
Ø show模块实现功能
    采用动态扫描3个数码管的方式显示fare模块中获得的三个数值即为乘客所需支付的总费用。
1.1.5 顶层信号

信号名接口方向定义
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   rev       ;          //转速    input             key_in    ;          //按键,表示乘客是否上车
    output            seg_sel   ;          //位选信号,在低电平是该位置数码管亮。    output            segment   ;          //段选信号,共8位。由低到高,分别表示数码管的a,b,c,d,e,f,g,点。当该比特为0时,表示点亮相应位置;为1时熄灭。
    wire         seg_sel    ; wire         segment   ;
    //中间量    wire            key_flag   ;         //有乘客为1,无乘客为0    wire            en_0      ;      //按时间计费使能信号    wire      distance   ;         //行驶路程    wire         x_g      ;         //总费用的个位值    wire         x_s      ;         //总费用的十位值    wire         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 设计思路
    此模块通过一个按键来表示车上是否有乘客,复位时输出信号key_flag为0,当乘客上车时,司机按一下按键,key_flag由0变,1,表明乘客已上车,系统开始计费。到达目的地时,司机再按一下按键,key_flag由1变0,表明到达目的地乘客下车,系统结束计费。
Ø 硬件电路
    独立式按键工作原理如上图所示,4条输入线接到FPGA的IO口上,当按键K1按下时,VCC通过电阻R1再通过按键K1最终进入GND形成一条通路,这条线路的全部电压都加在R1上,则引脚P14是低电平。当松开按键后,线路断开,就不会有电流通过,P14和VCC就应该是等电位,为高电平。我们可以通过P14这个IO口的高低电平状态来判断是否有按键按下。其它按键原理与K1一致,当然本实验只需要一个按键即可,任选一个按键都可以。
    从图中可以看出,如果我们按下按键,那么按键就会接通并连接到低电平GND,如果我们没有按下,那么按键就会断开并接到VCC,因此按键为低电平有效。通常的按键所用开关为机械弹性开关,当机械触点断开或者闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而机械式按键在闭合及断开的瞬间均伴随有一连串的抖动,如果不进行处理,会使系统识别到抖动信号而进行不必要的反应,导致模块功能不正常,为了避免这种现象的产生,需要进行按键消抖的操作。
Ø 按键消抖
    按键消抖主要分为硬件消抖和软件消抖。两个“与非”门构成一个RS触发器为常用的硬件消抖。软件方法消抖,即检测出键闭合后执行一个延时程序,抖动时间的长短由按键的机械特性决定,一般为5ms~20ms,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认按下按键操作有效。当检测到按键释放后,也要给5ms~20ms的延时,待后沿抖动消失后才能转入该键的处理程序。经过按键消抖的行人优先按键,判断按键有效后,按键信号传递给控制系统,控制系统再进入相应的处理程序。如还不明白之处,见实验的PDF。




图5.1.2按键消抖示意图

1.2.3 参考代码
    使用明德扬的计数器模板,可以很快速很熟练地写出按键消抖模块。
    每10ms扫描一次按键输入key_in,可以达到消抖的目的,再用寄存器缓存一下,按键为低电平有效;但本实验是需要按键按下松开这样一次完整的按按键操作后输出key_flag才发生变化,所以检测当检测到按键有上升沿变化时,代表该按键被按下松开,按键输出key_flag才发生变化。
本模块设计了一个状态机,用于按键的检测。该状态机共包括4个状态。其各个状态的含义如下。
空闲状态(IDLE):表示按键没有被按下,检测到低电平进入下一状态。
延时确认状态(S1):开始10ms延时计数,若计数完成且依然为低电平则进入下一状态,若计数期间按键出现高电平说明为抖动回到初始状态。
检测释放状态(S2):表示按键按下未松开,检测到高电平进入下一状态。
延时确认状态(S3):开始10ms延时计数,若计数完成且依然为高电平视为有效松手行为进入初始状态再检测下一次按键按下,若计数期间按键出现低电平说明为抖动回到S2状态。
   代码如下:

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         cnt   ;
    reg               key_in_1;                      //寄存器    reg               key_in_2;                      //寄存器
    reg               state_c ;    reg               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 设计思路
   消抖后的按键信号输入到本模块中,同样使用明德扬至简设计法和计数器模板,可以快速写出计算总路程和获得按时计费信号en_0有效的代码。当key_flag为1有效且转速rev>=3r/s时,计数器开始计数,每计一秒钟对转速信号rev取样,获得每秒行驶路程并累加,当key_flag为0时,计数停止,累加也停止,此时获得的累加值即为总路程。当key_flag为1有效且rev<3r/s时,en_0拉高为1,表示此时需要按时计费。

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       rev    ;    //输出信号定义    outputdistance   ;                     //路程    output            en_0       ;                     //按时计费信号
    //输出信号reg定义    reg   distance   ;    reg               en_0       ;
    //中间信号定义    reg           rev_1      ;                     //每秒取样转速信号    reg         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 设计思路
   从speed模块中得到乘客乘坐总路程distance和按时计费使能信号en_0,然后以5元起步价,超过3KM以每满1公里2元的计费方式计算出按路程计费的总费用,再通过en_0按20分钟1元的计费方式计算出按时间计费的总费用,再求和获得总费用,最后得到总费用得个位、十位、百位,分别是x_g、x_s、x_b。
1.4.3 参考代码


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         distance;                        //路程    input               key_flag;                        //有无乘客
    //输出信号定义    outputx_g    ;                           //数码管显示个位    outputx_s    ;                           //数码管显示十位    outputx_b    ;                           //数码管显示百位


    //输出信号reg定义    reg   x_g    ;    reg   x_s    ;    reg   x_b    ;
    //中间信号定义    reg               taxi_fare;                   //总车费    reg               taxi_fare_1;                   //按路程车费    reg               taxi_fare_2;                   //按时间车费    reg                cnt      ;                   //计数20分钟    reg                      key_flag1;                   //寄存器    reg                      key_flag2;                   //寄存器    reg                      key_flag_low;                //乘客下车信号    reg                      key_flag_low1 ;                //乘客下车后隔一个时钟周期信号    reg               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: beginx_g <= taxi_fare%10;taxi_fare <= taxi_fare/10;state <= 2;end             2: beginx_s <= taxi_fare%10;taxi_fare <= taxi_fare/10;state <= 3;end             3: beginx_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 设计思路
    由于在fare模块中已经获得总费用的个位、十位和百位的值,所以在本模块只需要控制3个数码管对其数值进行显示即可。
    本模块在设计过程中采用动态扫描3个数码管的方式进行显示,并且直接使用明德杨提供的数码管显示规范代码。动态扫描方式相比于使用3个独立的数码管显示会节约资源,硬件电路更简单,且数码管越多优势越明显。

1.5.3 参考代码


接口定义:clk               : 时钟信号,频率是50MHzrst_n             : 复位信号,在低电平时有效seg_sel      : 位选信号,在低电平是该位置数码管亮。segment       : 段选信号,共8位。由低到高,分别表示数码管的a,b,c,d,e,f,g,点。当该比特为0时,表示点亮相应位置;为1时熄灭。**********www.mdy-edu.com 明德扬科教注释结束****************/
moduleshow(               rst_n       ,               clk         ,               disp_en   ,               din         ,               din_vld   ,
               seg_sel   ,               segment                   );
/*********www.mdy-edu.com 明德扬科教注释开始****************参数定义,明德扬规范要求,verilog内的用到的数字,都使用参数表示。参数信号全部大写**********www.mdy-edu.com 明德扬科教注释结束****************/
parameterSEG_WID      =       8;parameterSEG_NUM      =       3;parameterCOUNT_WID      =       26;parameterTIME_20US      =       20'd1000;

parameterNUM_0          =       8'b1100_0000;parameterNUM_1          =       8'b1111_1001;parameterNUM_2          =       8'b1010_0100;parameterNUM_3          =       8'b1011_0000;parameterNUM_4          =       8'b1001_1001;parameterNUM_5          =       8'b1001_0010;parameterNUM_6          =       8'b1000_0010;parameterNUM_7          =       8'b1111_1000;parameterNUM_8          =       8'b1000_0000;parameterNUM_9          =       8'b1001_0000;parameterNUM_F          =       8'b1011_1111;parameterNUM_ERR      =       8'b1000_0110;

input                           clk       ;input                           rst_n   ;input                           disp_en   ;input            din       ;input            din_vld   ;output             seg_sel   ;output             segment   ;
reg                seg_sel   ;reg                segment   ;reg                cnt0      ;wire                              add_cnt0;wire                              end_cnt0;reg                cnt1      ;wire                              add_cnt1;wire                              end_cnt1;reg                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;    endend
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;    endend
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==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    endend
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;        endend
endmodule




1.6 效果和总结Ø 仿真验证特殊说明:
    由于本系统涉及1秒、20分钟等时间节点,时间很长,所以在仿真时有一定的困难,为此我将系统中的时间节点全部乘上10^(-5),让所有状态提前到达,方便我们仿真验证。
    由于乘上了10^(-5),所以原消抖10ms在仿真中为100ns;
    系统中原1s取样转速rev在仿真中为10us;     原按时计费20分钟/元在仿真中为12ms/元;


Ø 测试文件和理论计算
    在测试文件中转速rev>=3r/s的时间是50000000ns;
    总路程distance=15/3*50000000/10000=25km;
    由于在测试文件中按键消抖有一段时间且这一段时间在rev>=3r/s的时间内,所以系统实际对速度采样的次数会少一次,所以最终的总路程会少5m;
    所以,按路程计费的总费用taxi_fare_1=5+(25 - 3)*2 – 0.005*2=48.99(元);
    去除小数部分taxi_fare_1=48(元);
    在测试文件中转速rev<3r/s的时间是24000000ns;
    按时间计费的总费用taxi_fare_2=24000000/12000000=2(元);
    所以总车费taxi_fare_1=taxi_fare_1+taxi_fare_2=50(元);


`define clk_period 20module taxi_fare_tb(
    );
    reg             clk       ;    reg             rst_n   ;    reg             d_w       ;    reg   rev       ;    reg             key_in    ;
    wire          seg_sel    ;    wire          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


Ø 仿真验证结果
    由仿真结果可以看到seg_sel=110第1位数码管显示时segment=11000000、seg_sel=101第2位数码管显示时segment=10010010、seg_sel=011第3位数码管显示时segment=11000000,在数码管上分别对应数值为0、5、0,表示为50元。
    仿真结果和理论结构都是50元,符合我们的预期,验证成功。
    在这个设计中,使用明德杨的至简设计法,让我的思路非常清晰,逻辑非常严谨,虽然没有做到一遍成功,但在调试过程中我都比较快速的找到问题,并快速解决。对于学习FPGA的同学,我非常推荐使用明德杨至简设计法和明德杨模块进行学习和设计。

    感兴趣的朋友也可以访问明德扬论坛(http://www.fpgabbs.cn/)进行FPGA相关工程设计学习,也欢迎大家在评论与我进行讨论!


页: [1]
查看完整版本: [至简设计案例]基于FPGA的出租车计费系统