官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师
您的当前位置:主页 > 高级实训案例 >

【至简设计案例系列】基于FPGA的出租车计费系统设计

发布时间:2020-04-16   作者:admin 浏览量:

本文为明德扬原创及录用文章,转载请注明出处


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  [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 设计思路


    此模块通过一个按键来表示车上是否有乘客,复位时输出信号key_flag0,当乘客上车时,司机按一下按键,key_flag0,1,表明乘客已上车,系统开始计费。到达目的地时,司机再按一下按键,key_flag10,表明到达目的地乘客下车,系统结束计费。

  • 硬件电路

    

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


    从图中可以看出,如果我们按下按键,那么按键就会接通并连接到电平GND,如我们没有按下,那么按键就会断开并接VCC因此按键为低电平有效。通常的按键所用开关为机械弹性开关,当机械触点断开或者闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而机械式按键在闭合及断开的瞬间均伴随有一连串的抖动,如果不进行处理,会使系统识别到抖动信号而进行不必要的反应,导致模块功能不正常,为了避免这种现象的产生,需要进行按键消抖的操作。

  • 按键消抖
    按键消抖主要分为硬件消抖和软件消抖。两个与非门构成一个RS触发器为常用的硬件软件方法抖,即检测出键闭合后执行一个延时程序,抖动时间的长短由按键的机械特性决定,一般为5ms20ms让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认按下按键操作有效。当检测到按键释放后,也要给5ms20ms的延时,待后沿抖动消失后才能转入该键的处理程序。经过按键消抖的行人优先按键,判断按键有效后,按键信号传递给控制系统,控制系统再进入相应的处理程序。如还不明白之处,见实验的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   [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 设计思路


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


     从speed模块中得到乘客乘坐总路程distance和按时计费使能信号en_0,然后以5元起步价,超过3KM以每满1公里2元的计费方式计算出按路程计费的总费用,再通过en_020分钟1元的计费方式计算出按时间计费的总费用,再求和获得总费用,最后得到总费用得个位、十位、百位,分别是x_gx_sx_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   [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_bx_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               : 时钟信号,频率是50MHz
rst_n             : 复位信号,在低电平时有效
seg_sel        : 位选信号,在低电平是该位置数码管亮。
segment       : 段选信号,共8位。由低到高,分别表示数码管的a,b,c,d,e,f,g,点。当该比特为0时,表示点亮相应位置;为1时熄灭。
**********www.mdy-edu.com 明德扬科教注释结束****************/

module  show(
                 rst_n       ,
                 clk         ,
                 disp_en     ,
                 din         ,
                 din_vld     ,

                 seg_sel     ,
                 segment      
             );

/*********www.mdy-edu.com 明德扬科教注释开始****************
参数定义,明德扬规范要求,verilog内的用到的数字,都使用参数表示。
参数信号全部大写
**********www.mdy-edu.com 明德扬科教注释结束****************/

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 效果和总结



  • 仿真验证特殊说明:


    由于本系统涉及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 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


  •  仿真验证结果
    由仿真结果可以看到seg_sel=1101位数码管显示时segment=11000000seg_sel=1012位数码管显示时segment=10010010seg_sel=0113位数码管显示时segment=11000000,在数码管上分别对应数值为050,表示为50元。

    仿真结果和理论结构都是50元,符合我们的预期,验证成功。

    在这个设计中,使用明德杨的至简设计法,让我的思路非常清晰,逻辑非常严谨,虽然没有做到一遍成功,但在调试过程中我都比较快速的找到问题,并快速解决。对于学习FPGA的同学,我非常推荐使用明德杨至简设计法和明德杨模块进行学习和设计。


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

下一篇:没有了
  •   
  •   
  •   
  •  
  • FPGA教育领域第一品牌
  • 咨询热线:020-39002701
  • 技术交流Q群:544453837