【二级项目】用Verilog HDL与Ruby实现基于状态机的投币机控制


这次是第一次用Verilog HDL做项目,恰好去年学了FPGA,期末考试也恰好考的这道题目。结果还是用了一天下午+一天晚上才把他弄出来。

做这个的时候,最关键的难点还是在测试模块编写。

因为主程序存在

always @(posedge clk) Current_state<=Next_state;

当测试模块电平处于上升沿时,当前状态机才会改变,故每次状态机改变之后,都需要一个上升沿让状态机进行改变。

在考虑,现在这种变化是否会使状态机多次改变?是否换成=也可以,这是对Verilog不熟悉,等待进一步测试,但与本次项目无关。

在Ruby程序编写时,遇到了全局变量的异常问题。下次需要注意,还是要回到《Programing Ruby》中查看

 

投币机主程序(Verilog)

module ex3(out,clk,switch);
  input [1:0] switch;
  input clk;
  output reg[1:0] out;
  reg [2:0] Current_state, Next_state;//定义当前状态、下一状态
  parameter [4:0] Start=3'b000,S1=3'b001,S2=3'b010,Reject=3'b011,Paid=3'b100;//状态机定义
initial
begin
  out[0]=1'b0;out[1]=1'b0;//初始化输出,使开始状态时不退币不出货
  Current_state<=Start;   //初始化状态机,使其从Start开始
  end
  
always @(posedge clk) Current_state<=Next_state;

always @ (Current_state or switch[0] or switch[1])
  
  begin
    case(Current_state)
      Start: begin //初始状态,当前投币0元
            out[0]=1'b0;out[1]=1'b0;//当前状态不退币不出货
            if(switch[0]) Next_state<=S1;//若扫到了5毛,进入投币0.5元的状态(S1)
            if(switch[1]) Next_state<=S2;//若扫到了1元,进入投币1元的状态(S2)
            //if(switch[1]) begin out[0]=1'b1;out[1]=1'b1; end
           end
      S1:begin //当前已经投币0.5元
            out[0]=1'b0;out[1]=1'b0;//当前状态不退币不出货
            if(switch[0]) Next_state<=S2;//若扫到了5毛,进入投币1元的状态(S2)
            if(switch[1]) Next_state<=Paid;//若扫到了1元,进入出货状态(S4)
         end
      S2:begin //当前已经投币1元
            out[0]=1'b0;out[1]=1'b0;//当前状态不退币不出货
            if(switch[0]) Next_state<=Paid;//若扫到了5毛,进入出货状态(S4)
            if(switch[1]) Next_state<=Reject;//若扫到了1元,进入退币的状态(S3)
        end	
      Reject:begin //处于退币状态,退币1元,故现在已经投币1元
            out[0]=1'b1;out[1]=1'b0;//当前状态退币,不出货
            if(switch[0]) Next_state<=Paid;//若扫到了5毛,进入出货状态(S4)
            if(switch[1]) Next_state<=Reject;//若扫到了1元,进入退币的状态(S3)
        end	
      Paid:begin //处于出货状态,不退币,完成出货
            out[0]=1'b0;out[1]=1'b1;
            if(switch[0]) Next_state<=S1;//若扫到了5毛,进入投币0.5元的状态(S1)
            if(switch[1]) Next_state<=S2;//若扫到了1元,进入投币1元的状态(S2)
        end
    endcase
    //if(switch[1]) begin out[0]=1'b1;out[1]=1'b1; end
    //if(switch[0]) begin out[0]=1'b0;out[1]=1'b0; end
  end
  
endmodule

投币机测试平台:

`timescale 1ps/1ps
module tp;
  reg [1:0] switch;
  wire [1:0] out;
  reg clk;
  ex3 u1(.switch(switch),.out(out),.clk(clk));
   initial begin
  clk=1'b0;
  //投一个五毛一个一块
  switch[0]=1;switch[1]=0;
  #50 switch[0]=0;switch[1]=1;//出货
  //投一个一块一个五毛
  #50 switch[0]=0;switch[1]=1;
  #50 switch[0]=1;switch[1]=0;//出货
  //投三个五毛
  #50 switch[0]=1;switch[1]=0;
  #50 switch[0]=1;switch[1]=0;
  #50 switch[0]=1;switch[1]=0;//出货
  //投两个一块
  #50 switch[0]=0;switch[1]=1;
  #50 switch[0]=0;switch[1]=1;//溢出,退币
  #50 switch[0]=1;switch[1]=0;//再投入五毛,出货
  //投两个五毛一个一块
  #50 switch[0]=1;switch[1]=0;
  #50 switch[0]=1;switch[1]=0;
  #50 switch[0]=0;switch[1]=1;//溢出,退币
  #50 switch[0]=1;switch[1]=0;//再投入五毛,出货
  end
  
  always begin
  #40 clk=~clk;
  #10 clk=~clk;
  end
endmodule
  

Ruby程序:

#Copyright:StuEIE2014
#Author:Appix
#Date:2017-09-09
#Coding in Ruby	

#初始化
Start=0b000;S1=0b001;S2=0b010;Reject=0b011;Paid=0b100 #定义状态机
$State=Start;$NextState=Start; #初始化状态机
$stop=0;#退出循环设定
$out0=0;$out1=0;#初始化输出口

#定义输出显示
def Show 
  if $out0==1 #显示当前输出口0(退币)状态
    puts"请从退币口取回您的一元硬币后继续投币,当前已投币1元"
    else puts "当前退币关" 
  end 
  if $out1==1 #显示当前输出口1(出货)状态
    puts"正在出货,请取走您的商品" 
    else puts"当前出货关" 
  end 
end

#定义状态机

#定义Start状态
def IStart 
  $out0=0;$out1=0;
  puts"当前投币为0元,请投币"
  Show();
  $m=gets;
  if $m=="0.5\n"
    $NextState=S1;
  elsif $m=="1\n"
    $NextState=S2;
  elsif $m=="stop\n"
    $stop=1;
  end
end
#定义S1状态
def IS1 
  $out0=0;$out1=0;
  puts"当前投币为0.5元,请投币"
  Show();
  $m=gets;
  if $m=="0.5\n"
    $NextState=S2;
  elsif $m=="1\n"
    $NextState=Paid;
  elsif $m=="stop\n"
    $stop=1;
  end
end	
#定义S2状态	
def IS2 
  $out0=0;$out1=0;
  puts"当前投币为1元,请投币"
  Show();
  $m=gets;
  if $m=="0.5\n"
    $NextState=Paid;
  elsif $m=="1\n"
    $NextState=Reject;
  elsif $m=="stop\n"
    $stop=1
  end
end
 #定义Reject状态
def IReject
  $out0=1;$out1=0;
  puts"当前投币为2元,支付超额"
  Show();
  $m=gets;
  if $m=="0.5\n"
    $NextState=Paid
  elsif $m=="1\n"
    $NextState=Reject
  elsif $m=="stop\n"
    $stop=1
  end
end	
#定义Paid状态
def IPaid 
  $out0=0;$out1=1;
  puts"当前投币为1.5元,支付完成"
  Show();
  puts"当前投币为0元,请投币"
  $m=gets;
  if $m=="0.5\n"
    $NextState=S1
  elsif $m=="1\n"
    $NextState=S2
  elsif $m=="stop\n"
    $stop=1
  end
end		

#主循环
while $stop==0
  #判断当前状态调用子函数
  case $State
    when Start then IStart()
    when S1 then IS1()
    when S2 then IS2()
    when Reject then IReject()
    when Paid then IPaid()
  end
  #进入下一个状态
  $State=$NextState;
end

状态机

状态 状态别称 投入五角 投入一元 退币(Reject) 出货(Paid)
000 Start S1 S2 0 0
001 S1 S2 Paid 0 0
010 S2 Paid Reject 0 0
011 Reject Paid Reject 1 0
100 Paid S1 S2 0 1

 

Verilog仿真代码覆盖率的测试:

modelsim内置的代码覆盖率分析器需要重新建工程来编译,挺麻烦的。

测试模块tp.v 代码覆盖率100%

主程序ex3.v测试代码覆盖率,发现主程序第22行未在测试中覆盖:

检查发现,是由于Start状态下于测试中投入0.5元硬币,此后,状态机不会再次回到Start状态。故一次测试无法覆盖全部代码。

修改tp.v的9-14行代码再次进行仿真,使初始状态进行“先投入1元、再投入5角硬币”的仿真

 

查看ex3.v仿真代码覆盖率,主程序21行未被覆盖

检查仿真结果,仿真结果符合逻辑。

综合以上测试结果,目前ex3.v仿真代码覆盖率100%

 

 


想要成为自己的未来